diff --git a/conf/app.conf.yaml b/conf/app.conf.yaml index 2da6885..f308a51 100644 --- a/conf/app.conf.yaml +++ b/conf/app.conf.yaml @@ -7,6 +7,7 @@ verification_code_expiry: 300 api_token_expiry: 3600 api_token_key: fsfsfsafsfsasaf242342424sdfs;.]{77&&& symmetric_encryption_key: key-can-be--16-24-32-bytes-long! +symmetric_encryption_nonce: {{hex encoded 12 bytes}} pdf_org_signature_dir: ./conf/org_signature_pdf pdf_out_dir: ./conf/pdf diff --git a/config/config.go b/config/config.go index 77820d7..b182f7d 100644 --- a/config/config.go +++ b/config/config.go @@ -30,6 +30,7 @@ type appConfig struct { APITokenExpiry int64 `json:"api_token_expiry" required:"true"` APITokenKey string `json:"api_token_key" required:"true"` SymmetricEncryptionKey string `json:"symmetric_encryption_key" required:"true"` + SymmetricEncryptionNonce string `json:"symmetric_encryption_nonce" 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"` @@ -99,8 +100,8 @@ func (cfg *appConfig) validate() error { return fmt.Errorf("The length of api_token_key should be bigger than 20") } - if err := util.IsSymmetricEncryptionKeyValid(cfg.SymmetricEncryptionKey); err != nil { - return fmt.Errorf("The symmetric encryption key is not valid, %s", err.Error()) + if _, err := util.NewSymmetricEncryption(cfg.SymmetricEncryptionKey, cfg.SymmetricEncryptionNonce); err != nil { + return fmt.Errorf("The symmetric encryption key or nonce is invalid, %s", err.Error()) } if util.IsNotDir(cfg.PDFOrgSignatureDir) { diff --git a/controllers/access-controller.go b/controllers/access-controller.go index 0b4206f..57e27f9 100644 --- a/controllers/access-controller.go +++ b/controllers/access-controller.go @@ -103,12 +103,13 @@ func (this *accessController) verify(permission []string, addr string) error { return nil } -func (this *accessController) symmetricEncryptionKey() []byte { - return []byte(config.AppConfig.SymmetricEncryptionKey) +func (this *accessController) newEncryption() util.SymmetricEncryption { + e, _ := util.NewSymmetricEncryption(config.AppConfig.SymmetricEncryptionKey, "") + return e } func (this *accessController) encryptToken(token string) (string, error) { - t, err := util.Encrypt([]byte(token), this.symmetricEncryptionKey()) + t, err := this.newEncryption().Encrypt([]byte(token)) if err != nil { return "", err } @@ -121,7 +122,7 @@ func (this *accessController) decryptToken(token string) (string, error) { return "", err } - s, err := util.Decrypt(dst, this.symmetricEncryptionKey()) + s, err := this.newEncryption().Decrypt(dst) if err != nil { return "", err } diff --git a/controllers/corporation-manager.go b/controllers/corporation-manager.go index 3356267..443aa42 100644 --- a/controllers/corporation-manager.go +++ b/controllers/corporation-manager.go @@ -90,7 +90,7 @@ func (this *CorporationManagerController) Put() { this.sendSuccessResp(action + " successfully") - notifyCorpAdmin(orgInfo, added) + notifyCorpAdmin(linkID, orgInfo, added) } // @Title Patch diff --git a/controllers/employee-manager.go b/controllers/employee-manager.go index 7c79225..8ad2a49 100644 --- a/controllers/employee-manager.go +++ b/controllers/employee-manager.go @@ -50,7 +50,7 @@ func (this *EmployeeManagerController) Post() { this.sendSuccessResp(action + " successfully") - notifyCorpManagerWhenAdding(&pl.OrgInfo, added) + notifyCorpManagerWhenAdding(pl.LinkID, &pl.OrgInfo, added) } // @Title Delete @@ -95,7 +95,7 @@ func (this *EmployeeManagerController) Delete() { Org: pl.OrgAlias, ProjectURL: pl.ProjectURL(), } - sendEmailToIndividual(item.Email, pl.OrgEmail, subject, msg) + sendEmailToIndividual(pl.LinkID, item.Email, subject, msg) } } diff --git a/controllers/employee-signing.go b/controllers/employee-signing.go index 83bd55f..3e6291d 100644 --- a/controllers/employee-signing.go +++ b/controllers/employee-signing.go @@ -112,7 +112,7 @@ func (this *EmployeeSigningController) Post() { this.sendFailedResultAsResp(fr, action) } else { this.sendSuccessResp("sign successfully") - this.notifyManagers(managers, &info, orgInfo) + this.notifyManagers(linkID, managers, &info, orgInfo) } } @@ -222,10 +222,10 @@ func (this *EmployeeSigningController) Update() { msg := this.newEmployeeNotification(pl, employeeEmail) if info.Enabled { msg.Active = true - sendEmailToIndividual(employeeEmail, pl.OrgEmail, "Activate CLA signing", msg) + sendEmailToIndividual(pl.LinkID, employeeEmail, "Activate CLA signing", msg) } else { msg.Inactive = true - sendEmailToIndividual(employeeEmail, pl.OrgEmail, "Inactivate CLA signing", msg) + sendEmailToIndividual(pl.LinkID, employeeEmail, "Inactivate CLA signing", msg) } } @@ -258,10 +258,10 @@ func (this *EmployeeSigningController) Delete() { msg := this.newEmployeeNotification(pl, employeeEmail) msg.Removing = true - sendEmailToIndividual(employeeEmail, pl.OrgEmail, "Remove employee", msg) + sendEmailToIndividual(pl.LinkID, employeeEmail, "Remove employee", msg) } -func (this *EmployeeSigningController) notifyManagers(managers []dbmodels.CorporationManagerListResult, info *models.EmployeeSigning, orgInfo *models.OrgInfo) { +func (this *EmployeeSigningController) notifyManagers(linkID string, managers []dbmodels.CorporationManagerListResult, info *models.EmployeeSigning, orgInfo *models.OrgInfo) { ms := make([]string, 0, len(managers)) to := make([]string, 0, len(managers)) for _, item := range managers { @@ -276,7 +276,7 @@ func (this *EmployeeSigningController) notifyManagers(managers []dbmodels.Corpor Managers: " " + strings.Join(ms, "\n "), } sendEmailToIndividual( - info.Email, orgInfo.OrgEmail, + linkID, info.Email, fmt.Sprintf("Signing CLA on project of \"%s\"", msg.Org), msg, ) @@ -287,7 +287,7 @@ func (this *EmployeeSigningController) notifyManagers(managers []dbmodels.Corpor ProjectURL: orgInfo.ProjectURL(), URLOfCLAPlatform: config.AppConfig.CLAPlatformURL, } - sendEmail(to, orgInfo.OrgEmail, "An employee has signed CLA", msg1) + sendEmail(linkID, to, "An employee has signed CLA", msg1) } func (this *EmployeeSigningController) newEmployeeNotification(pl *acForCorpManagerPayload, employeeName string) *email.EmployeeNotification { diff --git a/controllers/org-email.go b/controllers/org-email.go index 338ea13..d804086 100644 --- a/controllers/org-email.go +++ b/controllers/org-email.go @@ -59,7 +59,12 @@ func (this *EmailController) Auth() { } if token.RefreshToken == "" { - if _, err := models.GetOrgEmailInfo(emailAddr); err != nil { + v, err := models.HasOrgEmail(emailAddr) + if err != nil { + rs(errSystemError, err) + return + } + if !v { rs(errNoRefreshToken, fmt.Errorf("no refresh token")) return } diff --git a/controllers/util.go b/controllers/util.go index a394d4e..94aa8d6 100644 --- a/controllers/util.go +++ b/controllers/util.go @@ -22,11 +22,11 @@ const ( fileNameOfUploadingOrgSignatue = "org_signature_file" ) -func sendEmailToIndividual(to, from, subject string, builder email.IEmailMessageBulder) { - sendEmail([]string{to}, from, subject, builder) +func sendEmailToIndividual(linkID, to, subject string, builder email.IEmailMessageBulder) { + sendEmail(linkID, []string{to}, subject, builder) } -func sendEmail(to []string, from, subject string, builder email.IEmailMessageBulder) { +func sendEmail(linkID string, to []string, subject string, builder email.IEmailMessageBulder) { msg, err := builder.GenEmailMsg() if err != nil { beego.Error(err) @@ -36,14 +36,14 @@ func sendEmail(to []string, from, subject string, builder email.IEmailMessageBul msg.To = to msg.Subject = subject - worker.GetEmailWorker().SendSimpleMessage(from, msg) + worker.GetEmailWorker().SendSimpleMessage(linkID, msg) } -func notifyCorpAdmin(orgInfo *models.OrgInfo, info *dbmodels.CorporationManagerCreateOption) { - notifyCorpManagerWhenAdding(orgInfo, []dbmodels.CorporationManagerCreateOption{*info}) +func notifyCorpAdmin(linkID string, orgInfo *models.OrgInfo, info *dbmodels.CorporationManagerCreateOption) { + notifyCorpManagerWhenAdding(linkID, orgInfo, []dbmodels.CorporationManagerCreateOption{*info}) } -func notifyCorpManagerWhenAdding(orgInfo *models.OrgInfo, info []dbmodels.CorporationManagerCreateOption) { +func notifyCorpManagerWhenAdding(linkID string, orgInfo *models.OrgInfo, info []dbmodels.CorporationManagerCreateOption) { admin := (info[0].Role == dbmodels.RoleAdmin) subject := fmt.Sprintf("Account on project of \"%s\"", orgInfo.OrgAlias) @@ -60,7 +60,7 @@ func notifyCorpManagerWhenAdding(orgInfo *models.OrgInfo, info []dbmodels.Corpor URLOfCLAPlatform: config.AppConfig.CLAPlatformURL, } - sendEmailToIndividual(item.Email, orgInfo.OrgEmail, subject, d) + sendEmailToIndividual(linkID, item.Email, subject, d) } } diff --git a/controllers/verification_code.go b/controllers/verification_code.go index 17f4504..7249ce7 100644 --- a/controllers/verification_code.go +++ b/controllers/verification_code.go @@ -45,7 +45,7 @@ func (this *VerificationCodeController) Post() { this.sendSuccessResp("create verification code successfully") sendEmailToIndividual( - emailOfSigner, orgInfo.OrgEmail, + linkID, emailOfSigner, fmt.Sprintf( "Verification code for signing CLA on project of \"%s\"", orgInfo.OrgAlias, diff --git a/dbmodels/db.go b/dbmodels/db.go index 865f2f9..c000172 100644 --- a/dbmodels/db.go +++ b/dbmodels/db.go @@ -56,6 +56,7 @@ type ICorporationManager interface { type IOrgEmail interface { CreateOrgEmail(opt OrgEmailCreateInfo) IDBError GetOrgEmailInfo(email string) (*OrgEmailCreateInfo, IDBError) + GetOrgEmailOfLink(linkID string) (*OrgEmailCreateInfo, IDBError) } type IIndividualSigning interface { diff --git a/deploy/app.conf.yaml b/deploy/app.conf.yaml index d5a6291..1644d1e 100644 --- a/deploy/app.conf.yaml +++ b/deploy/app.conf.yaml @@ -6,6 +6,7 @@ verification_code_expiry: 300 api_token_expiry: 1800 api_token_key: "${API_TOKEN_KEY}" symmetric_encryption_key: "${SYMMETRIC_ENCRYPTION_KEY}" +symmetric_encryption_nonce: "${SYMMETRIC_ENCRYPTION_NONCE}" pdf_org_signature_dir: ./conf/pdfs/org_signature_pdf pdf_out_dir: ./conf/pdfs/output diff --git a/main.go b/main.go index 0911021..fa5dd0a 100644 --- a/main.go +++ b/main.go @@ -40,7 +40,11 @@ func main() { } } - mongoClient, err := mongodb.Initialize(&AppConfig.Mongodb) + mongoClient, err := mongodb.Initialize( + &AppConfig.Mongodb, + AppConfig.SymmetricEncryptionKey, + AppConfig.SymmetricEncryptionNonce, + ) if err != nil { beego.Error(err) os.Exit(1) diff --git a/models/org-email.go b/models/org-email.go index 0596189..b60e1b5 100644 --- a/models/org-email.go +++ b/models/org-email.go @@ -30,8 +30,8 @@ func (this *OrgEmail) Create() IModelError { return parseDBError(dbErr) } -func GetOrgEmailInfo(email string) (*OrgEmail, IModelError) { - info, err := dbmodels.GetDB().GetOrgEmailInfo(email) +func GetOrgEmailOfLink(linkID string) (*OrgEmail, IModelError) { + info, err := dbmodels.GetDB().GetOrgEmailOfLink(linkID) if err != nil { if err.IsErrorOf(dbmodels.ErrNoDBRecord) { return nil, newModelError(ErrOrgEmailNotExists, err) @@ -46,8 +46,20 @@ func GetOrgEmailInfo(email string) (*OrgEmail, IModelError) { } return &OrgEmail{ - Email: email, + Email: info.Email, Token: &token, Platform: info.Platform, }, nil } + +func HasOrgEmail(email string) (bool, IModelError) { + _, err := dbmodels.GetDB().GetOrgEmailInfo(email) + if err == nil { + return true, nil + } + + if err.IsErrorOf(dbmodels.ErrNoDBRecord) { + return false, nil + } + return false, parseDBError(err) +} diff --git a/mongodb/corporation-manager.go b/mongodb/corporation-manager.go index 71f8d6b..975f202 100644 --- a/mongodb/corporation-manager.go +++ b/mongodb/corporation-manager.go @@ -13,11 +13,8 @@ func docFilterOfCorpManager(linkID string) bson.M { return docFilterOfSigning(linkID) } -func elemFilterOfCorpManager(email string) bson.M { - return bson.M{ - fieldCorpID: genCorpID(email), - fieldEmail: email, - } +func (c *client) elemFilterOfCorpManager(email string) (bson.M, dbmodels.IDBError) { + return c.elemFilterOfIndividualSigning(email) } func memberNameOfCorpManager(field string) string { @@ -25,10 +22,15 @@ func memberNameOfCorpManager(field string) string { } func (this *client) AddCorpAdministrator(linkID string, opt *dbmodels.CorporationManagerCreateOption) dbmodels.IDBError { + email, err := this.encrypt.encryptStr(opt.Email) + if err != nil { + return err + } + info := dCorpManager{ ID: opt.ID, Name: opt.Name, - Email: opt.Email, + Email: email, Role: dbmodels.RoleAdmin, Password: opt.Password, CorpID: genCorpID(opt.Email), @@ -65,7 +67,11 @@ func (this *client) CheckCorporationManagerExist(opt dbmodels.CorporationManager var elemFilter bson.M if opt.Email != "" { - elemFilter = elemFilterOfCorpManager(opt.Email) + v, err := this.elemFilterOfCorpManager(opt.Email) + if err != nil { + return nil, err + } + elemFilter = v } else { elemFilter = bson.M{ fieldCorpID: opt.EmailSuffix, @@ -109,10 +115,16 @@ func (this *client) CheckCorporationManagerExist(opt dbmodels.CorporationManager } item := &cm[0] + + email, err := this.encrypt.decryptStr(item.Email) + if err != nil { + return nil, err + } + orgRepo := dbmodels.ParseToOrgRepo(doc.OrgIdentity) result[doc.LinkID] = dbmodels.CorporationManagerCheckResult{ Name: item.Name, - Email: item.Email, + Email: email, Role: item.Role, Password: item.Password, InitialPWChanged: item.InitialPWChanged, @@ -138,7 +150,10 @@ func (this *client) ResetCorporationManagerPassword(linkID, email string, opt db fieldChanged: true, } - elemFilter := elemFilterOfCorpManager(email) + elemFilter, err := this.elemFilterOfCorpManager(email) + if err != nil { + return err + } elemFilter[fieldPassword] = opt.OldPassword docFilter := docFilterOfCorpManager(linkID) @@ -191,10 +206,16 @@ func (this *client) ListCorporationManager(linkID, email, role string) ([]dbmode r := make([]dbmodels.CorporationManagerListResult, 0, len(ms)) for i := range ms { item := &ms[i] + + email, err := this.encrypt.decryptStr(item.Email) + if err != nil { + return nil, err + } + r = append(r, dbmodels.CorporationManagerListResult{ ID: item.ID, Name: item.Name, - Email: item.Email, + Email: email, Role: item.Role, }) } @@ -202,6 +223,11 @@ func (this *client) ListCorporationManager(linkID, email, role string) ([]dbmode } func (this *client) GetCorporationManager(linkID, email string) (*dbmodels.CorporationManagerCheckResult, dbmodels.IDBError) { + elemFilter, err := this.elemFilterOfCorpManager(email) + if err != nil { + return nil, err + } + project := bson.M{ memberNameOfCorpManager(fieldPassword): 1, } @@ -211,8 +237,7 @@ func (this *client) GetCorporationManager(linkID, email string) (*dbmodels.Corpo f := func(ctx context.Context) error { return this.getArrayElem( ctx, this.corpSigningCollection, fieldCorpManagers, - docFilterOfCorpManager(linkID), elemFilterOfCorpManager(email), - project, &v, + docFilterOfCorpManager(linkID), elemFilter, project, &v, ) } diff --git a/mongodb/corporation-signing-deleted.go b/mongodb/corporation-signing-deleted.go index b725755..a39fe4e 100644 --- a/mongodb/corporation-signing-deleted.go +++ b/mongodb/corporation-signing-deleted.go @@ -49,6 +49,7 @@ func (this *client) DeleteCorpSigning(linkID, email string) dbmodels.IDBError { if err != nil { return err } + doc[fieldInfo] = data.SigningInfo f := func(ctx context.Context) dbmodels.IDBError { return this.moveArrayElem( @@ -97,7 +98,12 @@ func (this *client) ListDeletedCorpSignings(linkID string) ([]dbmodels.Corporati r := make([]dbmodels.CorporationSigningBasicInfo, 0, n) for i := 0; i < n; i++ { - r = append(r, *toDBModelCorporationSigningBasicInfo(&deleted[i])) + bi, err := this.toDBModelCorporationSigningBasicInfo(&deleted[i]) + if err != nil { + return nil, err + } + + r = append(r, *bi) } return r, nil diff --git a/mongodb/corporation-signing.go b/mongodb/corporation-signing.go index 1d031f0..b5cd5bd 100644 --- a/mongodb/corporation-signing.go +++ b/mongodb/corporation-signing.go @@ -14,19 +14,29 @@ func elemFilterOfCorpSigning(email string) bson.M { } func (c *client) SignCorpCLA(linkID string, info *dbmodels.CorpSigningCreateOpt) dbmodels.IDBError { + email, err := c.encrypt.encryptStr(info.AdminEmail) + if err != nil { + return err + } + + si, err := c.encrypt.encryptSigningInfo(&info.Info) + if err != nil { + return err + } + signing := dCorpSigning{ CLALanguage: info.CLALanguage, CorpID: genCorpID(info.AdminEmail), CorpName: info.CorporationName, - AdminEmail: info.AdminEmail, + AdminEmail: email, AdminName: info.AdminName, Date: info.Date, - SigningInfo: info.Info, } doc, err := structToMap(signing) if err != nil { return err } + doc[fieldInfo] = si docFilter := docFilterOfSigning(linkID) arrayFilterByElemMatch(fieldSignings, false, elemFilterOfCorpSigning(info.AdminEmail), docFilter) @@ -78,8 +88,13 @@ func (this *client) ListCorpSignings(linkID, language string) ([]dbmodels.Corpor r := make([]dbmodels.CorporationSigningSummary, 0, n) for i := 0; i < n; i++ { + bi, err := this.toDBModelCorporationSigningBasicInfo(&signings[i]) + if err != nil { + return nil, err + } + r = append(r, dbmodels.CorporationSigningSummary{ - CorporationSigningBasicInfo: *toDBModelCorporationSigningBasicInfo(&signings[i]), + CorporationSigningBasicInfo: *bi, AdminAdded: admins[signings[i].AdminEmail], }) } @@ -129,7 +144,7 @@ func (this *client) GetCorpSigningBasicInfo(linkID, email string) (*dbmodels.Cor return nil, nil } - return toDBModelCorporationSigningBasicInfo(&(signings[0])), nil + return this.toDBModelCorporationSigningBasicInfo(&(signings[0])) } func (this *client) GetCorpSigningDetail(linkID, email string) ([]dbmodels.Field, *dbmodels.CorpSigningCreateOpt, dbmodels.IDBError) { @@ -181,21 +196,36 @@ func (this *client) GetCorpSigningDetail(linkID, email string) ([]dbmodels.Field return nil, nil, nil } + si, err := this.encrypt.decryptSigningInfo(signing.SigningInfo) + if err != nil { + return nil, nil, err + } + + bi, err := this.toDBModelCorporationSigningBasicInfo(signing) + if err != nil { + return nil, nil, err + } + info := &dbmodels.CorpSigningCreateOpt{ - CorporationSigningBasicInfo: *toDBModelCorporationSigningBasicInfo(signing), - Info: signing.SigningInfo, + CorporationSigningBasicInfo: *bi, + Info: *si, } return toModelOfCLAFields(clas[0].Fields), info, nil } -func toDBModelCorporationSigningBasicInfo(cs *dCorpSigning) *dbmodels.CorporationSigningBasicInfo { +func (c *client) toDBModelCorporationSigningBasicInfo(cs *dCorpSigning) (*dbmodels.CorporationSigningBasicInfo, dbmodels.IDBError) { + email, err := c.encrypt.decryptStr(cs.AdminEmail) + if err != nil { + return nil, err + } + return &dbmodels.CorporationSigningBasicInfo{ CLALanguage: cs.CLALanguage, - AdminEmail: cs.AdminEmail, + AdminEmail: email, AdminName: cs.AdminName, CorporationName: cs.CorpName, Date: cs.Date, - } + }, nil } func projectOfCorpSigning() bson.M { diff --git a/mongodb/employee-manager.go b/mongodb/employee-manager.go index 8178bb0..15eca43 100644 --- a/mongodb/employee-manager.go +++ b/mongodb/employee-manager.go @@ -12,10 +12,16 @@ func (this *client) AddEmployeeManager(linkID string, opt []dbmodels.Corporation toAdd := make(bson.A, 0, len(opt)) for i := range opt { item := &opt[i] + + email, err := this.encrypt.encryptStr(item.Email) + if err != nil { + return err + } + info := dCorpManager{ ID: item.ID, Name: item.Name, - Email: item.Email, + Email: email, Role: item.Role, Password: item.Password, CorpID: genCorpID(item.Email), @@ -40,9 +46,20 @@ func (this *client) AddEmployeeManager(linkID string, opt []dbmodels.Corporation } func (this *client) DeleteEmployeeManager(linkID string, emails []string) ([]dbmodels.CorporationManagerCreateOption, dbmodels.IDBError) { + encryptedEmails := make([]string, 0, len(emails)) + m := map[string]string{} + for _, item := range emails { + email, err := this.encrypt.encryptStr(item) + if err != nil { + return nil, err + } + encryptedEmails = append(encryptedEmails, email) + m[email] = item + } + elemFilter := bson.M{ fieldCorpID: genCorpID(emails[0]), - fieldEmail: bson.M{"$in": emails}, + fieldEmail: bson.M{"$in": encryptedEmails}, } var v cCorpSigning @@ -62,7 +79,7 @@ func (this *client) DeleteEmployeeManager(linkID string, emails []string) ([]dbm deleted := make([]dbmodels.CorporationManagerCreateOption, 0, len(ms)) for _, item := range ms { deleted = append(deleted, dbmodels.CorporationManagerCreateOption{ - Email: item.Email, + Email: m[item.Email], Name: item.Name, }) } diff --git a/mongodb/encryption.go b/mongodb/encryption.go new file mode 100644 index 0000000..db150b7 --- /dev/null +++ b/mongodb/encryption.go @@ -0,0 +1,87 @@ +package mongodb + +import ( + "encoding/hex" + "encoding/json" + + "github.com/opensourceways/app-cla-server/dbmodels" + "github.com/opensourceways/app-cla-server/util" +) + +func newEncryption(key, nonce string) (encryption, error) { + e := encryption{} + + se, err := util.NewSymmetricEncryption(key, nonce) + if err != nil { + return e, err + } + + e.se = se + return e, nil +} + +type encryption struct { + se util.SymmetricEncryption +} + +func (e encryption) encryptBytes(data []byte) ([]byte, dbmodels.IDBError) { + d, err := e.se.Encrypt(data) + if err != nil { + return nil, newSystemError(err) + } + return d, nil +} + +func (e encryption) decryptBytes(data []byte) ([]byte, dbmodels.IDBError) { + s, err := e.se.Decrypt(data) + if err != nil { + return nil, newSystemError(err) + } + return s, nil +} + +func (e encryption) encryptStr(data string) (string, dbmodels.IDBError) { + d, err := e.encryptBytes([]byte(data)) + if err != nil { + return "", err + } + + return hex.EncodeToString(d), nil +} + +func (e encryption) decryptStr(data string) (string, dbmodels.IDBError) { + b, err := hex.DecodeString(data) + if err != nil { + return "", newSystemError(err) + } + + s, err1 := e.decryptBytes(b) + if err1 != nil { + return "", err1 + } + + return string(s), nil +} + +func (e encryption) encryptSigningInfo(data *dbmodels.TypeSigningInfo) ([]byte, dbmodels.IDBError) { + b, err := json.Marshal(*data) + if err != nil { + return nil, newSystemError(err) + } + + return e.encryptBytes(b) +} + +func (e encryption) decryptSigningInfo(data []byte) (*dbmodels.TypeSigningInfo, dbmodels.IDBError) { + b, err := e.decryptBytes(data) + if err != nil { + return nil, err + } + + var d dbmodels.TypeSigningInfo + + if err := json.Unmarshal(b, &d); err != nil { + return nil, newSystemError(err) + } + return &d, nil +} diff --git a/mongodb/individual-signing.go b/mongodb/individual-signing.go index d639ec7..7f88a67 100644 --- a/mongodb/individual-signing.go +++ b/mongodb/individual-signing.go @@ -8,11 +8,16 @@ import ( "github.com/opensourceways/app-cla-server/dbmodels" ) -func elemFilterOfIndividualSigning(email string) bson.M { +func (c *client) elemFilterOfIndividualSigning(email string) (bson.M, dbmodels.IDBError) { + encryptedEmail, err := c.encrypt.encryptStr(email) + if err != nil { + return nil, err + } + return bson.M{ fieldCorpID: genCorpID(email), - fieldEmail: email, - } + fieldEmail: encryptedEmail, + }, nil } func docFilterOfSigning(linkID string) bson.M { @@ -23,25 +28,37 @@ func docFilterOfSigning(linkID string) bson.M { } func (this *client) SignIndividualCLA(linkID string, info *dbmodels.IndividualSigningInfo) dbmodels.IDBError { + email, err := this.encrypt.encryptStr(info.Email) + if err != nil { + return err + } + + si, err := this.encrypt.encryptSigningInfo(&info.Info) + if err != nil { + return err + } + signing := dIndividualSigning{ CLALanguage: info.CLALanguage, CorpID: genCorpID(info.Email), ID: info.ID, Name: info.Name, - Email: info.Email, + Email: email, Date: info.Date, Enabled: info.Enabled, - SigningInfo: info.Info, } doc, err := structToMap(signing) if err != nil { return err } + doc[fieldInfo] = si + elemFilter := bson.M{ + fieldCorpID: genCorpID(info.Email), + fieldEmail: email, + } docFilter := docFilterOfSigning(linkID) - arrayFilterByElemMatch( - fieldSignings, false, elemFilterOfIndividualSigning(info.Email), docFilter, - ) + arrayFilterByElemMatch(fieldSignings, false, elemFilter, docFilter) f := func(ctx context.Context) dbmodels.IDBError { return this.pushArrayElem(ctx, this.individualSigningCollection, fieldSignings, docFilter, doc) @@ -51,11 +68,15 @@ func (this *client) SignIndividualCLA(linkID string, info *dbmodels.IndividualSi } func (this *client) DeleteIndividualSigning(linkID, email string) dbmodels.IDBError { + elemFilter, err := this.elemFilterOfIndividualSigning(email) + if err != nil { + return err + } + f := func(ctx context.Context) dbmodels.IDBError { return this.pullArrayElem( ctx, this.individualSigningCollection, fieldSignings, - docFilterOfSigning(linkID), - elemFilterOfIndividualSigning(email), + docFilterOfSigning(linkID), elemFilter, ) } @@ -63,7 +84,10 @@ func (this *client) DeleteIndividualSigning(linkID, email string) dbmodels.IDBEr } func (this *client) UpdateIndividualSigning(linkID, email string, enabled bool) dbmodels.IDBError { - elemFilter := elemFilterOfIndividualSigning(email) + elemFilter, err := this.elemFilterOfIndividualSigning(email) + if err != nil { + return err + } docFilter := docFilterOfSigning(linkID) arrayFilterByElemMatch(fieldSignings, true, elemFilter, docFilter) @@ -79,11 +103,14 @@ func (this *client) UpdateIndividualSigning(linkID, email string, enabled bool) } func (this *client) IsIndividualSigned(linkID, email string) (bool, dbmodels.IDBError) { - docFilter := docFilterOfSigning(linkID) - - elemFilter := elemFilterOfIndividualSigning(email) + elemFilter, err := this.elemFilterOfIndividualSigning(email) + if err != nil { + return false, err + } elemFilter[fieldEnabled] = true + docFilter := docFilterOfSigning(linkID) + signed := false f := func(ctx context.Context) dbmodels.IDBError { v, err := this.isArrayElemNotExists( @@ -96,7 +123,7 @@ func (this *client) IsIndividualSigned(linkID, email string) (bool, dbmodels.IDB return nil } - err := withContext1(f) + err = withContext1(f) return signed, err } @@ -138,9 +165,15 @@ func (this *client) ListIndividualSigning(linkID, corpEmail, claLang string) ([] r := make([]dbmodels.IndividualSigningBasicInfo, 0, len(docs)) for i := range docs { item := &docs[i] + + email, err := this.encrypt.decryptStr(item.Email) + if err != nil { + return nil, err + } + r = append(r, dbmodels.IndividualSigningBasicInfo{ ID: item.ID, - Email: item.Email, + Email: email, Name: item.Name, Enabled: item.Enabled, Date: item.Date, diff --git a/mongodb/models.go b/mongodb/models.go index 97ddc85..e44a2fe 100644 --- a/mongodb/models.go +++ b/mongodb/models.go @@ -1,10 +1,6 @@ package mongodb -import ( - "fmt" - - "github.com/opensourceways/app-cla-server/dbmodels" -) +import "fmt" const ( fieldLinkID = "link_id" @@ -41,6 +37,7 @@ const ( fieldDate = "date" fieldCorp = "corp" fieldEnabled = "enabled" + fieldInfo = "info" // 'ready' means the doc is ready to record the signing data currently. // 'deleted' means the signing data is invalid. @@ -79,7 +76,7 @@ type dIndividualSigning struct { Date string `bson:"date" json:"date" required:"true"` Enabled bool `bson:"enabled" json:"enabled"` - SigningInfo dbmodels.TypeSigningInfo `bson:"info" json:"info,omitempty"` + SigningInfo []byte `bson:"info" json:"-"` } type cCorpSigning struct { @@ -105,7 +102,7 @@ type dCorpSigning struct { AdminName string `bson:"name" json:"name" required:"true"` Date string `bson:"date" json:"date" required:"true"` - SigningInfo dbmodels.TypeSigningInfo `bson:"info" json:"info,omitempty"` + SigningInfo []byte `bson:"info" json:"-"` } type dCorpManager struct { diff --git a/mongodb/mongodb.go b/mongodb/mongodb.go index f6579a2..71ceec7 100644 --- a/mongodb/mongodb.go +++ b/mongodb/mongodb.go @@ -16,8 +16,9 @@ import ( var _ dbmodels.IModel = (*client)(nil) type client struct { - c *mongo.Client - db *mongo.Database + c *mongo.Client + db *mongo.Database + encrypt encryption vcCollection string orgEmailCollection string @@ -27,7 +28,7 @@ type client struct { individualSigningCollection string } -func Initialize(cfg *config.MongodbConfig) (*client, error) { +func Initialize(cfg *config.MongodbConfig, encryptionKey, nonce string) (*client, error) { c, err := mongo.NewClient(options.Client().ApplyURI(cfg.MongodbConn)) if err != nil { return nil, err @@ -45,9 +46,15 @@ func Initialize(cfg *config.MongodbConfig) (*client, error) { return nil, err } + e, err := newEncryption(encryptionKey, nonce) + if err != nil { + return nil, err + } + cli := &client{ - c: c, - db: c.Database(cfg.DBName), + c: c, + db: c.Database(cfg.DBName), + encrypt: e, vcCollection: cfg.VCCollection, orgEmailCollection: cfg.OrgEmailCollection, diff --git a/mongodb/org-email.go b/mongodb/org-email.go index 7df4add..ed78a09 100644 --- a/mongodb/org-email.go +++ b/mongodb/org-email.go @@ -28,6 +28,12 @@ func (this *client) CreateOrgEmail(opt dbmodels.OrgEmailCreateInfo) dbmodels.IDB return err } + t, err := this.encrypt.encryptBytes(opt.Token) + if err != nil { + return err + } + body[fieldToken] = t + f := func(ctx context.Context) dbmodels.IDBError { _, err := this.replaceDoc(ctx, this.orgEmailCollection, bson.M{fieldEmail: opt.Email}, body) return err @@ -53,3 +59,34 @@ func (this *client) GetOrgEmailInfo(email string) (*dbmodels.OrgEmailCreateInfo, Token: v.Token, }, nil } + +func (this *client) GetOrgEmailOfLink(linkID string) (*dbmodels.OrgEmailCreateInfo, dbmodels.IDBError) { + var v cLink + f := func(ctx context.Context) dbmodels.IDBError { + return this.getDoc( + ctx, this.linkCollection, + bson.M{ + fieldLinkID: linkID, + fieldLinkStatus: linkStatusReady, + }, + bson.M{fieldOrgEmail: 1}, &v, + ) + } + + if err := withContext1(f); err != nil { + return nil, err + } + + oe := &v.OrgEmail + + t, err := this.encrypt.decryptBytes(oe.Token) + if err != nil { + return nil, err + } + + return &dbmodels.OrgEmailCreateInfo{ + Email: oe.Email, + Platform: oe.Platform, + Token: t, + }, nil +} diff --git a/util/symmetric_encryption.go b/util/symmetric_encryption.go index 6968dde..18b4398 100644 --- a/util/symmetric_encryption.go +++ b/util/symmetric_encryption.go @@ -4,17 +4,19 @@ import ( "crypto/aes" "crypto/cipher" "crypto/rand" + "encoding/hex" "errors" + "fmt" "io" ) -func IsSymmetricEncryptionKeyValid(key string) error { - _, err := aes.NewCipher([]byte(key)) - return err +type SymmetricEncryption interface { + Encrypt(plaintext []byte) ([]byte, error) + Decrypt(ciphertext []byte) ([]byte, error) } -func Encrypt(plaintext []byte, key []byte) ([]byte, error) { - c, err := aes.NewCipher(key) +func NewSymmetricEncryption(key, nonce string) (SymmetricEncryption, error) { + c, err := aes.NewCipher([]byte(key)) if err != nil { return nil, err } @@ -24,30 +26,45 @@ func Encrypt(plaintext []byte, key []byte) ([]byte, error) { return nil, err } - nonce := make([]byte, gcm.NonceSize()) - if _, err = io.ReadFull(rand.Reader, nonce); err != nil { - return nil, err + se := symmetricEncryption{aead: gcm} + + if nonce != "" { + nonce1, err := hex.DecodeString(nonce) + if err != nil { + return nil, err + } + if len(nonce1) != gcm.NonceSize() { + return nil, fmt.Errorf("the length of nonce for symmetric encryption is unmatched") + } + se.nonce = nonce1 } - return gcm.Seal(nonce, nonce, plaintext, nil), nil + return se, nil } -func Decrypt(ciphertext []byte, key []byte) ([]byte, error) { - c, err := aes.NewCipher(key) - if err != nil { - return nil, err - } +type symmetricEncryption struct { + aead cipher.AEAD + nonce []byte +} - gcm, err := cipher.NewGCM(c) - if err != nil { - return nil, err +func (se symmetricEncryption) Encrypt(plaintext []byte) ([]byte, error) { + nonce := se.nonce + if nonce == nil { + nonce = make([]byte, se.aead.NonceSize()) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return nil, err + } } - nonceSize := gcm.NonceSize() + return se.aead.Seal(nonce, nonce, plaintext, nil), nil +} + +func (se symmetricEncryption) Decrypt(ciphertext []byte) ([]byte, error) { + nonceSize := se.aead.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) + return se.aead.Open(nil, nonce, ciphertext, nil) } diff --git a/worker/worker.go b/worker/worker.go index 923a50a..bf8c53b 100644 --- a/worker/worker.go +++ b/worker/worker.go @@ -19,7 +19,7 @@ var worker IEmailWorker type IEmailWorker interface { GenCLAPDFForCorporationAndSendIt(string, string, string, models.OrgInfo, models.CorporationSigning, []models.CLAField) - SendSimpleMessage(orgEmail string, msg *email.EmailMessage) + SendSimpleMessage(string, *email.EmailMessage) } func GetEmailWorker() IEmailWorker { @@ -49,7 +49,7 @@ func (this *emailWorker) GenCLAPDFForCorporationAndSendIt(linkID, orgSignatureFi this.wg.Done() }() - emailCfg, ec, err := getEmailClient(orgInfo.OrgEmail) + emailCfg, ec, err := getEmailClient(linkID) if err != nil { return } @@ -112,13 +112,13 @@ func (this *emailWorker) GenCLAPDFForCorporationAndSendIt(linkID, orgSignatureFi go f() } -func (this *emailWorker) SendSimpleMessage(orgEmail string, msg *email.EmailMessage) { +func (this *emailWorker) SendSimpleMessage(linkID string, msg *email.EmailMessage) { f := func() { defer func() { this.wg.Done() }() - emailCfg, ec, err := getEmailClient(orgEmail) + emailCfg, ec, err := getEmailClient(linkID) if err != nil { return } @@ -148,8 +148,8 @@ func next(err error) { } -func getEmailClient(orgEmail string) (*models.OrgEmail, email.IEmail, error) { - emailCfg, merr := models.GetOrgEmailInfo(orgEmail) +func getEmailClient(linkID string) (*models.OrgEmail, email.IEmail, error) { + emailCfg, merr := models.GetOrgEmailOfLink(linkID) if merr != nil { beego.Info(merr.Error()) return nil, nil, merr