From 64312ab09bbdbb5a440d1a251b73a032706beb88 Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Wed, 31 May 2017 10:22:10 +0200 Subject: [PATCH 01/33] Add LDAP Key Synchronization feature Signed-off-by: Magnus Lindvall --- models/migrations/migrations.go | 2 + models/migrations/v46.go | 23 ++++ models/ssh_key.go | 30 ++-- models/user.go | 189 +++++++++++++++++++++++--- modules/auth/auth_form.go | 1 + modules/auth/ldap/ldap.go | 61 +++++---- options/locale/locale_en-US.ini | 1 + package.json | 2 +- routers/admin/auths.go | 35 ++--- routers/api/v1/user/key.go | 2 +- routers/user/setting.go | 2 +- templates/admin/auth/edit.tmpl | 4 + templates/admin/auth/source/ldap.tmpl | 4 + 13 files changed, 277 insertions(+), 79 deletions(-) create mode 100644 models/migrations/v46.go diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 014aa2bce1a0c..084a14266acc6 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -140,6 +140,8 @@ var migrations = []Migration{ NewMigration("remove duplicate unit types", removeDuplicateUnitTypes), // v45 -> v46 NewMigration("remove index column from repo_unit table", removeIndexColumnFromRepoUnitTable), + // v46 -> v47 + NewMigration("add field for ldap public public ssh key synchronization", addLoginSourceLdapPublicSSHKeySyncEnabled), } // Migrate database to current version diff --git a/models/migrations/v46.go b/models/migrations/v46.go new file mode 100644 index 0000000000000..555a2a6025e36 --- /dev/null +++ b/models/migrations/v46.go @@ -0,0 +1,23 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "fmt" + + "github.com/go-xorm/xorm" +) + +func addLoginSourceLdapPublicSSHKeySyncEnabled(x *xorm.Engine) error { + // LoginSource see models/login_source.go + type LoginSource struct { + IsLdapPublicSSHKeySyncEnabled bool `xorm:"INDEX NOT NULL DEFAULT false"` + } + + if err := x.Sync2(new(LoginSource)); err != nil { + return fmt.Errorf("Sync2: %v", err) + } + return nil +} diff --git a/models/ssh_key.go b/models/ssh_key.go index 1cca47f565826..b0fa67820f229 100644 --- a/models/ssh_key.go +++ b/models/ssh_key.go @@ -46,13 +46,14 @@ const ( // PublicKey represents a user or deploy SSH public key. type PublicKey struct { - ID int64 `xorm:"pk autoincr"` - OwnerID int64 `xorm:"INDEX NOT NULL"` - Name string `xorm:"NOT NULL"` - Fingerprint string `xorm:"NOT NULL"` - Content string `xorm:"TEXT NOT NULL"` - Mode AccessMode `xorm:"NOT NULL DEFAULT 2"` - Type KeyType `xorm:"NOT NULL DEFAULT 1"` + ID int64 `xorm:"pk autoincr"` + OwnerID int64 `xorm:"INDEX NOT NULL"` + Name string `xorm:"NOT NULL"` + Fingerprint string `xorm:"NOT NULL"` + Content string `xorm:"TEXT NOT NULL"` + Mode AccessMode `xorm:"NOT NULL DEFAULT 2"` + Type KeyType `xorm:"NOT NULL DEFAULT 1"` + LoginSourceID int64 `xorm:"NULL DEFAULT NULL"` Created time.Time `xorm:"-"` CreatedUnix int64 `xorm:"created"` @@ -393,7 +394,7 @@ func addKey(e Engine, key *PublicKey) (err error) { } // AddPublicKey adds new public key to database and authorized_keys file. -func AddPublicKey(ownerID int64, name, content string) (*PublicKey, error) { +func AddPublicKey(ownerID int64, name, content string, LoginSourceID int64) (*PublicKey, error) { log.Trace(content) fingerprint, err := calcFingerprint(content) @@ -422,12 +423,13 @@ func AddPublicKey(ownerID int64, name, content string) (*PublicKey, error) { } key := &PublicKey{ - OwnerID: ownerID, - Name: name, - Fingerprint: fingerprint, - Content: content, - Mode: AccessModeWrite, - Type: KeyTypeUser, + OwnerID: ownerID, + Name: name, + Fingerprint: fingerprint, + Content: content, + Mode: AccessModeWrite, + Type: KeyTypeUser, + LoginSourceID: LoginSourceID, } if err = addKey(sess, key); err != nil { return nil, fmt.Errorf("addKey: %v", err) diff --git a/models/user.go b/models/user.go index f0cdc74d05f28..9a7f26acbc9c4 100644 --- a/models/user.go +++ b/models/user.go @@ -19,6 +19,7 @@ import ( "image/png" "os" "path/filepath" + "sort" "strings" "time" "unicode/utf8" @@ -1355,6 +1356,138 @@ func GetWatchedRepos(userID int64, private bool) ([]*Repository, error) { return repos, nil } +// existsInSlice returns true if string exists in slice +func existsInSlice(target string, slice []string) bool { + i := sort.Search(len(slice), + func(i int) bool { return slice[i] == target }) + return i < len(slice) +} + +// IsEqualSlice returns true if slices are equal +func IsEqualSlice(target []string, source []string) bool { + if len(target) != len(source) { + return false + } + + if (target == nil) != (source == nil) { + return false + } + + sort.Strings(target) + sort.Strings(source) + + for i, v := range target { + if v != source[i] { + return false + } + } + + return true +} + +// deleteKeysMarkedForDeletion returns true if ssh keys needs update +func deleteKeysMarkedForDeletion(keys []string) (sshKeysNeedUpdate bool, err error) { + // Start session + sess := x.NewSession() + defer sess.Close() + if err = sess.Begin(); err != nil { + return false, err + } + + // Delete keys marked for deletion + for _, KeyToDelete := range keys { + key, err := SearchPublicKeyByContent(KeyToDelete) + err = deletePublicKeys(sess, key.ID) + if err != nil { + sshKeysNeedUpdate = false + log.Trace("deleteKeysMarkedForDeletion: Error: %v", key) + } + sshKeysNeedUpdate = true + } + + return sshKeysNeedUpdate, sess.Commit() +} + +func addLdapSSHPublicKeys(s *LoginSource, usr *User, SSHPublicKeys []string) (sshKeysNeedUpdate bool, err error) { + for _, LDAPPublicSSHKey := range SSHPublicKeys { + LDAPPublicSSHKeyName := strings.Join([]string{s.Name, LDAPPublicSSHKey[0:40]}, "-") + _, err := AddPublicKey(usr.ID, LDAPPublicSSHKeyName, LDAPPublicSSHKey, s.ID) + if err != nil { + log.Error(4, "addLdapSSHPublicKeys[%s]: Error adding LDAP Public SSH Key for user %s: %v", s.Name, usr.Name, err) + } else { + log.Trace("addLdapSSHPublicKeys[%s]: Added LDAP Public SSH Key for user %s: %v", s.Name, usr.Name, LDAPPublicSSHKey) + sshKeysNeedUpdate = true + } + + } + return sshKeysNeedUpdate, err +} + +func synchronizeLdapSSHPublicKeys(s *LoginSource, SSHPublicKeys []string, usr *User) (sshKeysNeedUpdate bool, err error) { + log.Trace("synchronizeLdapSSHPublicKeys[%s]: Handling LDAP Public SSH Key synchronization for user %s", s.Name, usr.Name) + + // Get Public Keys from DB with LDAP source + var giteaKeys []string + keys, err := ListPublicKeys(usr.ID) + if err != nil { + log.Error(4, "synchronizeLdapSSHPublicKeys[%s]: Error listing LDAP Public SSH Keys for user %s: %v", s.Name, usr.Name, err) + } + + // Get Public Keys from DB, which has been synchronized from LDAP + for _, v := range keys { + // If key was synced from LDAP, add it to list + if v.LoginSourceID > 0 { + giteaKeys = append(giteaKeys, v.OmitEmail()) + } + } + sort.Strings(giteaKeys) + + // Get Public Keys from LDAP and skip duplicate keys + var ldapKeys []string + for _, v := range SSHPublicKeys { + ldapKey := strings.Join(strings.Split(v, " ")[:2], " ") + if !existsInSlice(ldapKey, ldapKeys) { + ldapKeys = append(ldapKeys, ldapKey) + } + } + sort.Strings(ldapKeys) + + // Check if Public Key sync is needed + var giteaKeysToDelete []string + if IsEqualSlice(giteaKeys, ldapKeys) { + log.Trace("synchronizeLdapSSHPublicKeys[%s]: LDAP Public Keys are already in sync for %s (LDAP:%v/DB:%v)", s.Name, usr.Name, len(ldapKeys), len(giteaKeys)) + } else { + log.Trace("synchronizeLdapSSHPublicKeys[%s]: LDAP Public Key needs update for user %s (LDAP:%v/DB:%v)", s.Name, usr.Name, len(ldapKeys), len(giteaKeys)) + // Delete giteaKeys that doesn't exist in ldapKeys and has been synced from LDAP earlier + for _, giteaKey := range giteaKeys { + if !existsInSlice(giteaKey, ldapKeys) { + log.Trace("synchronizeLdapSSHPublicKeys[%s]: Marking LDAP Public SSH Key for deletion for user %s: %v", s.Name, usr.Name, giteaKey) + giteaKeysToDelete = append(giteaKeysToDelete, giteaKey) + } + } + } + + // Delete LDAP keys from DB that doesn't exist in LDAP + sshKeysNeedUpdate, err = deleteKeysMarkedForDeletion(giteaKeysToDelete) + if err != nil { + log.Error(4, "synchronizeLdapSSHPublicKeys[%s]: Error deleting LDAP Public SSH Keys marked for deletion for user %s: %v", s.Name, usr.Name, err) + } + + // Add LDAP Public SSH Keys that doesn't already exist in DB + var newLdapSSHKeys []string + for _, LDAPPublicSSHKey := range ldapKeys { + if !existsInSlice(LDAPPublicSSHKey, giteaKeys) { + newLdapSSHKeys = append(newLdapSSHKeys, LDAPPublicSSHKey) + } + } + sshKeysNeedUpdate, err = addLdapSSHPublicKeys(s, usr, newLdapSSHKeys) + if err != nil { + log.Error(4, "synchronizeLdapSSHPublicKeys[%s]: Error updating LDAP Public SSH Keys for user %s: %v", s.Name, usr.Name, err) + } + + return sshKeysNeedUpdate, err +} + // SyncExternalUsers is used to synchronize users with external authorization source func SyncExternalUsers() { if !taskStatusTable.StartIfNotRunning(syncExternalUsers) { @@ -1376,10 +1509,13 @@ func SyncExternalUsers() { if !s.IsActived || !s.IsSyncEnabled { continue } + if s.IsLDAP() { log.Trace("Doing: SyncExternalUsers[%s]", s.Name) var existingUsers []int64 + var isAttributeSSHPublicKeySet = len(strings.TrimSpace(s.LDAP().AttributeSSHPublicKey)) > 0 + var sshKeysNeedUpdate bool // Find all users with this login type var users []User @@ -1388,7 +1524,6 @@ func SyncExternalUsers() { Find(&users) sr := s.LDAP().SearchEntries() - for _, su := range sr { if len(su.Username) == 0 { continue @@ -1425,35 +1560,57 @@ func SyncExternalUsers() { } err = CreateUser(usr) + if err != nil { log.Error(4, "SyncExternalUsers[%s]: Error creating user %s: %v", s.Name, su.Username, err) + } else if isAttributeSSHPublicKeySet { + log.Trace("SyncExternalUsers[%s]: Adding LDAP Public SSH Keys for user %s", s.Name, usr.Name) + sshKeysNeedUpdate, err = addLdapSSHPublicKeys(s, usr, su.SSHPublicKey) + if err != nil { + log.Error(4, "SyncExternalUsers[%s]: Error adding LDAP Public SSH Keys for user %s: %v", s.Name, su.Username, err) + } } } else if updateExisting { existingUsers = append(existingUsers, usr.ID) - // Check if user data has changed - if (len(s.LDAP().AdminFilter) > 0 && usr.IsAdmin != su.IsAdmin) || - strings.ToLower(usr.Email) != strings.ToLower(su.Mail) || - usr.FullName != fullName || - !usr.IsActive { - - log.Trace("SyncExternalUsers[%s]: Updating user %s", s.Name, usr.Name) - - usr.FullName = fullName - usr.Email = su.Mail - // Change existing admin flag only if AdminFilter option is set - if len(s.LDAP().AdminFilter) > 0 { - usr.IsAdmin = su.IsAdmin - } - usr.IsActive = true err = UpdateUserCols(usr, "full_name", "email", "is_admin", "is_active") if err != nil { log.Error(4, "SyncExternalUsers[%s]: Error updating user %s: %v", s.Name, usr.Name, err) } + + if isAttributeSSHPublicKeySet { + synchronizeLdapSSHPublicKeys(s, su.SSHPublicKey, usr) + } + } + + // Check if user data has changed + if (len(s.LDAP().AdminFilter) > 0 && usr.IsAdmin != su.IsAdmin) || + strings.ToLower(usr.Email) != strings.ToLower(su.Mail) || + usr.FullName != fullName || + !usr.IsActive { + + log.Trace("SyncExternalUsers[%s]: Updating user %s", s.Name, usr.Name) + + usr.FullName = fullName + usr.Email = su.Mail + // Change existing admin flag only if AdminFilter option is set + if len(s.LDAP().AdminFilter) > 0 { + usr.IsAdmin = su.IsAdmin + } + usr.IsActive = true + + err = UpdateUser(usr) + if err != nil { + log.Error(4, "SyncExternalUsers[%s]: Error updating user %s: %v", s.Name, usr.Name, err) } } } + // Rewrite authorized_keys file if LDAP Public SSH Key attribute is set and any key was added or removed + if isAttributeSSHPublicKeySet && sshKeysNeedUpdate { + RewriteAllPublicKeys() + } + // Deactivate users not present in LDAP if updateExisting { for _, usr := range users { diff --git a/modules/auth/auth_form.go b/modules/auth/auth_form.go index 7c452bbc35388..a44bf06511a2f 100644 --- a/modules/auth/auth_form.go +++ b/modules/auth/auth_form.go @@ -24,6 +24,7 @@ type AuthenticationForm struct { AttributeName string AttributeSurname string AttributeMail string + AttributeSSHPublicKey string AttributesInBind bool Filter string AdminFilter string diff --git a/modules/auth/ldap/ldap.go b/modules/auth/ldap/ldap.go index 7754cc8182646..b72a164812c8f 100644 --- a/modules/auth/ldap/ldap.go +++ b/modules/auth/ldap/ldap.go @@ -28,32 +28,34 @@ const ( // Source Basic LDAP authentication service type Source struct { - Name string // canonical name (ie. corporate.ad) - Host string // LDAP host - Port int // port number - SecurityProtocol SecurityProtocol - SkipVerify bool - BindDN string // DN to bind with - BindPassword string // Bind DN password - UserBase string // Base search path for users - UserDN string // Template for the DN of the user for simple auth - AttributeUsername string // Username attribute - AttributeName string // First name attribute - AttributeSurname string // Surname attribute - AttributeMail string // E-mail attribute - AttributesInBind bool // fetch attributes in bind context (not user) - Filter string // Query filter to validate entry - AdminFilter string // Query filter to check if user is admin - Enabled bool // if this source is disabled + Name string // canonical name (ie. corporate.ad) + Host string // LDAP host + Port int // port number + SecurityProtocol SecurityProtocol + SkipVerify bool + BindDN string // DN to bind with + BindPassword string // Bind DN password + UserBase string // Base search path for users + UserDN string // Template for the DN of the user for simple auth + AttributeUsername string // Username attribute + AttributeName string // First name attribute + AttributeSurname string // Surname attribute + AttributeMail string // E-mail attribute + AttributeSSHPublicKey string // LDAP SSH Public Key attribute + AttributesInBind bool // fetch attributes in bind context (not user) + Filter string // Query filter to validate entry + AdminFilter string // Query filter to check if user is admin + Enabled bool // if this source is disabled } // SearchResult : user data type SearchResult struct { - Username string // Username - Name string // Name - Surname string // Surname - Mail string // E-mail address - IsAdmin bool // if user is administrator + Username string // Username + Name string // Name + Surname string // Surname + Mail string // E-mail address + SSHPublicKey []string // SSH Public Key + IsAdmin bool // if user is administrator } func (ls *Source) sanitizedUserQuery(username string) (string, bool) { @@ -292,10 +294,10 @@ func (ls *Source) SearchEntries() []*SearchResult { userFilter := fmt.Sprintf(ls.Filter, "*") - log.Trace("Fetching attributes '%v', '%v', '%v', '%v' with filter %s and base %s", ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail, userFilter, ls.UserBase) + log.Trace("Fetching attributes '%v', '%v', '%v', '%v', '%v' with filter %s and base %s", ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail, ls.AttributeSSHPublicKey, userFilter, ls.UserBase) search := ldap.NewSearchRequest( ls.UserBase, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, userFilter, - []string{ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail}, + []string{ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail, ls.AttributeSSHPublicKey}, nil) sr, err := l.Search(search) @@ -308,11 +310,12 @@ func (ls *Source) SearchEntries() []*SearchResult { for i, v := range sr.Entries { result[i] = &SearchResult{ - Username: v.GetAttributeValue(ls.AttributeUsername), - Name: v.GetAttributeValue(ls.AttributeName), - Surname: v.GetAttributeValue(ls.AttributeSurname), - Mail: v.GetAttributeValue(ls.AttributeMail), - IsAdmin: checkAdmin(l, ls, v.DN), + Username: v.GetAttributeValue(ls.AttributeUsername), + Name: v.GetAttributeValue(ls.AttributeName), + Surname: v.GetAttributeValue(ls.AttributeSurname), + Mail: v.GetAttributeValue(ls.AttributeMail), + SSHPublicKey: v.GetAttributeValues(ls.AttributeSSHPublicKey), + IsAdmin: checkAdmin(l, ls, v.DN), } } diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index f39ff0fa60265..dd0fac794a1a6 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1240,6 +1240,7 @@ auths.attribute_username_placeholder = Leave empty to use sign-in form field val auths.attribute_name = First name attribute auths.attribute_surname = Surname attribute auths.attribute_mail = Email attribute +auths.attribute_ssh_public_key = Public SSH key attribute auths.attributes_in_bind = Fetch attributes in Bind DN context auths.filter = User Filter auths.admin_filter = Admin Filter diff --git a/package.json b/package.json index 8251e5026446c..3410bb1f3589d 100644 --- a/package.json +++ b/package.json @@ -4,4 +4,4 @@ "less": "^2.7.2", "less-plugin-clean-css": "^1.5.1" } -} \ No newline at end of file +} diff --git a/routers/admin/auths.go b/routers/admin/auths.go index 590e45a4f41e9..d287aa7c8f1ac 100644 --- a/routers/admin/auths.go +++ b/routers/admin/auths.go @@ -93,23 +93,24 @@ func NewAuthSource(ctx *context.Context) { func parseLDAPConfig(form auth.AuthenticationForm) *models.LDAPConfig { return &models.LDAPConfig{ Source: &ldap.Source{ - Name: form.Name, - Host: form.Host, - Port: form.Port, - SecurityProtocol: ldap.SecurityProtocol(form.SecurityProtocol), - SkipVerify: form.SkipVerify, - BindDN: form.BindDN, - UserDN: form.UserDN, - BindPassword: form.BindPassword, - UserBase: form.UserBase, - AttributeUsername: form.AttributeUsername, - AttributeName: form.AttributeName, - AttributeSurname: form.AttributeSurname, - AttributeMail: form.AttributeMail, - AttributesInBind: form.AttributesInBind, - Filter: form.Filter, - AdminFilter: form.AdminFilter, - Enabled: true, + Name: form.Name, + Host: form.Host, + Port: form.Port, + SecurityProtocol: ldap.SecurityProtocol(form.SecurityProtocol), + SkipVerify: form.SkipVerify, + BindDN: form.BindDN, + UserDN: form.UserDN, + BindPassword: form.BindPassword, + UserBase: form.UserBase, + AttributeUsername: form.AttributeUsername, + AttributeName: form.AttributeName, + AttributeSurname: form.AttributeSurname, + AttributeMail: form.AttributeMail, + AttributeSSHPublicKey: form.AttributeSSHPublicKey, + AttributesInBind: form.AttributesInBind, + Filter: form.Filter, + AdminFilter: form.AdminFilter, + Enabled: true, }, } } diff --git a/routers/api/v1/user/key.go b/routers/api/v1/user/key.go index 1772ef4d25285..1a6fb08b98145 100644 --- a/routers/api/v1/user/key.go +++ b/routers/api/v1/user/key.go @@ -119,7 +119,7 @@ func CreateUserPublicKey(ctx *context.APIContext, form api.CreateKeyOption, uid return } - key, err := models.AddPublicKey(uid, form.Title, content) + key, err := models.AddPublicKey(uid, form.Title, content, 0) if err != nil { repo.HandleAddKeyError(ctx, err) return diff --git a/routers/user/setting.go b/routers/user/setting.go index b71b29ba21cb3..ce5638e3bfa6f 100644 --- a/routers/user/setting.go +++ b/routers/user/setting.go @@ -401,7 +401,7 @@ func SettingsKeysPost(ctx *context.Context, form auth.AddKeyForm) { } } - if _, err = models.AddPublicKey(ctx.User.ID, form.Title, content); err != nil { + if _, err = models.AddPublicKey(ctx.User.ID, form.Title, content, 0); err != nil { ctx.Data["HasSSHError"] = true switch { case models.IsErrKeyAlreadyExist(err): diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl index e3048b21836b0..e88287e547405 100644 --- a/templates/admin/auth/edit.tmpl +++ b/templates/admin/auth/edit.tmpl @@ -90,6 +90,10 @@ +
+ + +
{{if .Source.IsLDAP}}
diff --git a/templates/admin/auth/source/ldap.tmpl b/templates/admin/auth/source/ldap.tmpl index 2131950212214..241cec49ff16b 100644 --- a/templates/admin/auth/source/ldap.tmpl +++ b/templates/admin/auth/source/ldap.tmpl @@ -62,4 +62,8 @@
+
+ + +
From d17981b75e2190d05033aa4816648c330c6d1d3a Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Sun, 25 Jun 2017 23:55:30 +0200 Subject: [PATCH 02/33] Move block: user data has changed Signed-off-by: Magnus Lindvall --- models/user.go | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/models/user.go b/models/user.go index 9a7f26acbc9c4..c5a381ce88a98 100644 --- a/models/user.go +++ b/models/user.go @@ -1581,27 +1581,27 @@ func SyncExternalUsers() { if isAttributeSSHPublicKeySet { synchronizeLdapSSHPublicKeys(s, su.SSHPublicKey, usr) } - } - // Check if user data has changed - if (len(s.LDAP().AdminFilter) > 0 && usr.IsAdmin != su.IsAdmin) || - strings.ToLower(usr.Email) != strings.ToLower(su.Mail) || - usr.FullName != fullName || - !usr.IsActive { + // Check if user data has changed + if (len(s.LDAP().AdminFilter) > 0 && usr.IsAdmin != su.IsAdmin) || + strings.ToLower(usr.Email) != strings.ToLower(su.Mail) || + usr.FullName != fullName || + !usr.IsActive { - log.Trace("SyncExternalUsers[%s]: Updating user %s", s.Name, usr.Name) + log.Trace("SyncExternalUsers[%s]: Updating user %s", s.Name, usr.Name) - usr.FullName = fullName - usr.Email = su.Mail - // Change existing admin flag only if AdminFilter option is set - if len(s.LDAP().AdminFilter) > 0 { - usr.IsAdmin = su.IsAdmin - } - usr.IsActive = true + usr.FullName = fullName + usr.Email = su.Mail + // Change existing admin flag only if AdminFilter option is set + if len(s.LDAP().AdminFilter) > 0 { + usr.IsAdmin = su.IsAdmin + } + usr.IsActive = true - err = UpdateUser(usr) - if err != nil { - log.Error(4, "SyncExternalUsers[%s]: Error updating user %s: %v", s.Name, usr.Name, err) + err = UpdateUser(usr) + if err != nil { + log.Error(4, "SyncExternalUsers[%s]: Error updating user %s: %v", s.Name, usr.Name, err) + } } } } From cc046c7a4452eb88ff452f8dd581dc2c56e802f1 Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Wed, 31 May 2017 10:22:10 +0200 Subject: [PATCH 03/33] Add LDAP Key Synchronization feature Signed-off-by: Magnus Lindvall --- models/migrations/migrations.go | 2 + models/migrations/v47.go | 23 ++++ models/ssh_key.go | 30 ++-- models/user.go | 189 +++++++++++++++++++++++--- modules/auth/auth_form.go | 1 + modules/auth/ldap/ldap.go | 61 +++++---- options/locale/locale_en-US.ini | 1 + package.json | 2 +- routers/admin/auths.go | 35 ++--- routers/api/v1/user/key.go | 2 +- routers/user/setting.go | 2 +- templates/admin/auth/edit.tmpl | 4 + templates/admin/auth/source/ldap.tmpl | 4 + 13 files changed, 277 insertions(+), 79 deletions(-) create mode 100644 models/migrations/v47.go diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 33678c0d29d6d..7fc3f9bd78553 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -142,6 +142,8 @@ var migrations = []Migration{ NewMigration("remove index column from repo_unit table", removeIndexColumnFromRepoUnitTable), // v46 -> v47 NewMigration("remove organization watch repositories", removeOrganizationWatchRepo), + // v47 -> v48 + NewMigration("add field for ldap public public ssh key synchronization", addLoginSourceLdapPublicSSHKeySyncEnabled), } // Migrate database to current version diff --git a/models/migrations/v47.go b/models/migrations/v47.go new file mode 100644 index 0000000000000..555a2a6025e36 --- /dev/null +++ b/models/migrations/v47.go @@ -0,0 +1,23 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "fmt" + + "github.com/go-xorm/xorm" +) + +func addLoginSourceLdapPublicSSHKeySyncEnabled(x *xorm.Engine) error { + // LoginSource see models/login_source.go + type LoginSource struct { + IsLdapPublicSSHKeySyncEnabled bool `xorm:"INDEX NOT NULL DEFAULT false"` + } + + if err := x.Sync2(new(LoginSource)); err != nil { + return fmt.Errorf("Sync2: %v", err) + } + return nil +} diff --git a/models/ssh_key.go b/models/ssh_key.go index 1cca47f565826..b0fa67820f229 100644 --- a/models/ssh_key.go +++ b/models/ssh_key.go @@ -46,13 +46,14 @@ const ( // PublicKey represents a user or deploy SSH public key. type PublicKey struct { - ID int64 `xorm:"pk autoincr"` - OwnerID int64 `xorm:"INDEX NOT NULL"` - Name string `xorm:"NOT NULL"` - Fingerprint string `xorm:"NOT NULL"` - Content string `xorm:"TEXT NOT NULL"` - Mode AccessMode `xorm:"NOT NULL DEFAULT 2"` - Type KeyType `xorm:"NOT NULL DEFAULT 1"` + ID int64 `xorm:"pk autoincr"` + OwnerID int64 `xorm:"INDEX NOT NULL"` + Name string `xorm:"NOT NULL"` + Fingerprint string `xorm:"NOT NULL"` + Content string `xorm:"TEXT NOT NULL"` + Mode AccessMode `xorm:"NOT NULL DEFAULT 2"` + Type KeyType `xorm:"NOT NULL DEFAULT 1"` + LoginSourceID int64 `xorm:"NULL DEFAULT NULL"` Created time.Time `xorm:"-"` CreatedUnix int64 `xorm:"created"` @@ -393,7 +394,7 @@ func addKey(e Engine, key *PublicKey) (err error) { } // AddPublicKey adds new public key to database and authorized_keys file. -func AddPublicKey(ownerID int64, name, content string) (*PublicKey, error) { +func AddPublicKey(ownerID int64, name, content string, LoginSourceID int64) (*PublicKey, error) { log.Trace(content) fingerprint, err := calcFingerprint(content) @@ -422,12 +423,13 @@ func AddPublicKey(ownerID int64, name, content string) (*PublicKey, error) { } key := &PublicKey{ - OwnerID: ownerID, - Name: name, - Fingerprint: fingerprint, - Content: content, - Mode: AccessModeWrite, - Type: KeyTypeUser, + OwnerID: ownerID, + Name: name, + Fingerprint: fingerprint, + Content: content, + Mode: AccessModeWrite, + Type: KeyTypeUser, + LoginSourceID: LoginSourceID, } if err = addKey(sess, key); err != nil { return nil, fmt.Errorf("addKey: %v", err) diff --git a/models/user.go b/models/user.go index f0cdc74d05f28..9a7f26acbc9c4 100644 --- a/models/user.go +++ b/models/user.go @@ -19,6 +19,7 @@ import ( "image/png" "os" "path/filepath" + "sort" "strings" "time" "unicode/utf8" @@ -1355,6 +1356,138 @@ func GetWatchedRepos(userID int64, private bool) ([]*Repository, error) { return repos, nil } +// existsInSlice returns true if string exists in slice +func existsInSlice(target string, slice []string) bool { + i := sort.Search(len(slice), + func(i int) bool { return slice[i] == target }) + return i < len(slice) +} + +// IsEqualSlice returns true if slices are equal +func IsEqualSlice(target []string, source []string) bool { + if len(target) != len(source) { + return false + } + + if (target == nil) != (source == nil) { + return false + } + + sort.Strings(target) + sort.Strings(source) + + for i, v := range target { + if v != source[i] { + return false + } + } + + return true +} + +// deleteKeysMarkedForDeletion returns true if ssh keys needs update +func deleteKeysMarkedForDeletion(keys []string) (sshKeysNeedUpdate bool, err error) { + // Start session + sess := x.NewSession() + defer sess.Close() + if err = sess.Begin(); err != nil { + return false, err + } + + // Delete keys marked for deletion + for _, KeyToDelete := range keys { + key, err := SearchPublicKeyByContent(KeyToDelete) + err = deletePublicKeys(sess, key.ID) + if err != nil { + sshKeysNeedUpdate = false + log.Trace("deleteKeysMarkedForDeletion: Error: %v", key) + } + sshKeysNeedUpdate = true + } + + return sshKeysNeedUpdate, sess.Commit() +} + +func addLdapSSHPublicKeys(s *LoginSource, usr *User, SSHPublicKeys []string) (sshKeysNeedUpdate bool, err error) { + for _, LDAPPublicSSHKey := range SSHPublicKeys { + LDAPPublicSSHKeyName := strings.Join([]string{s.Name, LDAPPublicSSHKey[0:40]}, "-") + _, err := AddPublicKey(usr.ID, LDAPPublicSSHKeyName, LDAPPublicSSHKey, s.ID) + if err != nil { + log.Error(4, "addLdapSSHPublicKeys[%s]: Error adding LDAP Public SSH Key for user %s: %v", s.Name, usr.Name, err) + } else { + log.Trace("addLdapSSHPublicKeys[%s]: Added LDAP Public SSH Key for user %s: %v", s.Name, usr.Name, LDAPPublicSSHKey) + sshKeysNeedUpdate = true + } + + } + return sshKeysNeedUpdate, err +} + +func synchronizeLdapSSHPublicKeys(s *LoginSource, SSHPublicKeys []string, usr *User) (sshKeysNeedUpdate bool, err error) { + log.Trace("synchronizeLdapSSHPublicKeys[%s]: Handling LDAP Public SSH Key synchronization for user %s", s.Name, usr.Name) + + // Get Public Keys from DB with LDAP source + var giteaKeys []string + keys, err := ListPublicKeys(usr.ID) + if err != nil { + log.Error(4, "synchronizeLdapSSHPublicKeys[%s]: Error listing LDAP Public SSH Keys for user %s: %v", s.Name, usr.Name, err) + } + + // Get Public Keys from DB, which has been synchronized from LDAP + for _, v := range keys { + // If key was synced from LDAP, add it to list + if v.LoginSourceID > 0 { + giteaKeys = append(giteaKeys, v.OmitEmail()) + } + } + sort.Strings(giteaKeys) + + // Get Public Keys from LDAP and skip duplicate keys + var ldapKeys []string + for _, v := range SSHPublicKeys { + ldapKey := strings.Join(strings.Split(v, " ")[:2], " ") + if !existsInSlice(ldapKey, ldapKeys) { + ldapKeys = append(ldapKeys, ldapKey) + } + } + sort.Strings(ldapKeys) + + // Check if Public Key sync is needed + var giteaKeysToDelete []string + if IsEqualSlice(giteaKeys, ldapKeys) { + log.Trace("synchronizeLdapSSHPublicKeys[%s]: LDAP Public Keys are already in sync for %s (LDAP:%v/DB:%v)", s.Name, usr.Name, len(ldapKeys), len(giteaKeys)) + } else { + log.Trace("synchronizeLdapSSHPublicKeys[%s]: LDAP Public Key needs update for user %s (LDAP:%v/DB:%v)", s.Name, usr.Name, len(ldapKeys), len(giteaKeys)) + // Delete giteaKeys that doesn't exist in ldapKeys and has been synced from LDAP earlier + for _, giteaKey := range giteaKeys { + if !existsInSlice(giteaKey, ldapKeys) { + log.Trace("synchronizeLdapSSHPublicKeys[%s]: Marking LDAP Public SSH Key for deletion for user %s: %v", s.Name, usr.Name, giteaKey) + giteaKeysToDelete = append(giteaKeysToDelete, giteaKey) + } + } + } + + // Delete LDAP keys from DB that doesn't exist in LDAP + sshKeysNeedUpdate, err = deleteKeysMarkedForDeletion(giteaKeysToDelete) + if err != nil { + log.Error(4, "synchronizeLdapSSHPublicKeys[%s]: Error deleting LDAP Public SSH Keys marked for deletion for user %s: %v", s.Name, usr.Name, err) + } + + // Add LDAP Public SSH Keys that doesn't already exist in DB + var newLdapSSHKeys []string + for _, LDAPPublicSSHKey := range ldapKeys { + if !existsInSlice(LDAPPublicSSHKey, giteaKeys) { + newLdapSSHKeys = append(newLdapSSHKeys, LDAPPublicSSHKey) + } + } + sshKeysNeedUpdate, err = addLdapSSHPublicKeys(s, usr, newLdapSSHKeys) + if err != nil { + log.Error(4, "synchronizeLdapSSHPublicKeys[%s]: Error updating LDAP Public SSH Keys for user %s: %v", s.Name, usr.Name, err) + } + + return sshKeysNeedUpdate, err +} + // SyncExternalUsers is used to synchronize users with external authorization source func SyncExternalUsers() { if !taskStatusTable.StartIfNotRunning(syncExternalUsers) { @@ -1376,10 +1509,13 @@ func SyncExternalUsers() { if !s.IsActived || !s.IsSyncEnabled { continue } + if s.IsLDAP() { log.Trace("Doing: SyncExternalUsers[%s]", s.Name) var existingUsers []int64 + var isAttributeSSHPublicKeySet = len(strings.TrimSpace(s.LDAP().AttributeSSHPublicKey)) > 0 + var sshKeysNeedUpdate bool // Find all users with this login type var users []User @@ -1388,7 +1524,6 @@ func SyncExternalUsers() { Find(&users) sr := s.LDAP().SearchEntries() - for _, su := range sr { if len(su.Username) == 0 { continue @@ -1425,35 +1560,57 @@ func SyncExternalUsers() { } err = CreateUser(usr) + if err != nil { log.Error(4, "SyncExternalUsers[%s]: Error creating user %s: %v", s.Name, su.Username, err) + } else if isAttributeSSHPublicKeySet { + log.Trace("SyncExternalUsers[%s]: Adding LDAP Public SSH Keys for user %s", s.Name, usr.Name) + sshKeysNeedUpdate, err = addLdapSSHPublicKeys(s, usr, su.SSHPublicKey) + if err != nil { + log.Error(4, "SyncExternalUsers[%s]: Error adding LDAP Public SSH Keys for user %s: %v", s.Name, su.Username, err) + } } } else if updateExisting { existingUsers = append(existingUsers, usr.ID) - // Check if user data has changed - if (len(s.LDAP().AdminFilter) > 0 && usr.IsAdmin != su.IsAdmin) || - strings.ToLower(usr.Email) != strings.ToLower(su.Mail) || - usr.FullName != fullName || - !usr.IsActive { - - log.Trace("SyncExternalUsers[%s]: Updating user %s", s.Name, usr.Name) - - usr.FullName = fullName - usr.Email = su.Mail - // Change existing admin flag only if AdminFilter option is set - if len(s.LDAP().AdminFilter) > 0 { - usr.IsAdmin = su.IsAdmin - } - usr.IsActive = true err = UpdateUserCols(usr, "full_name", "email", "is_admin", "is_active") if err != nil { log.Error(4, "SyncExternalUsers[%s]: Error updating user %s: %v", s.Name, usr.Name, err) } + + if isAttributeSSHPublicKeySet { + synchronizeLdapSSHPublicKeys(s, su.SSHPublicKey, usr) + } + } + + // Check if user data has changed + if (len(s.LDAP().AdminFilter) > 0 && usr.IsAdmin != su.IsAdmin) || + strings.ToLower(usr.Email) != strings.ToLower(su.Mail) || + usr.FullName != fullName || + !usr.IsActive { + + log.Trace("SyncExternalUsers[%s]: Updating user %s", s.Name, usr.Name) + + usr.FullName = fullName + usr.Email = su.Mail + // Change existing admin flag only if AdminFilter option is set + if len(s.LDAP().AdminFilter) > 0 { + usr.IsAdmin = su.IsAdmin + } + usr.IsActive = true + + err = UpdateUser(usr) + if err != nil { + log.Error(4, "SyncExternalUsers[%s]: Error updating user %s: %v", s.Name, usr.Name, err) } } } + // Rewrite authorized_keys file if LDAP Public SSH Key attribute is set and any key was added or removed + if isAttributeSSHPublicKeySet && sshKeysNeedUpdate { + RewriteAllPublicKeys() + } + // Deactivate users not present in LDAP if updateExisting { for _, usr := range users { diff --git a/modules/auth/auth_form.go b/modules/auth/auth_form.go index 7c452bbc35388..a44bf06511a2f 100644 --- a/modules/auth/auth_form.go +++ b/modules/auth/auth_form.go @@ -24,6 +24,7 @@ type AuthenticationForm struct { AttributeName string AttributeSurname string AttributeMail string + AttributeSSHPublicKey string AttributesInBind bool Filter string AdminFilter string diff --git a/modules/auth/ldap/ldap.go b/modules/auth/ldap/ldap.go index 7754cc8182646..b72a164812c8f 100644 --- a/modules/auth/ldap/ldap.go +++ b/modules/auth/ldap/ldap.go @@ -28,32 +28,34 @@ const ( // Source Basic LDAP authentication service type Source struct { - Name string // canonical name (ie. corporate.ad) - Host string // LDAP host - Port int // port number - SecurityProtocol SecurityProtocol - SkipVerify bool - BindDN string // DN to bind with - BindPassword string // Bind DN password - UserBase string // Base search path for users - UserDN string // Template for the DN of the user for simple auth - AttributeUsername string // Username attribute - AttributeName string // First name attribute - AttributeSurname string // Surname attribute - AttributeMail string // E-mail attribute - AttributesInBind bool // fetch attributes in bind context (not user) - Filter string // Query filter to validate entry - AdminFilter string // Query filter to check if user is admin - Enabled bool // if this source is disabled + Name string // canonical name (ie. corporate.ad) + Host string // LDAP host + Port int // port number + SecurityProtocol SecurityProtocol + SkipVerify bool + BindDN string // DN to bind with + BindPassword string // Bind DN password + UserBase string // Base search path for users + UserDN string // Template for the DN of the user for simple auth + AttributeUsername string // Username attribute + AttributeName string // First name attribute + AttributeSurname string // Surname attribute + AttributeMail string // E-mail attribute + AttributeSSHPublicKey string // LDAP SSH Public Key attribute + AttributesInBind bool // fetch attributes in bind context (not user) + Filter string // Query filter to validate entry + AdminFilter string // Query filter to check if user is admin + Enabled bool // if this source is disabled } // SearchResult : user data type SearchResult struct { - Username string // Username - Name string // Name - Surname string // Surname - Mail string // E-mail address - IsAdmin bool // if user is administrator + Username string // Username + Name string // Name + Surname string // Surname + Mail string // E-mail address + SSHPublicKey []string // SSH Public Key + IsAdmin bool // if user is administrator } func (ls *Source) sanitizedUserQuery(username string) (string, bool) { @@ -292,10 +294,10 @@ func (ls *Source) SearchEntries() []*SearchResult { userFilter := fmt.Sprintf(ls.Filter, "*") - log.Trace("Fetching attributes '%v', '%v', '%v', '%v' with filter %s and base %s", ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail, userFilter, ls.UserBase) + log.Trace("Fetching attributes '%v', '%v', '%v', '%v', '%v' with filter %s and base %s", ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail, ls.AttributeSSHPublicKey, userFilter, ls.UserBase) search := ldap.NewSearchRequest( ls.UserBase, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, userFilter, - []string{ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail}, + []string{ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail, ls.AttributeSSHPublicKey}, nil) sr, err := l.Search(search) @@ -308,11 +310,12 @@ func (ls *Source) SearchEntries() []*SearchResult { for i, v := range sr.Entries { result[i] = &SearchResult{ - Username: v.GetAttributeValue(ls.AttributeUsername), - Name: v.GetAttributeValue(ls.AttributeName), - Surname: v.GetAttributeValue(ls.AttributeSurname), - Mail: v.GetAttributeValue(ls.AttributeMail), - IsAdmin: checkAdmin(l, ls, v.DN), + Username: v.GetAttributeValue(ls.AttributeUsername), + Name: v.GetAttributeValue(ls.AttributeName), + Surname: v.GetAttributeValue(ls.AttributeSurname), + Mail: v.GetAttributeValue(ls.AttributeMail), + SSHPublicKey: v.GetAttributeValues(ls.AttributeSSHPublicKey), + IsAdmin: checkAdmin(l, ls, v.DN), } } diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index fede39484c811..ef563c5ca2c71 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1286,6 +1286,7 @@ auths.attribute_username_placeholder = Leave empty to use sign-in form field val auths.attribute_name = First name attribute auths.attribute_surname = Surname attribute auths.attribute_mail = Email attribute +auths.attribute_ssh_public_key = Public SSH key attribute auths.attributes_in_bind = Fetch attributes in Bind DN context auths.filter = User Filter auths.admin_filter = Admin Filter diff --git a/package.json b/package.json index 8251e5026446c..3410bb1f3589d 100644 --- a/package.json +++ b/package.json @@ -4,4 +4,4 @@ "less": "^2.7.2", "less-plugin-clean-css": "^1.5.1" } -} \ No newline at end of file +} diff --git a/routers/admin/auths.go b/routers/admin/auths.go index 590e45a4f41e9..d287aa7c8f1ac 100644 --- a/routers/admin/auths.go +++ b/routers/admin/auths.go @@ -93,23 +93,24 @@ func NewAuthSource(ctx *context.Context) { func parseLDAPConfig(form auth.AuthenticationForm) *models.LDAPConfig { return &models.LDAPConfig{ Source: &ldap.Source{ - Name: form.Name, - Host: form.Host, - Port: form.Port, - SecurityProtocol: ldap.SecurityProtocol(form.SecurityProtocol), - SkipVerify: form.SkipVerify, - BindDN: form.BindDN, - UserDN: form.UserDN, - BindPassword: form.BindPassword, - UserBase: form.UserBase, - AttributeUsername: form.AttributeUsername, - AttributeName: form.AttributeName, - AttributeSurname: form.AttributeSurname, - AttributeMail: form.AttributeMail, - AttributesInBind: form.AttributesInBind, - Filter: form.Filter, - AdminFilter: form.AdminFilter, - Enabled: true, + Name: form.Name, + Host: form.Host, + Port: form.Port, + SecurityProtocol: ldap.SecurityProtocol(form.SecurityProtocol), + SkipVerify: form.SkipVerify, + BindDN: form.BindDN, + UserDN: form.UserDN, + BindPassword: form.BindPassword, + UserBase: form.UserBase, + AttributeUsername: form.AttributeUsername, + AttributeName: form.AttributeName, + AttributeSurname: form.AttributeSurname, + AttributeMail: form.AttributeMail, + AttributeSSHPublicKey: form.AttributeSSHPublicKey, + AttributesInBind: form.AttributesInBind, + Filter: form.Filter, + AdminFilter: form.AdminFilter, + Enabled: true, }, } } diff --git a/routers/api/v1/user/key.go b/routers/api/v1/user/key.go index 1772ef4d25285..1a6fb08b98145 100644 --- a/routers/api/v1/user/key.go +++ b/routers/api/v1/user/key.go @@ -119,7 +119,7 @@ func CreateUserPublicKey(ctx *context.APIContext, form api.CreateKeyOption, uid return } - key, err := models.AddPublicKey(uid, form.Title, content) + key, err := models.AddPublicKey(uid, form.Title, content, 0) if err != nil { repo.HandleAddKeyError(ctx, err) return diff --git a/routers/user/setting.go b/routers/user/setting.go index b71b29ba21cb3..ce5638e3bfa6f 100644 --- a/routers/user/setting.go +++ b/routers/user/setting.go @@ -401,7 +401,7 @@ func SettingsKeysPost(ctx *context.Context, form auth.AddKeyForm) { } } - if _, err = models.AddPublicKey(ctx.User.ID, form.Title, content); err != nil { + if _, err = models.AddPublicKey(ctx.User.ID, form.Title, content, 0); err != nil { ctx.Data["HasSSHError"] = true switch { case models.IsErrKeyAlreadyExist(err): diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl index e3048b21836b0..e88287e547405 100644 --- a/templates/admin/auth/edit.tmpl +++ b/templates/admin/auth/edit.tmpl @@ -90,6 +90,10 @@ +
+ + +
{{if .Source.IsLDAP}}
diff --git a/templates/admin/auth/source/ldap.tmpl b/templates/admin/auth/source/ldap.tmpl index 2131950212214..241cec49ff16b 100644 --- a/templates/admin/auth/source/ldap.tmpl +++ b/templates/admin/auth/source/ldap.tmpl @@ -62,4 +62,8 @@
+
+ + +
From 26a9205be9744ec8dd27ab52ab195ad22c3d9d9f Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Sun, 25 Jun 2017 23:55:30 +0200 Subject: [PATCH 04/33] Move block: user data has changed Signed-off-by: Magnus Lindvall --- models/user.go | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/models/user.go b/models/user.go index 9a7f26acbc9c4..c5a381ce88a98 100644 --- a/models/user.go +++ b/models/user.go @@ -1581,27 +1581,27 @@ func SyncExternalUsers() { if isAttributeSSHPublicKeySet { synchronizeLdapSSHPublicKeys(s, su.SSHPublicKey, usr) } - } - // Check if user data has changed - if (len(s.LDAP().AdminFilter) > 0 && usr.IsAdmin != su.IsAdmin) || - strings.ToLower(usr.Email) != strings.ToLower(su.Mail) || - usr.FullName != fullName || - !usr.IsActive { + // Check if user data has changed + if (len(s.LDAP().AdminFilter) > 0 && usr.IsAdmin != su.IsAdmin) || + strings.ToLower(usr.Email) != strings.ToLower(su.Mail) || + usr.FullName != fullName || + !usr.IsActive { - log.Trace("SyncExternalUsers[%s]: Updating user %s", s.Name, usr.Name) + log.Trace("SyncExternalUsers[%s]: Updating user %s", s.Name, usr.Name) - usr.FullName = fullName - usr.Email = su.Mail - // Change existing admin flag only if AdminFilter option is set - if len(s.LDAP().AdminFilter) > 0 { - usr.IsAdmin = su.IsAdmin - } - usr.IsActive = true + usr.FullName = fullName + usr.Email = su.Mail + // Change existing admin flag only if AdminFilter option is set + if len(s.LDAP().AdminFilter) > 0 { + usr.IsAdmin = su.IsAdmin + } + usr.IsActive = true - err = UpdateUser(usr) - if err != nil { - log.Error(4, "SyncExternalUsers[%s]: Error updating user %s: %v", s.Name, usr.Name, err) + err = UpdateUser(usr) + if err != nil { + log.Error(4, "SyncExternalUsers[%s]: Error updating user %s: %v", s.Name, usr.Name, err) + } } } } From e0da1d7601c48f9c2ae33c9e593b5f28c6c0f072 Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Sun, 15 Oct 2017 23:29:12 +0200 Subject: [PATCH 05/33] Oops --- models/migrations/migrations.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 8c8e402a9b548..7fc3f9bd78553 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -141,11 +141,8 @@ var migrations = []Migration{ // v45 -> v46 NewMigration("remove index column from repo_unit table", removeIndexColumnFromRepoUnitTable), // v46 -> v47 -<<<<<<< HEAD NewMigration("remove organization watch repositories", removeOrganizationWatchRepo), // v47 -> v48 -======= ->>>>>>> d17981b75e2190d05033aa4816648c330c6d1d3a NewMigration("add field for ldap public public ssh key synchronization", addLoginSourceLdapPublicSSHKeySyncEnabled), } From b6e298579bf96f10127743996f6fdf46b3f4c6d8 Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Sun, 15 Oct 2017 23:32:27 +0200 Subject: [PATCH 06/33] Fix space --- models/user.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/models/user.go b/models/user.go index c5a381ce88a98..da570e91aa81c 100644 --- a/models/user.go +++ b/models/user.go @@ -1573,10 +1573,10 @@ func SyncExternalUsers() { } else if updateExisting { existingUsers = append(existingUsers, usr.ID) - err = UpdateUserCols(usr, "full_name", "email", "is_admin", "is_active") - if err != nil { - log.Error(4, "SyncExternalUsers[%s]: Error updating user %s: %v", s.Name, usr.Name, err) - } + err = UpdateUserCols(usr, "full_name", "email", "is_admin", "is_active") + if err != nil { + log.Error(4, "SyncExternalUsers[%s]: Error updating user %s: %v", s.Name, usr.Name, err) + } if isAttributeSSHPublicKeySet { synchronizeLdapSSHPublicKeys(s, su.SSHPublicKey, usr) From 1d4dbed6354d545e39ee18c11971ac216907015d Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Sun, 5 Nov 2017 19:36:52 +0100 Subject: [PATCH 07/33] null def null not needed --- models/ssh_key.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/ssh_key.go b/models/ssh_key.go index 41a927bee7c91..240d528f0d3a2 100644 --- a/models/ssh_key.go +++ b/models/ssh_key.go @@ -53,7 +53,7 @@ type PublicKey struct { Content string `xorm:"TEXT NOT NULL"` Mode AccessMode `xorm:"NOT NULL DEFAULT 2"` Type KeyType `xorm:"NOT NULL DEFAULT 1"` - LoginSourceID int64 `xorm:"NULL DEFAULT NULL"` + LoginSourceID int64 `xorm:"-"` Created time.Time `xorm:"-"` CreatedUnix int64 `xorm:"created"` From 55424bf5b762667fbe4a3854997eb798e76d0b7e Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Sun, 5 Nov 2017 19:38:59 +0100 Subject: [PATCH 08/33] move existsInSlice and IsEqualSlice to compare --- models/user.go | 29 ----------------------------- modules/util/compare.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/models/user.go b/models/user.go index 343651b0cd4d7..dfd717984afcd 100644 --- a/models/user.go +++ b/models/user.go @@ -1353,35 +1353,6 @@ func GetWatchedRepos(userID int64, private bool) ([]*Repository, error) { return repos, nil } -// existsInSlice returns true if string exists in slice -func existsInSlice(target string, slice []string) bool { - i := sort.Search(len(slice), - func(i int) bool { return slice[i] == target }) - return i < len(slice) -} - -// IsEqualSlice returns true if slices are equal -func IsEqualSlice(target []string, source []string) bool { - if len(target) != len(source) { - return false - } - - if (target == nil) != (source == nil) { - return false - } - - sort.Strings(target) - sort.Strings(source) - - for i, v := range target { - if v != source[i] { - return false - } - } - - return true -} - // deleteKeysMarkedForDeletion returns true if ssh keys needs update func deleteKeysMarkedForDeletion(keys []string) (sshKeysNeedUpdate bool, err error) { // Start session diff --git a/modules/util/compare.go b/modules/util/compare.go index c03a823d850df..55d648c2942c8 100644 --- a/modules/util/compare.go +++ b/modules/util/compare.go @@ -27,3 +27,32 @@ func IsSliceInt64Eq(a, b []int64) bool { } return true } + +// existsInSlice returns true if string exists in slice +func existsInSlice(target string, slice []string) bool { + i := sort.Search(len(slice), + func(i int) bool { return slice[i] == target }) + return i < len(slice) +} + +// IsEqualSlice returns true if slices are equal +func IsEqualSlice(target []string, source []string) bool { + if len(target) != len(source) { + return false + } + + if (target == nil) != (source == nil) { + return false + } + + sort.Strings(target) + sort.Strings(source) + + for i, v := range target { + if v != source[i] { + return false + } + } + + return true +} From 922e76cc6f9455dbdd30eb615675057d3f13802d Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Sun, 5 Nov 2017 19:40:38 +0100 Subject: [PATCH 09/33] revert just whitespace --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3410bb1f3589d..8251e5026446c 100644 --- a/package.json +++ b/package.json @@ -4,4 +4,4 @@ "less": "^2.7.2", "less-plugin-clean-css": "^1.5.1" } -} +} \ No newline at end of file From 03690debda7420d2666c7cc5d220e67bc4cb103b Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Sun, 5 Nov 2017 20:02:17 +0100 Subject: [PATCH 10/33] Make compare funcs exported --- models/user.go | 8 ++++---- modules/util/compare.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/models/user.go b/models/user.go index dfd717984afcd..86a37af7477be 100644 --- a/models/user.go +++ b/models/user.go @@ -1414,7 +1414,7 @@ func synchronizeLdapSSHPublicKeys(s *LoginSource, SSHPublicKeys []string, usr *U var ldapKeys []string for _, v := range SSHPublicKeys { ldapKey := strings.Join(strings.Split(v, " ")[:2], " ") - if !existsInSlice(ldapKey, ldapKeys) { + if !util.ExistsInSlice(ldapKey, ldapKeys) { ldapKeys = append(ldapKeys, ldapKey) } } @@ -1422,13 +1422,13 @@ func synchronizeLdapSSHPublicKeys(s *LoginSource, SSHPublicKeys []string, usr *U // Check if Public Key sync is needed var giteaKeysToDelete []string - if IsEqualSlice(giteaKeys, ldapKeys) { + if util.IsEqualSlice(giteaKeys, ldapKeys) { log.Trace("synchronizeLdapSSHPublicKeys[%s]: LDAP Public Keys are already in sync for %s (LDAP:%v/DB:%v)", s.Name, usr.Name, len(ldapKeys), len(giteaKeys)) } else { log.Trace("synchronizeLdapSSHPublicKeys[%s]: LDAP Public Key needs update for user %s (LDAP:%v/DB:%v)", s.Name, usr.Name, len(ldapKeys), len(giteaKeys)) // Delete giteaKeys that doesn't exist in ldapKeys and has been synced from LDAP earlier for _, giteaKey := range giteaKeys { - if !existsInSlice(giteaKey, ldapKeys) { + if !util.ExistsInSlice(giteaKey, ldapKeys) { log.Trace("synchronizeLdapSSHPublicKeys[%s]: Marking LDAP Public SSH Key for deletion for user %s: %v", s.Name, usr.Name, giteaKey) giteaKeysToDelete = append(giteaKeysToDelete, giteaKey) } @@ -1444,7 +1444,7 @@ func synchronizeLdapSSHPublicKeys(s *LoginSource, SSHPublicKeys []string, usr *U // Add LDAP Public SSH Keys that doesn't already exist in DB var newLdapSSHKeys []string for _, LDAPPublicSSHKey := range ldapKeys { - if !existsInSlice(LDAPPublicSSHKey, giteaKeys) { + if !util.ExistsInSlice(LDAPPublicSSHKey, giteaKeys) { newLdapSSHKeys = append(newLdapSSHKeys, LDAPPublicSSHKey) } } diff --git a/modules/util/compare.go b/modules/util/compare.go index 55d648c2942c8..5a98833a6e977 100644 --- a/modules/util/compare.go +++ b/modules/util/compare.go @@ -29,7 +29,7 @@ func IsSliceInt64Eq(a, b []int64) bool { } // existsInSlice returns true if string exists in slice -func existsInSlice(target string, slice []string) bool { +func ExistsInSlice(target string, slice []string) bool { i := sort.Search(len(slice), func(i int) bool { return slice[i] == target }) return i < len(slice) From ec95606604a8829999d52966d27b1d84595248c2 Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Sun, 5 Nov 2017 20:05:05 +0100 Subject: [PATCH 11/33] Fix typo in comment --- modules/util/compare.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/util/compare.go b/modules/util/compare.go index 5a98833a6e977..4d8db3eac2651 100644 --- a/modules/util/compare.go +++ b/modules/util/compare.go @@ -28,7 +28,7 @@ func IsSliceInt64Eq(a, b []int64) bool { return true } -// existsInSlice returns true if string exists in slice +// ExistsInSlice returns true if string exists in slice func ExistsInSlice(target string, slice []string) bool { i := sort.Search(len(slice), func(i int) bool { return slice[i] == target }) From 71cd39d3515755b88e620d5b20e9d5dc3039f25b Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Sun, 5 Nov 2017 20:10:44 +0100 Subject: [PATCH 12/33] Change comment, try to make lint happy --- modules/util/compare.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/util/compare.go b/modules/util/compare.go index 4d8db3eac2651..c61e7965ae612 100644 --- a/modules/util/compare.go +++ b/modules/util/compare.go @@ -28,14 +28,14 @@ func IsSliceInt64Eq(a, b []int64) bool { return true } -// ExistsInSlice returns true if string exists in slice +// ExistsInSlice returns true if string exists in slice. func ExistsInSlice(target string, slice []string) bool { i := sort.Search(len(slice), func(i int) bool { return slice[i] == target }) return i < len(slice) } -// IsEqualSlice returns true if slices are equal +// IsEqualSlice returns true if slices are equal. func IsEqualSlice(target []string, source []string) bool { if len(target) != len(source) { return false From eddb932ead9812971a068db5ac81d15031428fe5 Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Sun, 5 Nov 2017 23:36:29 +0100 Subject: [PATCH 13/33] Add migration: add login source id column for public_key table --- models/migrations/migrations.go | 2 ++ models/migrations/v49.go | 22 ++++++++++++++++++++++ models/ssh_key.go | 2 +- 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 models/migrations/v49.go diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index ba27568fd4d25..cc4989cc2ddd6 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -146,6 +146,8 @@ var migrations = []Migration{ NewMigration("add deleted branches", addDeletedBranch), // v48 -> v49 NewMigration("add repo indexer status", addRepoIndexerStatus), + // v49 -> v50 + NewMigration("add login source id column for public_key table", addLoginSourceIdToPublicKeyTable), } // Migrate database to current version diff --git a/models/migrations/v49.go b/models/migrations/v49.go new file mode 100644 index 0000000000000..66c09624617f6 --- /dev/null +++ b/models/migrations/v49.go @@ -0,0 +1,22 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "fmt" + + "github.com/go-xorm/xorm" +) + +func addLoginSourceIdToPublicKeyTable(x *xorm.Engine) error { + type PublicKey struct { + LoginSourceID int64 `xorm:"NOT NULL DEFAULT 0"` + } + + if err := x.Sync2(new(PublicKey)); err != nil { + return fmt.Errorf("Sync2: %v", err) + } + return nil +} diff --git a/models/ssh_key.go b/models/ssh_key.go index 240d528f0d3a2..742a99f5ccc6c 100644 --- a/models/ssh_key.go +++ b/models/ssh_key.go @@ -53,7 +53,7 @@ type PublicKey struct { Content string `xorm:"TEXT NOT NULL"` Mode AccessMode `xorm:"NOT NULL DEFAULT 2"` Type KeyType `xorm:"NOT NULL DEFAULT 1"` - LoginSourceID int64 `xorm:"-"` + LoginSourceID int64 `xorm:"NOT NULL DEFAULT 0"` Created time.Time `xorm:"-"` CreatedUnix int64 `xorm:"created"` From ab9f88bad545d83f54de4ff34b341749878beb88 Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Mon, 6 Nov 2017 00:11:28 +0100 Subject: [PATCH 14/33] Only update keys if needed --- models/user.go | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/models/user.go b/models/user.go index 86a37af7477be..ac68ca5f97a3f 100644 --- a/models/user.go +++ b/models/user.go @@ -1426,7 +1426,20 @@ func synchronizeLdapSSHPublicKeys(s *LoginSource, SSHPublicKeys []string, usr *U log.Trace("synchronizeLdapSSHPublicKeys[%s]: LDAP Public Keys are already in sync for %s (LDAP:%v/DB:%v)", s.Name, usr.Name, len(ldapKeys), len(giteaKeys)) } else { log.Trace("synchronizeLdapSSHPublicKeys[%s]: LDAP Public Key needs update for user %s (LDAP:%v/DB:%v)", s.Name, usr.Name, len(ldapKeys), len(giteaKeys)) - // Delete giteaKeys that doesn't exist in ldapKeys and has been synced from LDAP earlier + + // Add LDAP Public SSH Keys that doesn't already exist in DB + var newLdapSSHKeys []string + for _, LDAPPublicSSHKey := range ldapKeys { + if !util.ExistsInSlice(LDAPPublicSSHKey, giteaKeys) { + newLdapSSHKeys = append(newLdapSSHKeys, LDAPPublicSSHKey) + } + } + sshKeysNeedUpdate, err = addLdapSSHPublicKeys(s, usr, newLdapSSHKeys) + if err != nil { + log.Error(4, "synchronizeLdapSSHPublicKeys[%s]: Error updating LDAP Public SSH Keys for user %s: %v", s.Name, usr.Name, err) + } + + // Mark LDAP keys from DB that doesn't exist in LDAP for deletion for _, giteaKey := range giteaKeys { if !util.ExistsInSlice(giteaKey, ldapKeys) { log.Trace("synchronizeLdapSSHPublicKeys[%s]: Marking LDAP Public SSH Key for deletion for user %s: %v", s.Name, usr.Name, giteaKey) @@ -1441,18 +1454,6 @@ func synchronizeLdapSSHPublicKeys(s *LoginSource, SSHPublicKeys []string, usr *U log.Error(4, "synchronizeLdapSSHPublicKeys[%s]: Error deleting LDAP Public SSH Keys marked for deletion for user %s: %v", s.Name, usr.Name, err) } - // Add LDAP Public SSH Keys that doesn't already exist in DB - var newLdapSSHKeys []string - for _, LDAPPublicSSHKey := range ldapKeys { - if !util.ExistsInSlice(LDAPPublicSSHKey, giteaKeys) { - newLdapSSHKeys = append(newLdapSSHKeys, LDAPPublicSSHKey) - } - } - sshKeysNeedUpdate, err = addLdapSSHPublicKeys(s, usr, newLdapSSHKeys) - if err != nil { - log.Error(4, "synchronizeLdapSSHPublicKeys[%s]: Error updating LDAP Public SSH Keys for user %s: %v", s.Name, usr.Name, err) - } - return sshKeysNeedUpdate, err } From 056734ff652b818d25ef7601eb43ff3a1095ea57 Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Mon, 6 Nov 2017 00:32:18 +0100 Subject: [PATCH 15/33] Make lint happy --- models/migrations/migrations.go | 2 +- models/migrations/v49.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index cc4989cc2ddd6..8f601f271a3ec 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -147,7 +147,7 @@ var migrations = []Migration{ // v48 -> v49 NewMigration("add repo indexer status", addRepoIndexerStatus), // v49 -> v50 - NewMigration("add login source id column for public_key table", addLoginSourceIdToPublicKeyTable), + NewMigration("add login source id column for public_key table", addLoginSourceIDToPublicKeyTable), } // Migrate database to current version diff --git a/models/migrations/v49.go b/models/migrations/v49.go index 66c09624617f6..43acfb4ea56f9 100644 --- a/models/migrations/v49.go +++ b/models/migrations/v49.go @@ -10,7 +10,7 @@ import ( "github.com/go-xorm/xorm" ) -func addLoginSourceIdToPublicKeyTable(x *xorm.Engine) error { +func addLoginSourceIDToPublicKeyTable(x *xorm.Engine) error { type PublicKey struct { LoginSourceID int64 `xorm:"NOT NULL DEFAULT 0"` } From 83cd353d5ae06f87c9c47fb4a8ed09a9c3301d3b Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Mon, 6 Nov 2017 21:40:36 +0100 Subject: [PATCH 16/33] Trigger a build. From 027c832473e819719eddf605690f660c1f712325 Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Wed, 6 Dec 2017 05:43:32 +0100 Subject: [PATCH 17/33] Simple validity check for ssh key --- models/user.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/models/user.go b/models/user.go index c19640f65e87c..a8d55d504fe30 100644 --- a/models/user.go +++ b/models/user.go @@ -1390,15 +1390,18 @@ func deleteKeysMarkedForDeletion(keys []string) (sshKeysNeedUpdate bool, err err func addLdapSSHPublicKeys(s *LoginSource, usr *User, SSHPublicKeys []string) (sshKeysNeedUpdate bool, err error) { for _, LDAPPublicSSHKey := range SSHPublicKeys { - LDAPPublicSSHKeyName := strings.Join([]string{s.Name, LDAPPublicSSHKey[0:40]}, "-") - _, err := AddPublicKey(usr.ID, LDAPPublicSSHKeyName, LDAPPublicSSHKey, s.ID) - if err != nil { - log.Error(4, "addLdapSSHPublicKeys[%s]: Error adding LDAP Public SSH Key for user %s: %v", s.Name, usr.Name, err) + if strings.HasPrefix(strings.ToLower(LDAPPublicSSHKey), "ssh") { + LDAPPublicSSHKeyName := strings.Join([]string{s.Name, LDAPPublicSSHKey[0:40]}, "-") + _, err := AddPublicKey(usr.ID, LDAPPublicSSHKeyName, LDAPPublicSSHKey, s.ID) + if err != nil { + log.Error(4, "addLdapSSHPublicKeys[%s]: Error adding LDAP Public SSH Key for user %s: %v", s.Name, usr.Name, err) + } else { + log.Trace("addLdapSSHPublicKeys[%s]: Added LDAP Public SSH Key for user %s: %v", s.Name, usr.Name, LDAPPublicSSHKey) + sshKeysNeedUpdate = true + } } else { - log.Trace("addLdapSSHPublicKeys[%s]: Added LDAP Public SSH Key for user %s: %v", s.Name, usr.Name, LDAPPublicSSHKey) - sshKeysNeedUpdate = true + log.Error(3, "addLdapSSHPublicKeys[%s]: Skipping invalid LDAP Public SSH Key for user %s: %v", s.Name, usr.Name, LDAPPublicSSHKey) } - } return sshKeysNeedUpdate, err } From 23b343dc62385749024500fee9c9df455d8f8124 Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Tue, 19 Dec 2017 23:15:21 +0100 Subject: [PATCH 18/33] Move migration to resolve conflict --- models/migrations/migrations.go | 2 +- models/migrations/v54.go | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 models/migrations/v54.go diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 54e82b24c30db..6299871684be9 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -160,7 +160,7 @@ var migrations = []Migration{ NewMigration("add lfs lock table", addLFSLock), // v53 -> v54 NewMigration("add reactions", addReactions), - // v51 -> v52 + // v54 -> v55 NewMigration("add login source id column for public_key table", addLoginSourceIDToPublicKeyTable), } diff --git a/models/migrations/v54.go b/models/migrations/v54.go new file mode 100644 index 0000000000000..43acfb4ea56f9 --- /dev/null +++ b/models/migrations/v54.go @@ -0,0 +1,22 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "fmt" + + "github.com/go-xorm/xorm" +) + +func addLoginSourceIDToPublicKeyTable(x *xorm.Engine) error { + type PublicKey struct { + LoginSourceID int64 `xorm:"NOT NULL DEFAULT 0"` + } + + if err := x.Sync2(new(PublicKey)); err != nil { + return fmt.Errorf("Sync2: %v", err) + } + return nil +} From 98a80830680005e3c163d44ca9be87095cb08acd Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Mon, 25 Dec 2017 00:13:29 +0100 Subject: [PATCH 19/33] Only update columns which needs update --- models/user.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/user.go b/models/user.go index 631258ca6d55b..192c413a058d9 100644 --- a/models/user.go +++ b/models/user.go @@ -1547,7 +1547,7 @@ func SyncExternalUsers() { } else if updateExisting { existingUsers = append(existingUsers, usr.ID) - err = UpdateUserCols(usr, "full_name", "email", "is_admin", "is_active") + err = UpdateUser(usr) if err != nil { log.Error(4, "SyncExternalUsers[%s]: Error updating user %s: %v", s.Name, usr.Name, err) } From 615e9c536b939cfd7675cac474a8d64807ccfbe5 Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Sun, 21 Jan 2018 20:03:48 +0100 Subject: [PATCH 20/33] Do not always update all columns --- models/user.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/models/user.go b/models/user.go index baea8deac6c5b..5c96e40828034 100644 --- a/models/user.go +++ b/models/user.go @@ -1557,11 +1557,7 @@ func SyncExternalUsers() { } else if updateExisting { existingUsers = append(existingUsers, usr.ID) - err = UpdateUser(usr) - if err != nil { - log.Error(4, "SyncExternalUsers[%s]: Error updating user %s: %v", s.Name, usr.Name, err) - } - + // Synchronize SSH Public Key if that attribute is set if isAttributeSSHPublicKeySet { synchronizeLdapSSHPublicKeys(s, su.SSHPublicKey, usr) } From 45439fdeda0b12b0748c854cf5b336d6ee8160bb Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Sun, 21 Jan 2018 21:44:30 +0100 Subject: [PATCH 21/33] Add function to only list pubkey synchronized from ldap --- models/ssh_key.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/models/ssh_key.go b/models/ssh_key.go index 51fd4145a10df..5b397b5a354ce 100644 --- a/models/ssh_key.go +++ b/models/ssh_key.go @@ -472,6 +472,14 @@ func ListPublicKeys(uid int64) ([]*PublicKey, error) { Find(&keys) } +// ListPublicLdapSSHKeys returns a list of synchronized public ldap ssh keys belongs to given user. +func ListPublicLdapSSHKeys(uid int64) ([]*PublicKey, error) { + keys := make([]*PublicKey, 0, 5) + return keys, x. + Where("owner_id = ? AND login_source_id > 0", uid). + Find(&keys) +} + // UpdatePublicKeyUpdated updates public key use time. func UpdatePublicKeyUpdated(id int64) error { // Check if key exists before update as affected rows count is unreliable From a199541dbd0e9f264de9d02aac2854784bc2ac62 Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Sun, 21 Jan 2018 21:47:00 +0100 Subject: [PATCH 22/33] Only list pub ssh keys synchronized from ldap. Do not sort strings as ExistsInSlice does it. --- models/user.go | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/models/user.go b/models/user.go index 5c96e40828034..b74defa051c2d 100644 --- a/models/user.go +++ b/models/user.go @@ -19,7 +19,6 @@ import ( "image/png" "os" "path/filepath" - "sort" "strings" "time" "unicode/utf8" @@ -1411,19 +1410,14 @@ func synchronizeLdapSSHPublicKeys(s *LoginSource, SSHPublicKeys []string, usr *U // Get Public Keys from DB with LDAP source var giteaKeys []string - keys, err := ListPublicKeys(usr.ID) + keys, err := ListPublicLdapSSHKeys(usr.ID) if err != nil { log.Error(4, "synchronizeLdapSSHPublicKeys[%s]: Error listing LDAP Public SSH Keys for user %s: %v", s.Name, usr.Name, err) } - // Get Public Keys from DB, which has been synchronized from LDAP for _, v := range keys { - // If key was synced from LDAP, add it to list - if v.LoginSourceID > 0 { - giteaKeys = append(giteaKeys, v.OmitEmail()) - } + giteaKeys = append(giteaKeys, v.OmitEmail()) } - sort.Strings(giteaKeys) // Get Public Keys from LDAP and skip duplicate keys var ldapKeys []string @@ -1433,7 +1427,6 @@ func synchronizeLdapSSHPublicKeys(s *LoginSource, SSHPublicKeys []string, usr *U ldapKeys = append(ldapKeys, ldapKey) } } - sort.Strings(ldapKeys) // Check if Public Key sync is needed var giteaKeysToDelete []string From 8b6f96c75f19cc670c56d8337926c220488f9b68 Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Thu, 25 Jan 2018 12:15:44 +0100 Subject: [PATCH 23/33] Only get keys belonging to current login source id --- models/ssh_key.go | 6 +++--- models/user.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/models/ssh_key.go b/models/ssh_key.go index 2d493e00486c0..e292894018ea3 100644 --- a/models/ssh_key.go +++ b/models/ssh_key.go @@ -473,11 +473,11 @@ func ListPublicKeys(uid int64) ([]*PublicKey, error) { Find(&keys) } -// ListPublicLdapSSHKeys returns a list of synchronized public ldap ssh keys belongs to given user. -func ListPublicLdapSSHKeys(uid int64) ([]*PublicKey, error) { +// ListPublicLdapSSHKeys returns a list of synchronized public ldap ssh keys belongs to given user and ldap source. +func ListPublicLdapSSHKeys(uid int64, lsid int64) ([]*PublicKey, error) { keys := make([]*PublicKey, 0, 5) return keys, x. - Where("owner_id = ? AND login_source_id > 0", uid). + Where("owner_id = ? AND login_source_id = ?", uid, lsid). Find(&keys) } diff --git a/models/user.go b/models/user.go index 8680dfa9e6a17..461989493a629 100644 --- a/models/user.go +++ b/models/user.go @@ -1395,9 +1395,9 @@ func addLdapSSHPublicKeys(s *LoginSource, usr *User, SSHPublicKeys []string) (ss func synchronizeLdapSSHPublicKeys(s *LoginSource, SSHPublicKeys []string, usr *User) (sshKeysNeedUpdate bool, err error) { log.Trace("synchronizeLdapSSHPublicKeys[%s]: Handling LDAP Public SSH Key synchronization for user %s", s.Name, usr.Name) - // Get Public Keys from DB with LDAP source + // Get Public Keys from DB with current LDAP source var giteaKeys []string - keys, err := ListPublicLdapSSHKeys(usr.ID) + keys, err := ListPublicLdapSSHKeys(usr.ID, s.ID) if err != nil { log.Error(4, "synchronizeLdapSSHPublicKeys[%s]: Error listing LDAP Public SSH Keys for user %s: %v", s.Name, usr.Name, err) } From 68a1dc724a0b43b53211695158573a0720bb98a7 Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Thu, 15 Feb 2018 12:37:52 +0100 Subject: [PATCH 24/33] login source, not ldap source --- models/ssh_key.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/ssh_key.go b/models/ssh_key.go index e292894018ea3..bac2324cdade7 100644 --- a/models/ssh_key.go +++ b/models/ssh_key.go @@ -473,7 +473,7 @@ func ListPublicKeys(uid int64) ([]*PublicKey, error) { Find(&keys) } -// ListPublicLdapSSHKeys returns a list of synchronized public ldap ssh keys belongs to given user and ldap source. +// ListPublicLdapSSHKeys returns a list of synchronized public ldap ssh keys belongs to given user and login source. func ListPublicLdapSSHKeys(uid int64, lsid int64) ([]*PublicKey, error) { keys := make([]*PublicKey, 0, 5) return keys, x. From 68e206aa34c187e8bcd83f3cc2437d0c23dc5ea4 Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Thu, 15 Feb 2018 12:42:33 +0100 Subject: [PATCH 25/33] A better argument name --- models/ssh_key.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/ssh_key.go b/models/ssh_key.go index bac2324cdade7..997e8ee99758d 100644 --- a/models/ssh_key.go +++ b/models/ssh_key.go @@ -474,10 +474,10 @@ func ListPublicKeys(uid int64) ([]*PublicKey, error) { } // ListPublicLdapSSHKeys returns a list of synchronized public ldap ssh keys belongs to given user and login source. -func ListPublicLdapSSHKeys(uid int64, lsid int64) ([]*PublicKey, error) { +func ListPublicLdapSSHKeys(uid int64, LoginSourceID int64) ([]*PublicKey, error) { keys := make([]*PublicKey, 0, 5) return keys, x. - Where("owner_id = ? AND login_source_id = ?", uid, lsid). + Where("owner_id = ? AND login_source_id = ?", uid, LoginSourceID). Find(&keys) } From aff1346c900011eabc292932f52c6404cff25557 Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Sun, 4 Mar 2018 11:14:21 +0100 Subject: [PATCH 26/33] Remove unused line --- models/migrations/migrations.go | 1 - 1 file changed, 1 deletion(-) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 49ceb3cf25f18..e01640bcf58b8 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -23,7 +23,6 @@ import ( "code.gitea.io/gitea/modules/generate" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/vendor/gopkg.in/ini.v1" ) const minDBVersion = 4 From 3ff65c76a0ab679ed37e1a6fbb09349d8523ec86 Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Tue, 10 Apr 2018 12:17:22 +0200 Subject: [PATCH 27/33] Fix import --- models/migrations/migrations.go | 1 - 1 file changed, 1 deletion(-) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index e1767c6b4519b..59b1fc0008a6c 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -23,7 +23,6 @@ import ( "code.gitea.io/gitea/modules/generate" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/vendor/gopkg.in/ini.v1" ) const minDBVersion = 4 From 3181cb96bd7f09e9258b04d2a0dabf6281d19184 Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Tue, 10 Apr 2018 12:47:24 +0200 Subject: [PATCH 28/33] empty From 237ae234233bd4990cbc2b50ea8361529d3a9aed Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Sat, 19 May 2018 20:57:09 +0200 Subject: [PATCH 29/33] Set default login source id to 0 --- routers/user/setting/keys.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/user/setting/keys.go b/routers/user/setting/keys.go index 5c28fa6e6d7ad..ef986ef8c9063 100644 --- a/routers/user/setting/keys.go +++ b/routers/user/setting/keys.go @@ -99,7 +99,7 @@ func KeysPost(ctx *context.Context, form auth.AddKeyForm) { return } - if _, err = models.AddPublicKey(ctx.User.ID, form.Title, content); err != nil { + if _, err = models.AddPublicKey(ctx.User.ID, form.Title, content, 0); err != nil { ctx.Data["HasSSHError"] = true switch { case models.IsErrKeyAlreadyExist(err): From f3b4b31d2b7742d285b9d934333ef7c437f1696e Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Sat, 19 May 2018 22:27:52 +0200 Subject: [PATCH 30/33] Fixes spacing for LDAPConfig --- routers/admin/auths.go | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/routers/admin/auths.go b/routers/admin/auths.go index b8205d10451e8..40b7df108d9b5 100644 --- a/routers/admin/auths.go +++ b/routers/admin/auths.go @@ -97,25 +97,25 @@ func parseLDAPConfig(form auth.AuthenticationForm) *models.LDAPConfig { } return &models.LDAPConfig{ Source: &ldap.Source{ - Name: form.Name, - Host: form.Host, - Port: form.Port, - SecurityProtocol: ldap.SecurityProtocol(form.SecurityProtocol), - SkipVerify: form.SkipVerify, - BindDN: form.BindDN, - UserDN: form.UserDN, - BindPassword: form.BindPassword, - UserBase: form.UserBase, - AttributeUsername: form.AttributeUsername, - AttributeName: form.AttributeName, - AttributeSurname: form.AttributeSurname, - AttributeMail: form.AttributeMail, - AttributesInBind: form.AttributesInBind, + Name: form.Name, + Host: form.Host, + Port: form.Port, + SecurityProtocol: ldap.SecurityProtocol(form.SecurityProtocol), + SkipVerify: form.SkipVerify, + BindDN: form.BindDN, + UserDN: form.UserDN, + BindPassword: form.BindPassword, + UserBase: form.UserBase, + AttributeUsername: form.AttributeUsername, + AttributeName: form.AttributeName, + AttributeSurname: form.AttributeSurname, + AttributeMail: form.AttributeMail, + AttributesInBind: form.AttributesInBind, AttributeSSHPublicKey: form.AttributeSSHPublicKey, - SearchPageSize: pageSize, - Filter: form.Filter, - AdminFilter: form.AdminFilter, - Enabled: true, + SearchPageSize: pageSize, + Filter: form.Filter, + AdminFilter: form.AdminFilter, + Enabled: true, }, } } From 32d8e0fdeb4a7b5755118f1034d4bf436805fc42 Mon Sep 17 00:00:00 2001 From: Magnus Lindvall Date: Sat, 19 May 2018 22:39:17 +0200 Subject: [PATCH 31/33] empty From bc32826b6b45b1d9c66857d3e616c0aff1a2e8ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauris=20Buk=C5=A1is-Haberkorns?= Date: Thu, 24 May 2018 07:25:39 +0300 Subject: [PATCH 32/33] Some minor cleanup. Add integration tests (updete dep testify) --- Gopkg.lock | 6 +- integrations/auth_ldap_test.go | 79 ++++-- models/user.go | 97 ++++--- options/locale/locale_en-US.ini | 2 +- vendor/github.com/davecgh/go-spew/LICENSE | 2 +- .../github.com/davecgh/go-spew/spew/bypass.go | 6 +- .../github.com/davecgh/go-spew/spew/common.go | 2 +- .../github.com/davecgh/go-spew/spew/dump.go | 10 +- .../github.com/davecgh/go-spew/spew/format.go | 4 +- .../github.com/stretchr/testify/LICENCE.txt | 22 -- .../testify/assert/assertion_format.go | 104 +++---- .../testify/assert/assertion_forward.go | 208 +++++--------- .../stretchr/testify/assert/assertions.go | 264 +++++++++++------- .../testify/assert/http_assertions.go | 10 +- 14 files changed, 401 insertions(+), 415 deletions(-) delete mode 100644 vendor/github.com/stretchr/testify/LICENCE.txt diff --git a/Gopkg.lock b/Gopkg.lock index 9e1adb19478a2..8bf9f846592b2 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -167,7 +167,8 @@ [[projects]] name = "github.com/davecgh/go-spew" packages = ["spew"] - revision = "ecdeabc65495df2dec95d7c4a4c3e021903035e5" + revision = "346938d642f2ec3594ed81d874461961cd0faa76" + version = "v1.1.0" [[projects]] name = "github.com/denisenkom/go-mssqldb" @@ -669,7 +670,8 @@ [[projects]] name = "github.com/stretchr/testify" packages = ["assert"] - revision = "2aa2c176b9dab406a6970f6a55f513e8a8c8b18f" + revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71" + version = "v1.2.1" [[projects]] name = "github.com/syndtr/goleveldb" diff --git a/integrations/auth_ldap_test.go b/integrations/auth_ldap_test.go index df26f95ed01f1..f31f598fa463e 100644 --- a/integrations/auth_ldap_test.go +++ b/integrations/auth_ldap_test.go @@ -40,7 +40,11 @@ var gitLDAPUsers = []ldapUser{ Password: "hermes", FullName: "Conrad Hermes", Email: "hermes@planetexpress.com", - IsAdmin: true, + SSHKeys: []string{ + "SHA256:qLY06smKfHoW/92yXySpnxFR10QFrLdRjf/GNPvwcW8", + "SHA256:QlVTuM5OssDatqidn2ffY+Lc4YA5Fs78U+0KOHI51jQ", + }, + IsAdmin: true, }, { UserName: "fry", @@ -89,26 +93,27 @@ func getLDAPServerHost() string { return host } -func addAuthSourceLDAP(t *testing.T) { +func addAuthSourceLDAP(t *testing.T, sshKeyAttribute string) { session := loginUser(t, "user1") csrf := GetCSRF(t, session, "/admin/auths/new") req := NewRequestWithValues(t, "POST", "/admin/auths/new", map[string]string{ - "_csrf": csrf, - "type": "2", - "name": "ldap", - "host": getLDAPServerHost(), - "port": "389", - "bind_dn": "uid=gitea,ou=service,dc=planetexpress,dc=com", - "bind_password": "password", - "user_base": "ou=people,dc=planetexpress,dc=com", - "filter": "(&(objectClass=inetOrgPerson)(memberOf=cn=git,ou=people,dc=planetexpress,dc=com)(uid=%s))", - "admin_filter": "(memberOf=cn=admin_staff,ou=people,dc=planetexpress,dc=com)", - "attribute_username": "uid", - "attribute_name": "givenName", - "attribute_surname": "sn", - "attribute_mail": "mail", - "is_sync_enabled": "on", - "is_active": "on", + "_csrf": csrf, + "type": "2", + "name": "ldap", + "host": getLDAPServerHost(), + "port": "389", + "bind_dn": "uid=gitea,ou=service,dc=planetexpress,dc=com", + "bind_password": "password", + "user_base": "ou=people,dc=planetexpress,dc=com", + "filter": "(&(objectClass=inetOrgPerson)(memberOf=cn=git,ou=people,dc=planetexpress,dc=com)(uid=%s))", + "admin_filter": "(memberOf=cn=admin_staff,ou=people,dc=planetexpress,dc=com)", + "attribute_username": "uid", + "attribute_name": "givenName", + "attribute_surname": "sn", + "attribute_mail": "mail", + "attribute_ssh_public_key": sshKeyAttribute, + "is_sync_enabled": "on", + "is_active": "on", }) session.MakeRequest(t, req, http.StatusFound) } @@ -119,7 +124,7 @@ func TestLDAPUserSignin(t *testing.T) { return } prepareTestEnv(t) - addAuthSourceLDAP(t) + addAuthSourceLDAP(t, "") u := gitLDAPUsers[0] @@ -140,7 +145,7 @@ func TestLDAPUserSync(t *testing.T) { return } prepareTestEnv(t) - addAuthSourceLDAP(t) + addAuthSourceLDAP(t, "") models.SyncExternalUsers() session := loginUser(t, "user1") @@ -186,9 +191,41 @@ func TestLDAPUserSigninFailed(t *testing.T) { return } prepareTestEnv(t) - addAuthSourceLDAP(t) + addAuthSourceLDAP(t, "") u := otherLDAPUsers[0] testLoginFailed(t, u.UserName, u.Password, i18n.Tr("en", "form.username_password_incorrect")) } + +func TestLDAPUserSSHKeySync(t *testing.T) { + if skipLDAPTests() { + t.Skip() + return + } + prepareTestEnv(t) + addAuthSourceLDAP(t, "sshPublicKey") + models.SyncExternalUsers() + + // Check if users has SSH keys synced + for _, u := range gitLDAPUsers { + if len(u.SSHKeys) == 0 { + continue + } + session := loginUserWithPassword(t, u.UserName, u.Password) + + req := NewRequest(t, "GET", "/user/settings/keys") + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + + divs := htmlDoc.doc.Find(".key.list .print.meta") + + syncedKeys := make([]string, divs.Length()) + for i := 0; i < divs.Length(); i++ { + syncedKeys[i] = strings.TrimSpace(divs.Eq(i).Text()) + } + + assert.ElementsMatch(t, u.SSHKeys, syncedKeys) + } +} diff --git a/models/user.go b/models/user.go index 5a2e86eb9d51a..b2320c764a5d2 100644 --- a/models/user.go +++ b/models/user.go @@ -1357,47 +1357,57 @@ func GetWatchedRepos(userID int64, private bool) ([]*Repository, error) { } // deleteKeysMarkedForDeletion returns true if ssh keys needs update -func deleteKeysMarkedForDeletion(keys []string) (sshKeysNeedUpdate bool, err error) { +func deleteKeysMarkedForDeletion(keys []string) (bool, error) { // Start session sess := x.NewSession() defer sess.Close() - if err = sess.Begin(); err != nil { + if err := sess.Begin(); err != nil { return false, err } // Delete keys marked for deletion + var sshKeysNeedUpdate bool for _, KeyToDelete := range keys { key, err := SearchPublicKeyByContent(KeyToDelete) - err = deletePublicKeys(sess, key.ID) if err != nil { - sshKeysNeedUpdate = false - log.Trace("deleteKeysMarkedForDeletion: Error: %v", key) + log.Error(4, "SearchPublicKeyByContent: %v", err) + continue + } + if err = deletePublicKeys(sess, key.ID); err != nil { + log.Error(4, "deletePublicKeys: %v", err) + continue } sshKeysNeedUpdate = true } - return sshKeysNeedUpdate, sess.Commit() + if err := sess.Commit(); err != nil { + return false, err + } + + return sshKeysNeedUpdate, nil } -func addLdapSSHPublicKeys(s *LoginSource, usr *User, SSHPublicKeys []string) (sshKeysNeedUpdate bool, err error) { - for _, LDAPPublicSSHKey := range SSHPublicKeys { - if strings.HasPrefix(strings.ToLower(LDAPPublicSSHKey), "ssh") { - LDAPPublicSSHKeyName := strings.Join([]string{s.Name, LDAPPublicSSHKey[0:40]}, "-") - _, err := AddPublicKey(usr.ID, LDAPPublicSSHKeyName, LDAPPublicSSHKey, s.ID) - if err != nil { +func addLdapSSHPublicKeys(s *LoginSource, usr *User, SSHPublicKeys []string) bool { + var sshKeysNeedUpdate bool + for _, sshKey := range SSHPublicKeys { + if strings.HasPrefix(strings.ToLower(sshKey), "ssh") { + sshKeyName := fmt.Sprintf("%s-%s", s.Name, sshKey[0:40]) + if _, err := AddPublicKey(usr.ID, sshKeyName, sshKey, s.ID); err != nil { log.Error(4, "addLdapSSHPublicKeys[%s]: Error adding LDAP Public SSH Key for user %s: %v", s.Name, usr.Name, err) } else { - log.Trace("addLdapSSHPublicKeys[%s]: Added LDAP Public SSH Key for user %s: %v", s.Name, usr.Name, LDAPPublicSSHKey) + log.Trace("addLdapSSHPublicKeys[%s]: Added LDAP Public SSH Key for user %s", s.Name, usr.Name) sshKeysNeedUpdate = true } } else { - log.Error(3, "addLdapSSHPublicKeys[%s]: Skipping invalid LDAP Public SSH Key for user %s: %v", s.Name, usr.Name, LDAPPublicSSHKey) + log.Warn("addLdapSSHPublicKeys[%s]: Skipping invalid LDAP Public SSH Key for user %s: %v", s.Name, usr.Name, sshKey) } } - return sshKeysNeedUpdate, err + return sshKeysNeedUpdate } -func synchronizeLdapSSHPublicKeys(s *LoginSource, SSHPublicKeys []string, usr *User) (sshKeysNeedUpdate bool, err error) { +func synchronizeLdapSSHPublicKeys(s *LoginSource, SSHPublicKeys []string, usr *User) bool { + var sshKeysNeedUpdate bool + log.Trace("synchronizeLdapSSHPublicKeys[%s]: Handling LDAP Public SSH Key synchronization for user %s", s.Name, usr.Name) // Get Public Keys from DB with current LDAP source @@ -1421,40 +1431,42 @@ func synchronizeLdapSSHPublicKeys(s *LoginSource, SSHPublicKeys []string, usr *U } // Check if Public Key sync is needed - var giteaKeysToDelete []string if util.IsEqualSlice(giteaKeys, ldapKeys) { log.Trace("synchronizeLdapSSHPublicKeys[%s]: LDAP Public Keys are already in sync for %s (LDAP:%v/DB:%v)", s.Name, usr.Name, len(ldapKeys), len(giteaKeys)) - } else { - log.Trace("synchronizeLdapSSHPublicKeys[%s]: LDAP Public Key needs update for user %s (LDAP:%v/DB:%v)", s.Name, usr.Name, len(ldapKeys), len(giteaKeys)) + return false + } + log.Trace("synchronizeLdapSSHPublicKeys[%s]: LDAP Public Key needs update for user %s (LDAP:%v/DB:%v)", s.Name, usr.Name, len(ldapKeys), len(giteaKeys)) - // Add LDAP Public SSH Keys that doesn't already exist in DB - var newLdapSSHKeys []string - for _, LDAPPublicSSHKey := range ldapKeys { - if !util.ExistsInSlice(LDAPPublicSSHKey, giteaKeys) { - newLdapSSHKeys = append(newLdapSSHKeys, LDAPPublicSSHKey) - } - } - sshKeysNeedUpdate, err = addLdapSSHPublicKeys(s, usr, newLdapSSHKeys) - if err != nil { - log.Error(4, "synchronizeLdapSSHPublicKeys[%s]: Error updating LDAP Public SSH Keys for user %s: %v", s.Name, usr.Name, err) + // Add LDAP Public SSH Keys that doesn't already exist in DB + var newLdapSSHKeys []string + for _, LDAPPublicSSHKey := range ldapKeys { + if !util.ExistsInSlice(LDAPPublicSSHKey, giteaKeys) { + newLdapSSHKeys = append(newLdapSSHKeys, LDAPPublicSSHKey) } + } + if addLdapSSHPublicKeys(s, usr, newLdapSSHKeys) { + sshKeysNeedUpdate = true + } - // Mark LDAP keys from DB that doesn't exist in LDAP for deletion - for _, giteaKey := range giteaKeys { - if !util.ExistsInSlice(giteaKey, ldapKeys) { - log.Trace("synchronizeLdapSSHPublicKeys[%s]: Marking LDAP Public SSH Key for deletion for user %s: %v", s.Name, usr.Name, giteaKey) - giteaKeysToDelete = append(giteaKeysToDelete, giteaKey) - } + // Mark LDAP keys from DB that doesn't exist in LDAP for deletion + var giteaKeysToDelete []string + for _, giteaKey := range giteaKeys { + if !util.ExistsInSlice(giteaKey, ldapKeys) { + log.Trace("synchronizeLdapSSHPublicKeys[%s]: Marking LDAP Public SSH Key for deletion for user %s: %v", s.Name, usr.Name, giteaKey) + giteaKeysToDelete = append(giteaKeysToDelete, giteaKey) } } // Delete LDAP keys from DB that doesn't exist in LDAP - sshKeysNeedUpdate, err = deleteKeysMarkedForDeletion(giteaKeysToDelete) + needUpd, err := deleteKeysMarkedForDeletion(giteaKeysToDelete) if err != nil { log.Error(4, "synchronizeLdapSSHPublicKeys[%s]: Error deleting LDAP Public SSH Keys marked for deletion for user %s: %v", s.Name, usr.Name, err) } + if needUpd { + sshKeysNeedUpdate = true + } - return sshKeysNeedUpdate, err + return sshKeysNeedUpdate } // SyncExternalUsers is used to synchronize users with external authorization source @@ -1534,17 +1546,16 @@ func SyncExternalUsers() { log.Error(4, "SyncExternalUsers[%s]: Error creating user %s: %v", s.Name, su.Username, err) } else if isAttributeSSHPublicKeySet { log.Trace("SyncExternalUsers[%s]: Adding LDAP Public SSH Keys for user %s", s.Name, usr.Name) - sshKeysNeedUpdate, err = addLdapSSHPublicKeys(s, usr, su.SSHPublicKey) - if err != nil { - log.Error(4, "SyncExternalUsers[%s]: Error adding LDAP Public SSH Keys for user %s: %v", s.Name, su.Username, err) + if addLdapSSHPublicKeys(s, usr, su.SSHPublicKey) { + sshKeysNeedUpdate = true } } } else if updateExisting { existingUsers = append(existingUsers, usr.ID) // Synchronize SSH Public Key if that attribute is set - if isAttributeSSHPublicKeySet { - synchronizeLdapSSHPublicKeys(s, su.SSHPublicKey, usr) + if isAttributeSSHPublicKeySet && synchronizeLdapSSHPublicKeys(s, su.SSHPublicKey, usr) { + sshKeysNeedUpdate = true } // Check if user data has changed @@ -1572,7 +1583,7 @@ func SyncExternalUsers() { } // Rewrite authorized_keys file if LDAP Public SSH Key attribute is set and any key was added or removed - if isAttributeSSHPublicKeySet && sshKeysNeedUpdate { + if sshKeysNeedUpdate { RewriteAllPublicKeys() } diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 80ce711b9e790..c24c1ac96da7a 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1385,7 +1385,7 @@ auths.attribute_username_placeholder = Leave empty to use the username entered i auths.attribute_name = First Name Attribute auths.attribute_surname = Surname Attribute auths.attribute_mail = Email Attribute -auths.attribute_ssh_public_key = Public SSH key attribute +auths.attribute_ssh_public_key = Public SSH Key Attribute auths.attributes_in_bind = Fetch Attributes in Bind DN Context auths.use_paged_search = Use Paged Search auths.search_page_size = Page Size diff --git a/vendor/github.com/davecgh/go-spew/LICENSE b/vendor/github.com/davecgh/go-spew/LICENSE index bc52e96f2b0ea..c836416192da3 100644 --- a/vendor/github.com/davecgh/go-spew/LICENSE +++ b/vendor/github.com/davecgh/go-spew/LICENSE @@ -2,7 +2,7 @@ ISC License Copyright (c) 2012-2016 Dave Collins -Permission to use, copy, modify, and/or distribute this software for any +Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. diff --git a/vendor/github.com/davecgh/go-spew/spew/bypass.go b/vendor/github.com/davecgh/go-spew/spew/bypass.go index 7f166c3a3d95b..8a4a6589a2d42 100644 --- a/vendor/github.com/davecgh/go-spew/spew/bypass.go +++ b/vendor/github.com/davecgh/go-spew/spew/bypass.go @@ -41,9 +41,9 @@ var ( // after commit 82f48826c6c7 which changed the format again to mirror // the original format. Code in the init function updates these offsets // as necessary. - offsetPtr = ptrSize + offsetPtr = uintptr(ptrSize) offsetScalar = uintptr(0) - offsetFlag = ptrSize * 2 + offsetFlag = uintptr(ptrSize * 2) // flagKindWidth and flagKindShift indicate various bits that the // reflect package uses internally to track kind information. @@ -58,7 +58,7 @@ var ( // changed their positions. Code in the init function updates these // flags as necessary. flagKindWidth = uintptr(5) - flagKindShift = flagKindWidth - 1 + flagKindShift = uintptr(flagKindWidth - 1) flagRO = uintptr(1 << 0) flagIndir = uintptr(1 << 1) ) diff --git a/vendor/github.com/davecgh/go-spew/spew/common.go b/vendor/github.com/davecgh/go-spew/spew/common.go index 1be8ce9457612..7c519ff47ac3c 100644 --- a/vendor/github.com/davecgh/go-spew/spew/common.go +++ b/vendor/github.com/davecgh/go-spew/spew/common.go @@ -180,7 +180,7 @@ func printComplex(w io.Writer, c complex128, floatPrecision int) { w.Write(closeParenBytes) } -// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x' +// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x' // prefix to Writer w. func printHexPtr(w io.Writer, p uintptr) { // Null pointer. diff --git a/vendor/github.com/davecgh/go-spew/spew/dump.go b/vendor/github.com/davecgh/go-spew/spew/dump.go index f78d89fc1f6c4..df1d582a728ae 100644 --- a/vendor/github.com/davecgh/go-spew/spew/dump.go +++ b/vendor/github.com/davecgh/go-spew/spew/dump.go @@ -35,16 +35,16 @@ var ( // cCharRE is a regular expression that matches a cgo char. // It is used to detect character arrays to hexdump them. - cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`) + cCharRE = regexp.MustCompile("^.*\\._Ctype_char$") // cUnsignedCharRE is a regular expression that matches a cgo unsigned // char. It is used to detect unsigned character arrays to hexdump // them. - cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`) + cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$") // cUint8tCharRE is a regular expression that matches a cgo uint8_t. // It is used to detect uint8_t arrays to hexdump them. - cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`) + cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$") ) // dumpState contains information about the state of a dump operation. @@ -143,10 +143,10 @@ func (d *dumpState) dumpPtr(v reflect.Value) { // Display dereferenced value. d.w.Write(openParenBytes) switch { - case nilFound: + case nilFound == true: d.w.Write(nilAngleBytes) - case cycleFound: + case cycleFound == true: d.w.Write(circularBytes) default: diff --git a/vendor/github.com/davecgh/go-spew/spew/format.go b/vendor/github.com/davecgh/go-spew/spew/format.go index b04edb7d7ac27..c49875bacbb88 100644 --- a/vendor/github.com/davecgh/go-spew/spew/format.go +++ b/vendor/github.com/davecgh/go-spew/spew/format.go @@ -182,10 +182,10 @@ func (f *formatState) formatPtr(v reflect.Value) { // Display dereferenced value. switch { - case nilFound: + case nilFound == true: f.fs.Write(nilAngleBytes) - case cycleFound: + case cycleFound == true: f.fs.Write(circularShortBytes) default: diff --git a/vendor/github.com/stretchr/testify/LICENCE.txt b/vendor/github.com/stretchr/testify/LICENCE.txt deleted file mode 100644 index 473b670a7c619..0000000000000 --- a/vendor/github.com/stretchr/testify/LICENCE.txt +++ /dev/null @@ -1,22 +0,0 @@ -Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell - -Please consider promoting this project if you find it useful. - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of the Software, -and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT -OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE -OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/stretchr/testify/assert/assertion_format.go b/vendor/github.com/stretchr/testify/assert/assertion_format.go index 23838c4ceea79..ae06a54e20a7d 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_format.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_format.go @@ -22,18 +22,28 @@ func Conditionf(t TestingT, comp Comparison, msg string, args ...interface{}) bo // assert.Containsf(t, "Hello World", "World", "error message %s", "formatted") // assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted") // assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool { return Contains(t, s, contains, append([]interface{}{msg}, args...)...) } +// DirExistsf checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists. +func DirExistsf(t TestingT, path string, msg string, args ...interface{}) bool { + return DirExists(t, path, append([]interface{}{msg}, args...)...) +} + +// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// assert.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") +func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) bool { + return ElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...) +} + // Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either // a slice or a channel with len == 0. // // assert.Emptyf(t, obj, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool { return Empty(t, object, append([]interface{}{msg}, args...)...) } @@ -42,8 +52,6 @@ func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) boo // // assert.Equalf(t, 123, 123, "error message %s", "formatted") // -// Returns whether the assertion was successful (true) or not (false). -// // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). Function equality // cannot be determined and will always fail. @@ -56,8 +64,6 @@ func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, ar // // actualObj, err := SomeFunction() // assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) bool { return EqualError(t, theError, errString, append([]interface{}{msg}, args...)...) } @@ -66,8 +72,6 @@ func EqualErrorf(t TestingT, theError error, errString string, msg string, args // and equal. // // assert.EqualValuesf(t, uint32(123, "error message %s", "formatted"), int32(123)) -// -// Returns whether the assertion was successful (true) or not (false). func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { return EqualValues(t, expected, actual, append([]interface{}{msg}, args...)...) } @@ -78,17 +82,13 @@ func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg stri // if assert.Errorf(t, err, "error message %s", "formatted") { // assert.Equal(t, expectedErrorf, err) // } -// -// Returns whether the assertion was successful (true) or not (false). func Errorf(t TestingT, err error, msg string, args ...interface{}) bool { return Error(t, err, append([]interface{}{msg}, args...)...) } -// Exactlyf asserts that two objects are equal is value and type. +// Exactlyf asserts that two objects are equal in value and type. // // assert.Exactlyf(t, int32(123, "error message %s", "formatted"), int64(123)) -// -// Returns whether the assertion was successful (true) or not (false). func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { return Exactly(t, expected, actual, append([]interface{}{msg}, args...)...) } @@ -106,20 +106,23 @@ func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{} // Falsef asserts that the specified value is false. // // assert.Falsef(t, myBool, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func Falsef(t TestingT, value bool, msg string, args ...interface{}) bool { return False(t, value, append([]interface{}{msg}, args...)...) } +// FileExistsf checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file. +func FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool { + return FileExists(t, path, append([]interface{}{msg}, args...)...) +} + // HTTPBodyContainsf asserts that a specified handler returns a // body that contains a string. // // assert.HTTPBodyContainsf(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). -func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool { - return HTTPBodyContains(t, handler, method, url, values, str) +func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool { + return HTTPBodyContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...) } // HTTPBodyNotContainsf asserts that a specified handler returns a @@ -128,8 +131,8 @@ func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url // assert.HTTPBodyNotContainsf(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). -func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool { - return HTTPBodyNotContains(t, handler, method, url, values, str) +func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool { + return HTTPBodyNotContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...) } // HTTPErrorf asserts that a specified handler returns an error status code. @@ -137,8 +140,8 @@ func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, u // assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). -func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPError(t, handler, method, url, values) +func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + return HTTPError(t, handler, method, url, values, append([]interface{}{msg}, args...)...) } // HTTPRedirectf asserts that a specified handler returns a redirect status code. @@ -146,8 +149,8 @@ func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, // assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). -func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPRedirect(t, handler, method, url, values) +func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + return HTTPRedirect(t, handler, method, url, values, append([]interface{}{msg}, args...)...) } // HTTPSuccessf asserts that a specified handler returns a success status code. @@ -155,8 +158,8 @@ func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url stri // assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). -func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPSuccess(t, handler, method, url, values) +func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + return HTTPSuccess(t, handler, method, url, values, append([]interface{}{msg}, args...)...) } // Implementsf asserts that an object is implemented by the specified interface. @@ -169,20 +172,21 @@ func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, ms // InDeltaf asserts that the two numerals are within delta of each other. // // assert.InDeltaf(t, math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01) -// -// Returns whether the assertion was successful (true) or not (false). func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { return InDelta(t, expected, actual, delta, append([]interface{}{msg}, args...)...) } +// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func InDeltaMapValuesf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { + return InDeltaMapValues(t, expected, actual, delta, append([]interface{}{msg}, args...)...) +} + // InDeltaSlicef is the same as InDelta, except it compares two slices. func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { return InDeltaSlice(t, expected, actual, delta, append([]interface{}{msg}, args...)...) } // InEpsilonf asserts that expected and actual have a relative error less than epsilon -// -// Returns whether the assertion was successful (true) or not (false). func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool { return InEpsilon(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...) } @@ -200,8 +204,6 @@ func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg strin // JSONEqf asserts that two JSON strings are equivalent. // // assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool { return JSONEq(t, expected, actual, append([]interface{}{msg}, args...)...) } @@ -210,8 +212,6 @@ func JSONEqf(t TestingT, expected string, actual string, msg string, args ...int // Lenf also fails if the object has a type that len() not accept. // // assert.Lenf(t, mySlice, 3, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) bool { return Len(t, object, length, append([]interface{}{msg}, args...)...) } @@ -219,8 +219,6 @@ func Lenf(t TestingT, object interface{}, length int, msg string, args ...interf // Nilf asserts that the specified object is nil. // // assert.Nilf(t, err, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool { return Nil(t, object, append([]interface{}{msg}, args...)...) } @@ -231,8 +229,6 @@ func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool // if assert.NoErrorf(t, err, "error message %s", "formatted") { // assert.Equal(t, expectedObj, actualObj) // } -// -// Returns whether the assertion was successful (true) or not (false). func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool { return NoError(t, err, append([]interface{}{msg}, args...)...) } @@ -243,8 +239,6 @@ func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool { // assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted") // assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted") // assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool { return NotContains(t, s, contains, append([]interface{}{msg}, args...)...) } @@ -255,8 +249,6 @@ func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, a // if assert.NotEmptyf(t, obj, "error message %s", "formatted") { // assert.Equal(t, "two", obj[1]) // } -// -// Returns whether the assertion was successful (true) or not (false). func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool { return NotEmpty(t, object, append([]interface{}{msg}, args...)...) } @@ -265,8 +257,6 @@ func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) // // assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted") // -// Returns whether the assertion was successful (true) or not (false). -// // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { @@ -276,8 +266,6 @@ func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, // NotNilf asserts that the specified object is not nil. // // assert.NotNilf(t, err, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) bool { return NotNil(t, object, append([]interface{}{msg}, args...)...) } @@ -285,8 +273,6 @@ func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) bo // NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. // // assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool { return NotPanics(t, f, append([]interface{}{msg}, args...)...) } @@ -295,8 +281,6 @@ func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bo // // assert.NotRegexpf(t, regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting") // assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool { return NotRegexp(t, rx, str, append([]interface{}{msg}, args...)...) } @@ -305,13 +289,11 @@ func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args .. // elements given in the specified subset(array, slice...). // // assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool { return NotSubset(t, list, subset, append([]interface{}{msg}, args...)...) } -// NotZerof asserts that i is not the zero value for its type and returns the truth. +// NotZerof asserts that i is not the zero value for its type. func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) bool { return NotZero(t, i, append([]interface{}{msg}, args...)...) } @@ -319,8 +301,6 @@ func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) bool { // Panicsf asserts that the code inside the specified PanicTestFunc panics. // // assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool { return Panics(t, f, append([]interface{}{msg}, args...)...) } @@ -329,8 +309,6 @@ func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool // the recovered panic value equals the expected panic value. // // assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool { return PanicsWithValue(t, expected, f, append([]interface{}{msg}, args...)...) } @@ -339,8 +317,6 @@ func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg str // // assert.Regexpf(t, regexp.MustCompile("start", "error message %s", "formatted"), "it's starting") // assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool { return Regexp(t, rx, str, append([]interface{}{msg}, args...)...) } @@ -349,8 +325,6 @@ func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...in // elements given in the specified subset(array, slice...). // // assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool { return Subset(t, list, subset, append([]interface{}{msg}, args...)...) } @@ -358,8 +332,6 @@ func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args // Truef asserts that the specified value is true. // // assert.Truef(t, myBool, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func Truef(t TestingT, value bool, msg string, args ...interface{}) bool { return True(t, value, append([]interface{}{msg}, args...)...) } @@ -367,13 +339,11 @@ func Truef(t TestingT, value bool, msg string, args ...interface{}) bool { // WithinDurationf asserts that the two times are within duration delta of each other. // // assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool { return WithinDuration(t, expected, actual, delta, append([]interface{}{msg}, args...)...) } -// Zerof asserts that i is the zero value for its type and returns the truth. +// Zerof asserts that i is the zero value for its type. func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) bool { return Zero(t, i, append([]interface{}{msg}, args...)...) } diff --git a/vendor/github.com/stretchr/testify/assert/assertion_forward.go b/vendor/github.com/stretchr/testify/assert/assertion_forward.go index fcccbd01c8da5..ffa5428f34d91 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_forward.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_forward.go @@ -27,8 +27,6 @@ func (a *Assertions) Conditionf(comp Comparison, msg string, args ...interface{} // a.Contains("Hello World", "World") // a.Contains(["Hello", "World"], "World") // a.Contains({"Hello": "World"}, "Hello") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool { return Contains(a.t, s, contains, msgAndArgs...) } @@ -39,18 +37,42 @@ func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs .. // a.Containsf("Hello World", "World", "error message %s", "formatted") // a.Containsf(["Hello", "World"], "World", "error message %s", "formatted") // a.Containsf({"Hello": "World"}, "Hello", "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Containsf(s interface{}, contains interface{}, msg string, args ...interface{}) bool { return Containsf(a.t, s, contains, msg, args...) } +// DirExists checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists. +func (a *Assertions) DirExists(path string, msgAndArgs ...interface{}) bool { + return DirExists(a.t, path, msgAndArgs...) +} + +// DirExistsf checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists. +func (a *Assertions) DirExistsf(path string, msg string, args ...interface{}) bool { + return DirExistsf(a.t, path, msg, args...) +} + +// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// a.ElementsMatch([1, 3, 2, 3], [1, 3, 3, 2]) +func (a *Assertions) ElementsMatch(listA interface{}, listB interface{}, msgAndArgs ...interface{}) bool { + return ElementsMatch(a.t, listA, listB, msgAndArgs...) +} + +// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// a.ElementsMatchf([1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") +func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg string, args ...interface{}) bool { + return ElementsMatchf(a.t, listA, listB, msg, args...) +} + // Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either // a slice or a channel with len == 0. // // a.Empty(obj) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool { return Empty(a.t, object, msgAndArgs...) } @@ -59,8 +81,6 @@ func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool { // a slice or a channel with len == 0. // // a.Emptyf(obj, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) bool { return Emptyf(a.t, object, msg, args...) } @@ -69,8 +89,6 @@ func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) // // a.Equal(123, 123) // -// Returns whether the assertion was successful (true) or not (false). -// // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). Function equality // cannot be determined and will always fail. @@ -83,8 +101,6 @@ func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs // // actualObj, err := SomeFunction() // a.EqualError(err, expectedErrorString) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) bool { return EqualError(a.t, theError, errString, msgAndArgs...) } @@ -94,8 +110,6 @@ func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ... // // actualObj, err := SomeFunction() // a.EqualErrorf(err, expectedErrorString, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) EqualErrorf(theError error, errString string, msg string, args ...interface{}) bool { return EqualErrorf(a.t, theError, errString, msg, args...) } @@ -104,8 +118,6 @@ func (a *Assertions) EqualErrorf(theError error, errString string, msg string, a // and equal. // // a.EqualValues(uint32(123), int32(123)) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { return EqualValues(a.t, expected, actual, msgAndArgs...) } @@ -114,8 +126,6 @@ func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAn // and equal. // // a.EqualValuesf(uint32(123, "error message %s", "formatted"), int32(123)) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { return EqualValuesf(a.t, expected, actual, msg, args...) } @@ -124,8 +134,6 @@ func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg // // a.Equalf(123, 123, "error message %s", "formatted") // -// Returns whether the assertion was successful (true) or not (false). -// // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). Function equality // cannot be determined and will always fail. @@ -139,8 +147,6 @@ func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string // if a.Error(err) { // assert.Equal(t, expectedError, err) // } -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool { return Error(a.t, err, msgAndArgs...) } @@ -151,26 +157,20 @@ func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool { // if a.Errorf(err, "error message %s", "formatted") { // assert.Equal(t, expectedErrorf, err) // } -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Errorf(err error, msg string, args ...interface{}) bool { return Errorf(a.t, err, msg, args...) } -// Exactly asserts that two objects are equal is value and type. +// Exactly asserts that two objects are equal in value and type. // // a.Exactly(int32(123), int64(123)) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { return Exactly(a.t, expected, actual, msgAndArgs...) } -// Exactlyf asserts that two objects are equal is value and type. +// Exactlyf asserts that two objects are equal in value and type. // // a.Exactlyf(int32(123, "error message %s", "formatted"), int64(123)) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Exactlyf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { return Exactlyf(a.t, expected, actual, msg, args...) } @@ -198,8 +198,6 @@ func (a *Assertions) Failf(failureMessage string, msg string, args ...interface{ // False asserts that the specified value is false. // // a.False(myBool) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) False(value bool, msgAndArgs ...interface{}) bool { return False(a.t, value, msgAndArgs...) } @@ -207,20 +205,28 @@ func (a *Assertions) False(value bool, msgAndArgs ...interface{}) bool { // Falsef asserts that the specified value is false. // // a.Falsef(myBool, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Falsef(value bool, msg string, args ...interface{}) bool { return Falsef(a.t, value, msg, args...) } +// FileExists checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file. +func (a *Assertions) FileExists(path string, msgAndArgs ...interface{}) bool { + return FileExists(a.t, path, msgAndArgs...) +} + +// FileExistsf checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file. +func (a *Assertions) FileExistsf(path string, msg string, args ...interface{}) bool { + return FileExistsf(a.t, path, msg, args...) +} + // HTTPBodyContains asserts that a specified handler returns a // body that contains a string. // // a.HTTPBodyContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky") // // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool { - return HTTPBodyContains(a.t, handler, method, url, values, str) +func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { + return HTTPBodyContains(a.t, handler, method, url, values, str, msgAndArgs...) } // HTTPBodyContainsf asserts that a specified handler returns a @@ -229,8 +235,8 @@ func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, u // a.HTTPBodyContainsf(myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool { - return HTTPBodyContainsf(a.t, handler, method, url, values, str) +func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool { + return HTTPBodyContainsf(a.t, handler, method, url, values, str, msg, args...) } // HTTPBodyNotContains asserts that a specified handler returns a @@ -239,8 +245,8 @@ func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, // a.HTTPBodyNotContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky") // // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool { - return HTTPBodyNotContains(a.t, handler, method, url, values, str) +func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { + return HTTPBodyNotContains(a.t, handler, method, url, values, str, msgAndArgs...) } // HTTPBodyNotContainsf asserts that a specified handler returns a @@ -249,8 +255,8 @@ func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string // a.HTTPBodyNotContainsf(myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool { - return HTTPBodyNotContainsf(a.t, handler, method, url, values, str) +func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool { + return HTTPBodyNotContainsf(a.t, handler, method, url, values, str, msg, args...) } // HTTPError asserts that a specified handler returns an error status code. @@ -258,8 +264,8 @@ func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method strin // a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPError(a.t, handler, method, url, values) +func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool { + return HTTPError(a.t, handler, method, url, values, msgAndArgs...) } // HTTPErrorf asserts that a specified handler returns an error status code. @@ -267,8 +273,8 @@ func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url stri // a.HTTPErrorf(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). -func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPErrorf(a.t, handler, method, url, values) +func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + return HTTPErrorf(a.t, handler, method, url, values, msg, args...) } // HTTPRedirect asserts that a specified handler returns a redirect status code. @@ -276,8 +282,8 @@ func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url str // a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPRedirect(a.t, handler, method, url, values) +func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool { + return HTTPRedirect(a.t, handler, method, url, values, msgAndArgs...) } // HTTPRedirectf asserts that a specified handler returns a redirect status code. @@ -285,8 +291,8 @@ func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url s // a.HTTPRedirectf(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). -func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPRedirectf(a.t, handler, method, url, values) +func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + return HTTPRedirectf(a.t, handler, method, url, values, msg, args...) } // HTTPSuccess asserts that a specified handler returns a success status code. @@ -294,8 +300,8 @@ func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url // a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil) // // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPSuccess(a.t, handler, method, url, values) +func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool { + return HTTPSuccess(a.t, handler, method, url, values, msgAndArgs...) } // HTTPSuccessf asserts that a specified handler returns a success status code. @@ -303,8 +309,8 @@ func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url st // a.HTTPSuccessf(myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPSuccessf(a.t, handler, method, url, values) +func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + return HTTPSuccessf(a.t, handler, method, url, values, msg, args...) } // Implements asserts that an object is implemented by the specified interface. @@ -324,12 +330,20 @@ func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{} // InDelta asserts that the two numerals are within delta of each other. // // a.InDelta(math.Pi, (22 / 7.0), 0.01) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { return InDelta(a.t, expected, actual, delta, msgAndArgs...) } +// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func (a *Assertions) InDeltaMapValues(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + return InDeltaMapValues(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func (a *Assertions) InDeltaMapValuesf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { + return InDeltaMapValuesf(a.t, expected, actual, delta, msg, args...) +} + // InDeltaSlice is the same as InDelta, except it compares two slices. func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { return InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...) @@ -343,15 +357,11 @@ func (a *Assertions) InDeltaSlicef(expected interface{}, actual interface{}, del // InDeltaf asserts that the two numerals are within delta of each other. // // a.InDeltaf(math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) InDeltaf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { return InDeltaf(a.t, expected, actual, delta, msg, args...) } // InEpsilon asserts that expected and actual have a relative error less than epsilon -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { return InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...) } @@ -367,8 +377,6 @@ func (a *Assertions) InEpsilonSlicef(expected interface{}, actual interface{}, e } // InEpsilonf asserts that expected and actual have a relative error less than epsilon -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool { return InEpsilonf(a.t, expected, actual, epsilon, msg, args...) } @@ -386,8 +394,6 @@ func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg s // JSONEq asserts that two JSON strings are equivalent. // // a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) bool { return JSONEq(a.t, expected, actual, msgAndArgs...) } @@ -395,8 +401,6 @@ func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interf // JSONEqf asserts that two JSON strings are equivalent. // // a.JSONEqf(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) JSONEqf(expected string, actual string, msg string, args ...interface{}) bool { return JSONEqf(a.t, expected, actual, msg, args...) } @@ -405,8 +409,6 @@ func (a *Assertions) JSONEqf(expected string, actual string, msg string, args .. // Len also fails if the object has a type that len() not accept. // // a.Len(mySlice, 3) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) bool { return Len(a.t, object, length, msgAndArgs...) } @@ -415,8 +417,6 @@ func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface // Lenf also fails if the object has a type that len() not accept. // // a.Lenf(mySlice, 3, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...interface{}) bool { return Lenf(a.t, object, length, msg, args...) } @@ -424,8 +424,6 @@ func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...in // Nil asserts that the specified object is nil. // // a.Nil(err) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) bool { return Nil(a.t, object, msgAndArgs...) } @@ -433,8 +431,6 @@ func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) bool { // Nilf asserts that the specified object is nil. // // a.Nilf(err, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Nilf(object interface{}, msg string, args ...interface{}) bool { return Nilf(a.t, object, msg, args...) } @@ -445,8 +441,6 @@ func (a *Assertions) Nilf(object interface{}, msg string, args ...interface{}) b // if a.NoError(err) { // assert.Equal(t, expectedObj, actualObj) // } -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) bool { return NoError(a.t, err, msgAndArgs...) } @@ -457,8 +451,6 @@ func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) bool { // if a.NoErrorf(err, "error message %s", "formatted") { // assert.Equal(t, expectedObj, actualObj) // } -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) bool { return NoErrorf(a.t, err, msg, args...) } @@ -469,8 +461,6 @@ func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) bool { // a.NotContains("Hello World", "Earth") // a.NotContains(["Hello", "World"], "Earth") // a.NotContains({"Hello": "World"}, "Earth") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool { return NotContains(a.t, s, contains, msgAndArgs...) } @@ -481,8 +471,6 @@ func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs // a.NotContainsf("Hello World", "Earth", "error message %s", "formatted") // a.NotContainsf(["Hello", "World"], "Earth", "error message %s", "formatted") // a.NotContainsf({"Hello": "World"}, "Earth", "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg string, args ...interface{}) bool { return NotContainsf(a.t, s, contains, msg, args...) } @@ -493,8 +481,6 @@ func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg strin // if a.NotEmpty(obj) { // assert.Equal(t, "two", obj[1]) // } -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) bool { return NotEmpty(a.t, object, msgAndArgs...) } @@ -505,8 +491,6 @@ func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) boo // if a.NotEmptyf(obj, "error message %s", "formatted") { // assert.Equal(t, "two", obj[1]) // } -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface{}) bool { return NotEmptyf(a.t, object, msg, args...) } @@ -515,8 +499,6 @@ func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface // // a.NotEqual(obj1, obj2) // -// Returns whether the assertion was successful (true) or not (false). -// // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { @@ -527,8 +509,6 @@ func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndAr // // a.NotEqualf(obj1, obj2, "error message %s", "formatted") // -// Returns whether the assertion was successful (true) or not (false). -// // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { @@ -538,8 +518,6 @@ func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg str // NotNil asserts that the specified object is not nil. // // a.NotNil(err) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) bool { return NotNil(a.t, object, msgAndArgs...) } @@ -547,8 +525,6 @@ func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) bool // NotNilf asserts that the specified object is not nil. // // a.NotNilf(err, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotNilf(object interface{}, msg string, args ...interface{}) bool { return NotNilf(a.t, object, msg, args...) } @@ -556,8 +532,6 @@ func (a *Assertions) NotNilf(object interface{}, msg string, args ...interface{} // NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. // // a.NotPanics(func(){ RemainCalm() }) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotPanics(f PanicTestFunc, msgAndArgs ...interface{}) bool { return NotPanics(a.t, f, msgAndArgs...) } @@ -565,8 +539,6 @@ func (a *Assertions) NotPanics(f PanicTestFunc, msgAndArgs ...interface{}) bool // NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. // // a.NotPanicsf(func(){ RemainCalm() }, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotPanicsf(f PanicTestFunc, msg string, args ...interface{}) bool { return NotPanicsf(a.t, f, msg, args...) } @@ -575,8 +547,6 @@ func (a *Assertions) NotPanicsf(f PanicTestFunc, msg string, args ...interface{} // // a.NotRegexp(regexp.MustCompile("starts"), "it's starting") // a.NotRegexp("^start", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { return NotRegexp(a.t, rx, str, msgAndArgs...) } @@ -585,8 +555,6 @@ func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...in // // a.NotRegexpf(regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting") // a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool { return NotRegexpf(a.t, rx, str, msg, args...) } @@ -595,8 +563,6 @@ func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, arg // elements given in the specified subset(array, slice...). // // a.NotSubset([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool { return NotSubset(a.t, list, subset, msgAndArgs...) } @@ -605,18 +571,16 @@ func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs // elements given in the specified subset(array, slice...). // // a.NotSubsetf([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool { return NotSubsetf(a.t, list, subset, msg, args...) } -// NotZero asserts that i is not the zero value for its type and returns the truth. +// NotZero asserts that i is not the zero value for its type. func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) bool { return NotZero(a.t, i, msgAndArgs...) } -// NotZerof asserts that i is not the zero value for its type and returns the truth. +// NotZerof asserts that i is not the zero value for its type. func (a *Assertions) NotZerof(i interface{}, msg string, args ...interface{}) bool { return NotZerof(a.t, i, msg, args...) } @@ -624,8 +588,6 @@ func (a *Assertions) NotZerof(i interface{}, msg string, args ...interface{}) bo // Panics asserts that the code inside the specified PanicTestFunc panics. // // a.Panics(func(){ GoCrazy() }) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Panics(f PanicTestFunc, msgAndArgs ...interface{}) bool { return Panics(a.t, f, msgAndArgs...) } @@ -634,8 +596,6 @@ func (a *Assertions) Panics(f PanicTestFunc, msgAndArgs ...interface{}) bool { // the recovered panic value equals the expected panic value. // // a.PanicsWithValue("crazy error", func(){ GoCrazy() }) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) PanicsWithValue(expected interface{}, f PanicTestFunc, msgAndArgs ...interface{}) bool { return PanicsWithValue(a.t, expected, f, msgAndArgs...) } @@ -644,8 +604,6 @@ func (a *Assertions) PanicsWithValue(expected interface{}, f PanicTestFunc, msgA // the recovered panic value equals the expected panic value. // // a.PanicsWithValuef("crazy error", func(){ GoCrazy() }, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) PanicsWithValuef(expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool { return PanicsWithValuef(a.t, expected, f, msg, args...) } @@ -653,8 +611,6 @@ func (a *Assertions) PanicsWithValuef(expected interface{}, f PanicTestFunc, msg // Panicsf asserts that the code inside the specified PanicTestFunc panics. // // a.Panicsf(func(){ GoCrazy() }, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Panicsf(f PanicTestFunc, msg string, args ...interface{}) bool { return Panicsf(a.t, f, msg, args...) } @@ -663,8 +619,6 @@ func (a *Assertions) Panicsf(f PanicTestFunc, msg string, args ...interface{}) b // // a.Regexp(regexp.MustCompile("start"), "it's starting") // a.Regexp("start...$", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { return Regexp(a.t, rx, str, msgAndArgs...) } @@ -673,8 +627,6 @@ func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...inter // // a.Regexpf(regexp.MustCompile("start", "error message %s", "formatted"), "it's starting") // a.Regexpf("start...$", "it's not starting", "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool { return Regexpf(a.t, rx, str, msg, args...) } @@ -683,8 +635,6 @@ func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args . // elements given in the specified subset(array, slice...). // // a.Subset([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool { return Subset(a.t, list, subset, msgAndArgs...) } @@ -693,8 +643,6 @@ func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ... // elements given in the specified subset(array, slice...). // // a.Subsetf([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool { return Subsetf(a.t, list, subset, msg, args...) } @@ -702,8 +650,6 @@ func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, a // True asserts that the specified value is true. // // a.True(myBool) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) True(value bool, msgAndArgs ...interface{}) bool { return True(a.t, value, msgAndArgs...) } @@ -711,8 +657,6 @@ func (a *Assertions) True(value bool, msgAndArgs ...interface{}) bool { // Truef asserts that the specified value is true. // // a.Truef(myBool, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Truef(value bool, msg string, args ...interface{}) bool { return Truef(a.t, value, msg, args...) } @@ -720,8 +664,6 @@ func (a *Assertions) Truef(value bool, msg string, args ...interface{}) bool { // WithinDuration asserts that the two times are within duration delta of each other. // // a.WithinDuration(time.Now(), time.Now(), 10*time.Second) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool { return WithinDuration(a.t, expected, actual, delta, msgAndArgs...) } @@ -729,18 +671,16 @@ func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta // WithinDurationf asserts that the two times are within duration delta of each other. // // a.WithinDurationf(time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool { return WithinDurationf(a.t, expected, actual, delta, msg, args...) } -// Zero asserts that i is the zero value for its type and returns the truth. +// Zero asserts that i is the zero value for its type. func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) bool { return Zero(a.t, i, msgAndArgs...) } -// Zerof asserts that i is the zero value for its type and returns the truth. +// Zerof asserts that i is the zero value for its type. func (a *Assertions) Zerof(i interface{}, msg string, args ...interface{}) bool { return Zerof(a.t, i, msg, args...) } diff --git a/vendor/github.com/stretchr/testify/assert/assertions.go b/vendor/github.com/stretchr/testify/assert/assertions.go index 82590507abfdd..47bda77866664 100644 --- a/vendor/github.com/stretchr/testify/assert/assertions.go +++ b/vendor/github.com/stretchr/testify/assert/assertions.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "math" + "os" "reflect" "regexp" "runtime" @@ -231,6 +232,13 @@ func Fail(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool { {"Error", failureMessage}, } + // Add test name if the Go version supports it + if n, ok := t.(interface { + Name() string + }); ok { + content = append(content, labeledContent{"Test", n.Name()}) + } + message := messageFromMsgAndArgs(msgAndArgs...) if len(message) > 0 { content = append(content, labeledContent{"Messages", message}) @@ -273,15 +281,16 @@ func labeledOutput(content ...labeledContent) string { // // assert.Implements(t, (*MyInterface)(nil), new(MyObject)) func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool { - interfaceType := reflect.TypeOf(interfaceObject).Elem() + if object == nil { + return Fail(t, fmt.Sprintf("Cannot check if nil implements %v", interfaceType), msgAndArgs...) + } if !reflect.TypeOf(object).Implements(interfaceType) { return Fail(t, fmt.Sprintf("%T must implement %v", object, interfaceType), msgAndArgs...) } return true - } // IsType asserts that the specified objects are of the same type. @@ -298,8 +307,6 @@ func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs // // assert.Equal(t, 123, 123) // -// Returns whether the assertion was successful (true) or not (false). -// // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). Function equality // cannot be determined and will always fail. @@ -314,7 +321,7 @@ func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) expected, actual = formatUnequalValues(expected, actual) return Fail(t, fmt.Sprintf("Not equal: \n"+ "expected: %s\n"+ - "actual: %s%s", expected, actual, diff), msgAndArgs...) + "actual : %s%s", expected, actual, diff), msgAndArgs...) } return true @@ -341,8 +348,6 @@ func formatUnequalValues(expected, actual interface{}) (e string, a string) { // and equal. // // assert.EqualValues(t, uint32(123), int32(123)) -// -// Returns whether the assertion was successful (true) or not (false). func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { if !ObjectsAreEqualValues(expected, actual) { @@ -350,18 +355,16 @@ func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interfa expected, actual = formatUnequalValues(expected, actual) return Fail(t, fmt.Sprintf("Not equal: \n"+ "expected: %s\n"+ - "actual: %s%s", expected, actual, diff), msgAndArgs...) + "actual : %s%s", expected, actual, diff), msgAndArgs...) } return true } -// Exactly asserts that two objects are equal is value and type. +// Exactly asserts that two objects are equal in value and type. // // assert.Exactly(t, int32(123), int64(123)) -// -// Returns whether the assertion was successful (true) or not (false). func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { aType := reflect.TypeOf(expected) @@ -378,8 +381,6 @@ func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{} // NotNil asserts that the specified object is not nil. // // assert.NotNil(t, err) -// -// Returns whether the assertion was successful (true) or not (false). func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { if !isNil(object) { return true @@ -405,8 +406,6 @@ func isNil(object interface{}) bool { // Nil asserts that the specified object is nil. // // assert.Nil(t, err) -// -// Returns whether the assertion was successful (true) or not (false). func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { if isNil(object) { return true @@ -414,72 +413,38 @@ func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { return Fail(t, fmt.Sprintf("Expected nil, but got: %#v", object), msgAndArgs...) } -var numericZeros = []interface{}{ - int(0), - int8(0), - int16(0), - int32(0), - int64(0), - uint(0), - uint8(0), - uint16(0), - uint32(0), - uint64(0), - float32(0), - float64(0), -} - // isEmpty gets whether the specified object is considered empty or not. func isEmpty(object interface{}) bool { + // get nil case out of the way if object == nil { return true - } else if object == "" { - return true - } else if object == false { - return true - } - - for _, v := range numericZeros { - if object == v { - return true - } } objValue := reflect.ValueOf(object) switch objValue.Kind() { - case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String: - { - return (objValue.Len() == 0) - } - case reflect.Struct: - switch object.(type) { - case time.Time: - return object.(time.Time).IsZero() - } + // collection types are empty when they have no element + case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: + return objValue.Len() == 0 + // pointers are empty if nil or if the value they point to is empty case reflect.Ptr: - { - if objValue.IsNil() { - return true - } - switch object.(type) { - case *time.Time: - return object.(*time.Time).IsZero() - default: - return false - } + if objValue.IsNil() { + return true } + deref := objValue.Elem().Interface() + return isEmpty(deref) + // for all other types, compare against the zero value + default: + zero := reflect.Zero(objValue.Type()) + return reflect.DeepEqual(object, zero.Interface()) } - return false } // Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either // a slice or a channel with len == 0. // // assert.Empty(t, obj) -// -// Returns whether the assertion was successful (true) or not (false). func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { pass := isEmpty(object) @@ -497,8 +462,6 @@ func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { // if assert.NotEmpty(t, obj) { // assert.Equal(t, "two", obj[1]) // } -// -// Returns whether the assertion was successful (true) or not (false). func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { pass := !isEmpty(object) @@ -526,8 +489,6 @@ func getLen(x interface{}) (ok bool, length int) { // Len also fails if the object has a type that len() not accept. // // assert.Len(t, mySlice, 3) -// -// Returns whether the assertion was successful (true) or not (false). func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) bool { ok, l := getLen(object) if !ok { @@ -543,8 +504,6 @@ func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) // True asserts that the specified value is true. // // assert.True(t, myBool) -// -// Returns whether the assertion was successful (true) or not (false). func True(t TestingT, value bool, msgAndArgs ...interface{}) bool { if value != true { @@ -558,8 +517,6 @@ func True(t TestingT, value bool, msgAndArgs ...interface{}) bool { // False asserts that the specified value is false. // // assert.False(t, myBool) -// -// Returns whether the assertion was successful (true) or not (false). func False(t TestingT, value bool, msgAndArgs ...interface{}) bool { if value != false { @@ -574,8 +531,6 @@ func False(t TestingT, value bool, msgAndArgs ...interface{}) bool { // // assert.NotEqual(t, obj1, obj2) // -// Returns whether the assertion was successful (true) or not (false). -// // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { @@ -636,8 +591,6 @@ func includeElement(list interface{}, element interface{}) (ok, found bool) { // assert.Contains(t, "Hello World", "World") // assert.Contains(t, ["Hello", "World"], "World") // assert.Contains(t, {"Hello": "World"}, "Hello") -// -// Returns whether the assertion was successful (true) or not (false). func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool { ok, found := includeElement(s, contains) @@ -658,8 +611,6 @@ func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bo // assert.NotContains(t, "Hello World", "Earth") // assert.NotContains(t, ["Hello", "World"], "Earth") // assert.NotContains(t, {"Hello": "World"}, "Earth") -// -// Returns whether the assertion was successful (true) or not (false). func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool { ok, found := includeElement(s, contains) @@ -678,8 +629,6 @@ func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) // elements given in the specified subset(array, slice...). // // assert.Subset(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]") -// -// Returns whether the assertion was successful (true) or not (false). func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) { if subset == nil { return true // we consider nil to be equal to the nil set @@ -721,11 +670,9 @@ func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok // elements given in the specified subset(array, slice...). // // assert.NotSubset(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]") -// -// Returns whether the assertion was successful (true) or not (false). func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) { if subset == nil { - return false // we consider nil to be equal to the nil set + return Fail(t, fmt.Sprintf("nil is the empty set which is a subset of every set"), msgAndArgs...) } subsetValue := reflect.ValueOf(subset) @@ -760,6 +707,60 @@ func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) return Fail(t, fmt.Sprintf("%q is a subset of %q", subset, list), msgAndArgs...) } +// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// assert.ElementsMatch(t, [1, 3, 2, 3], [1, 3, 3, 2]) +func ElementsMatch(t TestingT, listA, listB interface{}, msgAndArgs ...interface{}) (ok bool) { + if isEmpty(listA) && isEmpty(listB) { + return true + } + + aKind := reflect.TypeOf(listA).Kind() + bKind := reflect.TypeOf(listB).Kind() + + if aKind != reflect.Array && aKind != reflect.Slice { + return Fail(t, fmt.Sprintf("%q has an unsupported type %s", listA, aKind), msgAndArgs...) + } + + if bKind != reflect.Array && bKind != reflect.Slice { + return Fail(t, fmt.Sprintf("%q has an unsupported type %s", listB, bKind), msgAndArgs...) + } + + aValue := reflect.ValueOf(listA) + bValue := reflect.ValueOf(listB) + + aLen := aValue.Len() + bLen := bValue.Len() + + if aLen != bLen { + return Fail(t, fmt.Sprintf("lengths don't match: %d != %d", aLen, bLen), msgAndArgs...) + } + + // Mark indexes in bValue that we already used + visited := make([]bool, bLen) + for i := 0; i < aLen; i++ { + element := aValue.Index(i).Interface() + found := false + for j := 0; j < bLen; j++ { + if visited[j] { + continue + } + if ObjectsAreEqual(bValue.Index(j).Interface(), element) { + visited[j] = true + found = true + break + } + } + if !found { + return Fail(t, fmt.Sprintf("element %s appears more times in %s than in %s", element, aValue, bValue), msgAndArgs...) + } + } + + return true +} + // Condition uses a Comparison to assert a complex condition. func Condition(t TestingT, comp Comparison, msgAndArgs ...interface{}) bool { result := comp() @@ -798,8 +799,6 @@ func didPanic(f PanicTestFunc) (bool, interface{}) { // Panics asserts that the code inside the specified PanicTestFunc panics. // // assert.Panics(t, func(){ GoCrazy() }) -// -// Returns whether the assertion was successful (true) or not (false). func Panics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { if funcDidPanic, panicValue := didPanic(f); !funcDidPanic { @@ -813,8 +812,6 @@ func Panics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { // the recovered panic value equals the expected panic value. // // assert.PanicsWithValue(t, "crazy error", func(){ GoCrazy() }) -// -// Returns whether the assertion was successful (true) or not (false). func PanicsWithValue(t TestingT, expected interface{}, f PanicTestFunc, msgAndArgs ...interface{}) bool { funcDidPanic, panicValue := didPanic(f) @@ -831,8 +828,6 @@ func PanicsWithValue(t TestingT, expected interface{}, f PanicTestFunc, msgAndAr // NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. // // assert.NotPanics(t, func(){ RemainCalm() }) -// -// Returns whether the assertion was successful (true) or not (false). func NotPanics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { if funcDidPanic, panicValue := didPanic(f); funcDidPanic { @@ -845,8 +840,6 @@ func NotPanics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { // WithinDuration asserts that the two times are within duration delta of each other. // // assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second) -// -// Returns whether the assertion was successful (true) or not (false). func WithinDuration(t TestingT, expected, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool { dt := expected.Sub(actual) @@ -896,8 +889,6 @@ func toFloat(x interface{}) (float64, bool) { // InDelta asserts that the two numerals are within delta of each other. // // assert.InDelta(t, math.Pi, (22 / 7.0), 0.01) -// -// Returns whether the assertion was successful (true) or not (false). func InDelta(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { af, aok := toFloat(expected) @@ -944,6 +935,47 @@ func InDeltaSlice(t TestingT, expected, actual interface{}, delta float64, msgAn return true } +// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func InDeltaMapValues(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + if expected == nil || actual == nil || + reflect.TypeOf(actual).Kind() != reflect.Map || + reflect.TypeOf(expected).Kind() != reflect.Map { + return Fail(t, "Arguments must be maps", msgAndArgs...) + } + + expectedMap := reflect.ValueOf(expected) + actualMap := reflect.ValueOf(actual) + + if expectedMap.Len() != actualMap.Len() { + return Fail(t, "Arguments must have the same number of keys", msgAndArgs...) + } + + for _, k := range expectedMap.MapKeys() { + ev := expectedMap.MapIndex(k) + av := actualMap.MapIndex(k) + + if !ev.IsValid() { + return Fail(t, fmt.Sprintf("missing key %q in expected map", k), msgAndArgs...) + } + + if !av.IsValid() { + return Fail(t, fmt.Sprintf("missing key %q in actual map", k), msgAndArgs...) + } + + if !InDelta( + t, + ev.Interface(), + av.Interface(), + delta, + msgAndArgs..., + ) { + return false + } + } + + return true +} + func calcRelativeError(expected, actual interface{}) (float64, error) { af, aok := toFloat(expected) if !aok { @@ -961,8 +993,6 @@ func calcRelativeError(expected, actual interface{}) (float64, error) { } // InEpsilon asserts that expected and actual have a relative error less than epsilon -// -// Returns whether the assertion was successful (true) or not (false). func InEpsilon(t TestingT, expected, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { actualEpsilon, err := calcRelativeError(expected, actual) if err != nil { @@ -1007,8 +1037,6 @@ func InEpsilonSlice(t TestingT, expected, actual interface{}, epsilon float64, m // if assert.NoError(t, err) { // assert.Equal(t, expectedObj, actualObj) // } -// -// Returns whether the assertion was successful (true) or not (false). func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool { if err != nil { return Fail(t, fmt.Sprintf("Received unexpected error:\n%+v", err), msgAndArgs...) @@ -1023,8 +1051,6 @@ func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool { // if assert.Error(t, err) { // assert.Equal(t, expectedError, err) // } -// -// Returns whether the assertion was successful (true) or not (false). func Error(t TestingT, err error, msgAndArgs ...interface{}) bool { if err == nil { @@ -1039,8 +1065,6 @@ func Error(t TestingT, err error, msgAndArgs ...interface{}) bool { // // actualObj, err := SomeFunction() // assert.EqualError(t, err, expectedErrorString) -// -// Returns whether the assertion was successful (true) or not (false). func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) bool { if !Error(t, theError, msgAndArgs...) { return false @@ -1051,7 +1075,7 @@ func EqualError(t TestingT, theError error, errString string, msgAndArgs ...inte if expected != actual { return Fail(t, fmt.Sprintf("Error message not equal:\n"+ "expected: %q\n"+ - "actual: %q", expected, actual), msgAndArgs...) + "actual : %q", expected, actual), msgAndArgs...) } return true } @@ -1074,8 +1098,6 @@ func matchRegexp(rx interface{}, str interface{}) bool { // // assert.Regexp(t, regexp.MustCompile("start"), "it's starting") // assert.Regexp(t, "start...$", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { match := matchRegexp(rx, str) @@ -1091,8 +1113,6 @@ func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface // // assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") // assert.NotRegexp(t, "^start", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { match := matchRegexp(rx, str) @@ -1104,7 +1124,7 @@ func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interf } -// Zero asserts that i is the zero value for its type and returns the truth. +// Zero asserts that i is the zero value for its type. func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool { if i != nil && !reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface()) { return Fail(t, fmt.Sprintf("Should be zero, but was %v", i), msgAndArgs...) @@ -1112,7 +1132,7 @@ func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool { return true } -// NotZero asserts that i is not the zero value for its type and returns the truth. +// NotZero asserts that i is not the zero value for its type. func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool { if i == nil || reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface()) { return Fail(t, fmt.Sprintf("Should not be zero, but was %v", i), msgAndArgs...) @@ -1120,11 +1140,39 @@ func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool { return true } +// FileExists checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file. +func FileExists(t TestingT, path string, msgAndArgs ...interface{}) bool { + info, err := os.Lstat(path) + if err != nil { + if os.IsNotExist(err) { + return Fail(t, fmt.Sprintf("unable to find file %q", path), msgAndArgs...) + } + return Fail(t, fmt.Sprintf("error when running os.Lstat(%q): %s", path, err), msgAndArgs...) + } + if info.IsDir() { + return Fail(t, fmt.Sprintf("%q is a directory", path), msgAndArgs...) + } + return true +} + +// DirExists checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists. +func DirExists(t TestingT, path string, msgAndArgs ...interface{}) bool { + info, err := os.Lstat(path) + if err != nil { + if os.IsNotExist(err) { + return Fail(t, fmt.Sprintf("unable to find file %q", path), msgAndArgs...) + } + return Fail(t, fmt.Sprintf("error when running os.Lstat(%q): %s", path, err), msgAndArgs...) + } + if !info.IsDir() { + return Fail(t, fmt.Sprintf("%q is a file", path), msgAndArgs...) + } + return true +} + // JSONEq asserts that two JSON strings are equivalent. // // assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) -// -// Returns whether the assertion was successful (true) or not (false). func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) bool { var expectedJSONAsInterface, actualJSONAsInterface interface{} diff --git a/vendor/github.com/stretchr/testify/assert/http_assertions.go b/vendor/github.com/stretchr/testify/assert/http_assertions.go index ba811c04dd5f3..3101e78ddcf6e 100644 --- a/vendor/github.com/stretchr/testify/assert/http_assertions.go +++ b/vendor/github.com/stretchr/testify/assert/http_assertions.go @@ -25,7 +25,7 @@ func httpCode(handler http.HandlerFunc, method, url string, values url.Values) ( // assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) // // Returns whether the assertion was successful (true) or not (false). -func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool { +func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { code, err := httpCode(handler, method, url, values) if err != nil { Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err)) @@ -45,7 +45,7 @@ func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, value // assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). -func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool { +func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { code, err := httpCode(handler, method, url, values) if err != nil { Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err)) @@ -65,7 +65,7 @@ func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, valu // assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). -func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool { +func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { code, err := httpCode(handler, method, url, values) if err != nil { Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err)) @@ -98,7 +98,7 @@ func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) s // assert.HTTPBodyContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") // // Returns whether the assertion was successful (true) or not (false). -func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}) bool { +func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { body := HTTPBody(handler, method, url, values) contains := strings.Contains(body, fmt.Sprint(str)) @@ -115,7 +115,7 @@ func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, // assert.HTTPBodyNotContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") // // Returns whether the assertion was successful (true) or not (false). -func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}) bool { +func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { body := HTTPBody(handler, method, url, values) contains := strings.Contains(body, fmt.Sprint(str)) From cb138800c44a96a05527b4bf98301bb184319a9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauris=20Buk=C5=A1is-Haberkorns?= Date: Thu, 24 May 2018 07:40:08 +0300 Subject: [PATCH 33/33] Revert to update only needed user columns --- models/user.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/user.go b/models/user.go index b2320c764a5d2..c6246c356a3cc 100644 --- a/models/user.go +++ b/models/user.go @@ -1574,7 +1574,7 @@ func SyncExternalUsers() { } usr.IsActive = true - err = UpdateUser(usr) + err = UpdateUserCols(usr, "full_name", "email", "is_admin", "is_active") if err != nil { log.Error(4, "SyncExternalUsers[%s]: Error updating user %s: %v", s.Name, usr.Name, err) }