diff --git a/conf/app.conf b/conf/app.conf index 41e4c58..c902b66 100644 --- a/conf/app.conf +++ b/conf/app.conf @@ -13,6 +13,7 @@ cla_platform_url = https://clasign.osinfra.cn verification_code_expiry = 300 api_token_expiry = 3600 api_token_key = fsfsfsafsfsasaf242342424sdfs;.]{77&&& +symmetric_encryption_key = key-can-be--16-24-32-bytes-long! pdf_org_signature_dir = ./conf/org_signature_pdf pdf_out_dir = ./conf/pdf diff --git a/config/config.go b/config/config.go index 83a1090..7753ad0 100644 --- a/config/config.go +++ b/config/config.go @@ -21,6 +21,7 @@ type appConfig struct { VerificationCodeExpiry int64 `json:"verification_code_expiry" required:"true"` APITokenExpiry int64 `json:"api_token_expiry" required:"true"` APITokenKey string `json:"api_token_key" required:"true"` + SymmetricEncryptionKey string `json:"symmetric_encryption_key" required:"true"` PDFOrgSignatureDir string `json:"pdf_org_signature_dir" required:"true"` PDFOutDir string `json:"pdf_out_dir" required:"true"` CodePlatformConfigFile string `json:"code_platforms" required:"true"` @@ -77,6 +78,7 @@ func InitAppConfig() error { VerificationCodeExpiry: codeExpiry, APITokenExpiry: tokenExpiry, APITokenKey: beego.AppConfig.String("api_token_key"), + SymmetricEncryptionKey: beego.AppConfig.String("symmetric_encryption_key"), PDFOrgSignatureDir: beego.AppConfig.String("pdf_org_signature_dir"), PDFOutDir: beego.AppConfig.String("pdf_out_dir"), CodePlatformConfigFile: beego.AppConfig.String("code_platforms"), @@ -127,6 +129,10 @@ func (this *appConfig) validate() error { return fmt.Errorf("The length of api_token_key should be bigger than 20") } + if err := util.IsSymmetricEncryptionKeyValid(this.SymmetricEncryptionKey); err != nil { + return fmt.Errorf("The symmetric encryption key is not valid, %s", err.Error()) + } + if util.IsNotDir(this.PDFOrgSignatureDir) { return fmt.Errorf("The directory:%s is not exist", this.PDFOrgSignatureDir) } diff --git a/controllers/access-controller.go b/controllers/access-controller.go index f59dff2..0b4206f 100644 --- a/controllers/access-controller.go +++ b/controllers/access-controller.go @@ -1,12 +1,14 @@ package controllers import ( + "encoding/hex" "encoding/json" "fmt" "github.com/dgrijalva/jwt-go" "github.com/huaweicloud/golangsdk" + "github.com/opensourceways/app-cla-server/config" "github.com/opensourceways/app-cla-server/util" ) @@ -18,6 +20,7 @@ const ( ) type accessController struct { + RemoteAddr string `json:"remote_addr"` Expiry int64 `json:"expiry"` Permission string `json:"permission"` Payload interface{} `json:"payload"` @@ -32,7 +35,12 @@ func (this *accessController) newToken(secret string) (string, error) { token := jwt.New(jwt.SigningMethodHS256) token.Claims = jwt.MapClaims(body) - return token.SignedString([]byte(secret)) + s, err := token.SignedString([]byte(secret)) + if err != nil { + return "", err + } + + return this.encryptToken(s) } func (this *accessController) refreshToken(expiry int64, secret string) (string, error) { @@ -41,7 +49,12 @@ func (this *accessController) refreshToken(expiry int64, secret string) (string, } func (this *accessController) parseToken(token, secret string) error { - t, err := jwt.Parse(token, func(t1 *jwt.Token) (interface{}, error) { + token1, err := this.decryptToken(token) + if err != nil { + return err + } + + t, err := jwt.Parse(token1, func(t1 *jwt.Token) (interface{}, error) { if _, ok := t1.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("Unexpected signing method") } @@ -72,12 +85,46 @@ func (this *accessController) isTokenExpired() bool { return this.Expiry < util.Now() } -func (this *accessController) verify(permission []string) error { +func (this *accessController) verify(permission []string, addr string) error { + bingo := false for _, item := range permission { if this.Permission == item { - return nil + bingo = true + break } } + if !bingo { + return fmt.Errorf("Not allowed permission") + } + + if this.RemoteAddr != addr { + return fmt.Errorf("Unmatched remote address") + } + return nil +} + +func (this *accessController) symmetricEncryptionKey() []byte { + return []byte(config.AppConfig.SymmetricEncryptionKey) +} + +func (this *accessController) encryptToken(token string) (string, error) { + t, err := util.Encrypt([]byte(token), this.symmetricEncryptionKey()) + if err != nil { + return "", err + } + return hex.EncodeToString(t), nil +} + +func (this *accessController) decryptToken(token string) (string, error) { + dst, err := hex.DecodeString(token) + if err != nil { + return "", err + } + + s, err := util.Decrypt(dst, this.symmetricEncryptionKey()) + if err != nil { + return "", err + } - return fmt.Errorf("Not allowed permission") + return string(s), nil } diff --git a/controllers/auth_on_code_platform.go b/controllers/auth_on_code_platform.go index 6e2202e..7c87562 100644 --- a/controllers/auth_on_code_platform.go +++ b/controllers/auth_on_code_platform.go @@ -54,6 +54,12 @@ func (this *AuthController) Callback() { return } + ip, fr := this.getRemoteAddr() + if fr != nil { + rs(fr.errCode, fr.reason) + return + } + cp, err := authHelper.GetAuthInstance(platform) if err != nil { rs(errUnsupportedCodePlatform, err) @@ -81,7 +87,7 @@ func (this *AuthController) Callback() { return } - at, err := this.newApiToken(permission, pl) + at, err := this.newApiToken(permission, ip, pl) if err != nil { rs(errSystemError, err) return @@ -122,6 +128,12 @@ func (this *AuthController) Auth() { return } + ip, fr := this.getRemoteAddr() + if fr != nil { + this.sendFailedResultAsResp(fr, action) + return + } + cp, err := platformAuth.Auth[platformAuth.AuthApplyToLogin].GetAuthInstance(platform) if err != nil { this.sendFailedResponse(400, errUnsupportedCodePlatform, err, action) @@ -141,7 +153,7 @@ func (this *AuthController) Auth() { return } - at, err := this.newApiToken(permission, pl) + at, err := this.newApiToken(permission, ip, pl) if err != nil { this.sendFailedResponse(500, errSystemError, err, action) return diff --git a/controllers/auth_on_corp_manager.go b/controllers/auth_on_corp_manager.go index 99c90d9..d74e910 100644 --- a/controllers/auth_on_corp_manager.go +++ b/controllers/auth_on_corp_manager.go @@ -17,6 +17,12 @@ import ( func (this *CorporationManagerController) Auth() { action := "authenticate as corp/employee manager" + ip, fr := this.getRemoteAddr() + if fr != nil { + this.sendFailedResultAsResp(fr, action) + return + } + var info models.CorporationManagerAuthentication if fr := this.fetchInputPayload(&info); fr != nil { this.sendFailedResultAsResp(fr, action) @@ -44,7 +50,7 @@ func (this *CorporationManagerController) Auth() { result := make([]authInfo, 0, len(v)) for linkID, item := range v { - token, err := this.newAccessToken(linkID, &item) + token, err := this.newAccessToken(linkID, ip, &item) if err != nil { continue } @@ -60,7 +66,7 @@ func (this *CorporationManagerController) Auth() { this.sendSuccessResp(result) } -func (this *CorporationManagerController) newAccessToken(linkID string, info *dbmodels.CorporationManagerCheckResult) (string, error) { +func (this *CorporationManagerController) newAccessToken(linkID, ip string, info *dbmodels.CorporationManagerCheckResult) (string, error) { permission := "" switch info.Role { case dbmodels.RoleAdmin: @@ -70,7 +76,7 @@ func (this *CorporationManagerController) newAccessToken(linkID string, info *db } return this.newApiToken( - permission, + permission, ip, &acForCorpManagerPayload{ Name: info.Name, Email: info.Email, diff --git a/controllers/base-controller.go b/controllers/base-controller.go index 22d3237..38c677b 100644 --- a/controllers/base-controller.go +++ b/controllers/base-controller.go @@ -3,6 +3,7 @@ package controllers import ( "fmt" "io/ioutil" + "net" "net/http" "strings" @@ -29,8 +30,6 @@ func newFailedApiResult(statusCode int, errCode string, err error) *failedApiRes type baseController struct { beego.Controller - - ac *accessController } func (this *baseController) sendResponse(body interface{}, statusCode int) { @@ -94,11 +93,12 @@ func (this *baseController) sendFailedResponse(statusCode int, errCode string, r this.sendResponse(d, statusCode) } -func (this *baseController) newApiToken(permission string, pl interface{}) (string, error) { +func (this *baseController) newApiToken(permission, addr string, pl interface{}) (string, error) { ac := &accessController{ Expiry: util.Expiry(config.AppConfig.APITokenExpiry), Permission: permission, Payload: pl, + RemoteAddr: addr, } return ac.newToken(config.AppConfig.APITokenKey) @@ -233,7 +233,12 @@ func (this *baseController) checkApiReqToken(ac *accessController, permission [] return newFailedApiResult(403, errExpiredToken, fmt.Errorf("token is expired")) } - if err := ac.verify(permission); err != nil { + addr, fr := this.getRemoteAddr() + if fr != nil { + return fr + } + + if err := ac.verify(permission, addr); err != nil { return newFailedApiResult(403, errUnauthorizedToken, err) } @@ -298,3 +303,17 @@ func (this *baseController) setCookies(value map[string]string) { this.Ctx.SetCookie(k, v, "3600", "/") } } + +func (this *baseController) getRemoteAddr() (string, *failedApiResult) { + ips := this.Ctx.Request.Header.Get("x-forwarded-for") + beego.Info(ips) + beego.Info(this.Ctx.Request.Header.Get("x-real-ip")) + + for _, item := range strings.Split(ips, ", ") { + if net.ParseIP(item) != nil { + return item, nil + } + } + + return "", newFailedApiResult(400, errCanNotFetchClientIP, fmt.Errorf("can not fetch client ip")) +} diff --git a/controllers/error.go b/controllers/error.go index ea7374d..9dea58d 100644 --- a/controllers/error.go +++ b/controllers/error.go @@ -39,6 +39,7 @@ const ( errUnsupportedCLALang = "unsupported_cla_lang" errNotSameCorp = string(models.ErrNotSameCorp) errFrequentOperation = "frequent_operation" + errCanNotFetchClientIP = "can_not_fetch_client_ip" ) func parseModelError(err models.IModelError) *failedApiResult { diff --git a/deploy/app.conf b/deploy/app.conf index f561271..82c79fa 100644 --- a/deploy/app.conf +++ b/deploy/app.conf @@ -12,6 +12,7 @@ cla_fields_number = 10 verification_code_expiry = 300 api_token_expiry = 1800 api_token_key = "${API_TOKEN_KEY}" +symmetric_encryption_key = "${SYMMETRIC_ENCRYPTION_KEY}" pdf_org_signature_dir = ./conf/pdfs/org_signature_pdf pdf_out_dir = ./conf/pdfs/output diff --git a/util/symmetric_encryption.go b/util/symmetric_encryption.go new file mode 100644 index 0000000..6968dde --- /dev/null +++ b/util/symmetric_encryption.go @@ -0,0 +1,53 @@ +package util + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "errors" + "io" +) + +func IsSymmetricEncryptionKeyValid(key string) error { + _, err := aes.NewCipher([]byte(key)) + return err +} + +func Encrypt(plaintext []byte, key []byte) ([]byte, error) { + c, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + gcm, err := cipher.NewGCM(c) + if err != nil { + return nil, err + } + + nonce := make([]byte, gcm.NonceSize()) + if _, err = io.ReadFull(rand.Reader, nonce); err != nil { + return nil, err + } + + return gcm.Seal(nonce, nonce, plaintext, nil), nil +} + +func Decrypt(ciphertext []byte, key []byte) ([]byte, error) { + c, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + gcm, err := cipher.NewGCM(c) + if err != nil { + return nil, err + } + + nonceSize := gcm.NonceSize() + if len(ciphertext) < nonceSize { + return nil, errors.New("ciphertext too short") + } + + nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] + return gcm.Open(nil, nonce, ciphertext, nil) +}