From 1ea504810bcc9c6801f2dff6904bb170fc782afe Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Fri, 27 Aug 2021 21:18:16 +0100 Subject: [PATCH 1/7] Allow LDAP Sources to provide Avatars Add setting to LDAP source to allow it to provide an Avatar. Currently this is required to point to the image bytes. Fix #4144 Signed-off-by: Andrew Thornton --- cmd/admin_auth_ldap.go | 7 +++++++ cmd/admin_auth_ldap_test.go | 8 ++++++++ docs/content/doc/usage/command-line.en-us.md | 4 ++++ options/locale/locale_en-US.ini | 1 + routers/web/admin/auths.go | 1 + services/auth/source/ldap/source.go | 1 + services/auth/source/ldap/source_authenticate.go | 4 ++++ services/auth/source/ldap/source_search.go | 10 ++++++++++ services/auth/source/ldap/source_sync.go | 12 +++++++++++- services/forms/auth_form.go | 1 + templates/admin/auth/edit.tmpl | 4 ++++ templates/admin/auth/source/ldap.tmpl | 4 ++++ 12 files changed, 56 insertions(+), 1 deletion(-) diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index 4314930a3e08..7dcb2a5ffc22 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -89,6 +89,10 @@ var ( Name: "public-ssh-key-attribute", Usage: "The attribute of the user’s LDAP record containing the user’s public ssh key.", }, + cli.StringFlag{ + Name: "jpeg-avatar-attribute", + Usage: "The attribute of the user’s LDAP record containing the user’s avatar.", + }, } ldapBindDnCLIFlags = append(commonLdapCLIFlags, @@ -230,6 +234,9 @@ func parseLdapConfig(c *cli.Context, config *ldap.Source) error { if c.IsSet("public-ssh-key-attribute") { config.AttributeSSHPublicKey = c.String("public-ssh-key-attribute") } + if c.IsSet("jpeg-avatar-attribute") { + config.AttributeAvatarJPEG = c.String("jpeg-avatar-attribute") + } if c.IsSet("page-size") { config.SearchPageSize = uint32(c.Uint("page-size")) } diff --git a/cmd/admin_auth_ldap_test.go b/cmd/admin_auth_ldap_test.go index 692b11e3f422..e964467735fb 100644 --- a/cmd/admin_auth_ldap_test.go +++ b/cmd/admin_auth_ldap_test.go @@ -45,6 +45,7 @@ func TestAddLdapBindDn(t *testing.T) { "--surname-attribute", "sn-bind full", "--email-attribute", "mail-bind full", "--public-ssh-key-attribute", "publickey-bind full", + "--jpeg-avatar-attribute", "avatar-bind full", "--bind-dn", "cn=readonly,dc=full-domain-bind,dc=org", "--bind-password", "secret-bind-full", "--attributes-in-bind", @@ -71,6 +72,7 @@ func TestAddLdapBindDn(t *testing.T) { AttributeMail: "mail-bind full", AttributesInBind: true, AttributeSSHPublicKey: "publickey-bind full", + AttributeAvatarJPEG: "avatar-bind full", SearchPageSize: 99, Filter: "(memberOf=cn=user-group,ou=example,dc=full-domain-bind,dc=org)", AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)", @@ -269,6 +271,7 @@ func TestAddLdapSimpleAuth(t *testing.T) { "--surname-attribute", "sn-simple full", "--email-attribute", "mail-simple full", "--public-ssh-key-attribute", "publickey-simple full", + "--jpeg-avatar-attribute", "avatar-simple full", "--user-dn", "cn=%s,ou=Users,dc=full-domain-simple,dc=org", }, loginSource: &models.LoginSource{ @@ -288,6 +291,7 @@ func TestAddLdapSimpleAuth(t *testing.T) { AttributeSurname: "sn-simple full", AttributeMail: "mail-simple full", AttributeSSHPublicKey: "publickey-simple full", + AttributeAvatarJPEG: "avatar-simple full", Filter: "(&(objectClass=posixAccount)(full-simple-cn=%s))", AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-simple,dc=org)", RestrictedFilter: "(memberOf=cn=restricted-group,ou=example,dc=full-domain-simple,dc=org)", @@ -501,6 +505,7 @@ func TestUpdateLdapBindDn(t *testing.T) { "--surname-attribute", "sn-bind full", "--email-attribute", "mail-bind full", "--public-ssh-key-attribute", "publickey-bind full", + "--jpeg-avatar-attribute", "avatar-bind full", "--bind-dn", "cn=readonly,dc=full-domain-bind,dc=org", "--bind-password", "secret-bind-full", "--synchronize-users", @@ -534,6 +539,7 @@ func TestUpdateLdapBindDn(t *testing.T) { AttributeMail: "mail-bind full", AttributesInBind: false, AttributeSSHPublicKey: "publickey-bind full", + AttributeAvatarJPEG: "avatar-bind full", SearchPageSize: 99, Filter: "(memberOf=cn=user-group,ou=example,dc=full-domain-bind,dc=org)", AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)", @@ -932,6 +938,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { "--surname-attribute", "sn-simple full", "--email-attribute", "mail-simple full", "--public-ssh-key-attribute", "publickey-simple full", + "--jpeg-avatar-attribute", "avatar-simple full", "--user-dn", "cn=%s,ou=Users,dc=full-domain-simple,dc=org", }, id: 7, @@ -952,6 +959,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { AttributeSurname: "sn-simple full", AttributeMail: "mail-simple full", AttributeSSHPublicKey: "publickey-simple full", + AttributeAvatarJPEG: "avatar-simple full", Filter: "(&(objectClass=posixAccount)(full-simple-cn=%s))", AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-simple,dc=org)", RestrictedFilter: "(memberOf=cn=restricted-group,ou=example,dc=full-domain-simple,dc=org)", diff --git a/docs/content/doc/usage/command-line.en-us.md b/docs/content/doc/usage/command-line.en-us.md index 0bc8d70fdb53..4932b2170af8 100644 --- a/docs/content/doc/usage/command-line.en-us.md +++ b/docs/content/doc/usage/command-line.en-us.md @@ -152,6 +152,7 @@ Admin operations: - `--surname-attribute value`: The attribute of the user’s LDAP record containing the user’s surname. - `--email-attribute value`: The attribute of the user’s LDAP record containing the user’s email address. Required. - `--public-ssh-key-attribute value`: The attribute of the user’s LDAP record containing the user’s public ssh key. + - `--jpeg-avatar-attribute value`: The attribute of the user’s LDAP record containing the user’s avatar. - `--bind-dn value`: The DN to bind to the LDAP server with when searching for the user. - `--bind-password value`: The password for the Bind DN, if any. - `--attributes-in-bind`: Fetch attributes in bind DN context. @@ -177,6 +178,7 @@ Admin operations: - `--surname-attribute value`: The attribute of the user’s LDAP record containing the user’s surname. - `--email-attribute value`: The attribute of the user’s LDAP record containing the user’s email address. - `--public-ssh-key-attribute value`: The attribute of the user’s LDAP record containing the user’s public ssh key. + - `--jpeg-avatar-attribute value`: The attribute of the user’s LDAP record containing the user’s avatar. - `--bind-dn value`: The DN to bind to the LDAP server with when searching for the user. - `--bind-password value`: The password for the Bind DN, if any. - `--attributes-in-bind`: Fetch attributes in bind DN context. @@ -202,6 +204,7 @@ Admin operations: - `--surname-attribute value`: The attribute of the user’s LDAP record containing the user’s surname. - `--email-attribute value`: The attribute of the user’s LDAP record containing the user’s email address. Required. - `--public-ssh-key-attribute value`: The attribute of the user’s LDAP record containing the user’s public ssh key. + - `--jpeg-avatar-attribute value`: The attribute of the user’s LDAP record containing the user’s avatar. - `--user-dn value`: The user’s DN. Required. - Examples: - `gitea admin auth add-ldap-simple --name ldap --security-protocol unencrypted --host mydomain.org --port 389 --user-dn "cn=%s,ou=Users,dc=mydomain,dc=org" --user-filter "(&(objectClass=posixAccount)(cn=%s))" --email-attribute mail` @@ -223,6 +226,7 @@ Admin operations: - `--surname-attribute value`: The attribute of the user’s LDAP record containing the user’s surname. - `--email-attribute value`: The attribute of the user’s LDAP record containing the user’s email address. - `--public-ssh-key-attribute value`: The attribute of the user’s LDAP record containing the user’s public ssh key. + - `--jpeg-avatar-attribute value`: The attribute of the user’s LDAP record containing the user’s avatar. - `--user-dn value`: The user’s DN. - Examples: - `gitea admin auth update-ldap-simple --id 1 --name "my ldap auth source"` diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 76536f2d4975..a5d66b29b4a3 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2416,6 +2416,7 @@ 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_avatar_jpeg = JPEG Avatar Attribute auths.attributes_in_bind = Fetch Attributes in Bind DN Context auths.allow_deactivate_all = Allow an empty search result to deactivate all users auths.use_paged_search = Use Paged Search diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go index 342318e04e9b..38f25d509283 100644 --- a/routers/web/admin/auths.go +++ b/routers/web/admin/auths.go @@ -134,6 +134,7 @@ func parseLDAPConfig(form forms.AuthenticationForm) *ldap.Source { AttributeMail: form.AttributeMail, AttributesInBind: form.AttributesInBind, AttributeSSHPublicKey: form.AttributeSSHPublicKey, + AttributeAvatarJPEG: form.AttributeAvatarJPEG, SearchPageSize: pageSize, Filter: form.Filter, GroupsEnabled: form.GroupsEnabled, diff --git a/services/auth/source/ldap/source.go b/services/auth/source/ldap/source.go index 79f118f78465..6be05e5c2cef 100644 --- a/services/auth/source/ldap/source.go +++ b/services/auth/source/ldap/source.go @@ -41,6 +41,7 @@ type Source struct { AttributeMail string // E-mail attribute AttributesInBind bool // fetch attributes in bind context (not user) AttributeSSHPublicKey string // LDAP SSH Public Key attribute + AttributeAvatarJPEG string SearchPageSize uint32 // Search with paging page size Filter string // Query filter to validate entry AdminFilter string // Query filter to check if user is admin diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go index ecc95fbd56ef..4fadcf4dc8ff 100644 --- a/services/auth/source/ldap/source_authenticate.go +++ b/services/auth/source/ldap/source_authenticate.go @@ -95,5 +95,9 @@ func (source *Source) Authenticate(user *models.User, login, password string) (* err = models.RewriteAllPublicKeys() } + if err == nil && source.AttributeAvatarJPEG != "" { + _ = user.UploadAvatar(sr.AvatarJPEG) + } + return user, err } diff --git a/services/auth/source/ldap/source_search.go b/services/auth/source/ldap/source_search.go index f2acbb0d4b89..156b6e7d8f37 100644 --- a/services/auth/source/ldap/source_search.go +++ b/services/auth/source/ldap/source_search.go @@ -26,6 +26,7 @@ type SearchResult struct { SSHPublicKey []string // SSH Public Key IsAdmin bool // if user is administrator IsRestricted bool // if user is restricted + AvatarJPEG []byte } func (ls *Source) sanitizedUserQuery(username string) (string, bool) { @@ -295,6 +296,7 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResul } var sshPublicKey []string + var avatarJPEG []byte username := sr.Entries[0].GetAttributeValue(ls.AttributeUsername) firstname := sr.Entries[0].GetAttributeValue(ls.AttributeName) @@ -362,6 +364,10 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResul } } + if ls.AttributeAvatarJPEG != "" { + avatarJPEG = sr.Entries[0].GetRawAttributeValue(ls.AttributeAvatarJPEG) + } + return &SearchResult{ Username: username, Name: firstname, @@ -370,6 +376,7 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResul SSHPublicKey: sshPublicKey, IsAdmin: isAdmin, IsRestricted: isRestricted, + AvatarJPEG: avatarJPEG, } } @@ -440,6 +447,9 @@ func (ls *Source) SearchEntries() ([]*SearchResult, error) { if isAttributeSSHPublicKeySet { result[i].SSHPublicKey = v.GetAttributeValues(ls.AttributeSSHPublicKey) } + if ls.AttributeAvatarJPEG != "" { + result[i].AvatarJPEG = v.GetRawAttributeValue(ls.AttributeAvatarJPEG) + } } return result, nil diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go index 7e4088e57173..4fc49c038d1f 100644 --- a/services/auth/source/ldap/source_sync.go +++ b/services/auth/source/ldap/source_sync.go @@ -101,12 +101,18 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { if err != nil { log.Error("SyncExternalUsers[%s]: Error creating user %s: %v", source.loginSource.Name, su.Username, err) - } else if isAttributeSSHPublicKeySet { + } + + if err == nil && isAttributeSSHPublicKeySet { log.Trace("SyncExternalUsers[%s]: Adding LDAP Public SSH Keys for user %s", source.loginSource.Name, usr.Name) if models.AddPublicKeysBySource(usr, source.loginSource, su.SSHPublicKey) { sshKeysNeedUpdate = true } } + + if err == nil && source.AttributeAvatarJPEG != "" { + _ = usr.UploadAvatar(su.AvatarJPEG) + } } else if updateExisting { existingUsers = append(existingUsers, usr.ID) @@ -140,6 +146,10 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { if err != nil { log.Error("SyncExternalUsers[%s]: Error updating user %s: %v", source.loginSource.Name, usr.Name, err) } + + if err == nil && source.AttributeAvatarJPEG != "" { + _ = usr.UploadAvatar(su.AvatarJPEG) + } } } } diff --git a/services/forms/auth_form.go b/services/forms/auth_form.go index b45ea6ea124f..2c45465d9491 100644 --- a/services/forms/auth_form.go +++ b/services/forms/auth_form.go @@ -29,6 +29,7 @@ type AuthenticationForm struct { AttributeSurname string AttributeMail string AttributeSSHPublicKey string + AttributeAvatarJPEG string AttributesInBind bool UsePagedSearch bool SearchPageSize int diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl index 109186a17821..cdde5b2435be 100644 --- a/templates/admin/auth/edit.tmpl +++ b/templates/admin/auth/edit.tmpl @@ -104,6 +104,10 @@ +
+ + +
diff --git a/templates/admin/auth/source/ldap.tmpl b/templates/admin/auth/source/ldap.tmpl index 295e001cf4a3..16bda8937c18 100644 --- a/templates/admin/auth/source/ldap.tmpl +++ b/templates/admin/auth/source/ldap.tmpl @@ -76,6 +76,10 @@
+
+ + +
From 79d8b78e8df93abac4f0c5d64faea34eef261fcc Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Sun, 29 Aug 2021 16:29:20 +0100 Subject: [PATCH 2/7] Rename as Avatar Attribute (drop JPEG) Signed-off-by: Andrew Thornton --- cmd/admin_auth_ldap.go | 6 +++--- cmd/admin_auth_ldap_test.go | 16 ++++++++-------- docs/content/doc/usage/command-line.en-us.md | 8 ++++---- options/locale/locale_en-US.ini | 2 +- routers/web/admin/auths.go | 2 +- services/auth/source/ldap/source.go | 2 +- services/auth/source/ldap/source_authenticate.go | 4 ++-- services/auth/source/ldap/source_search.go | 14 +++++++------- services/auth/source/ldap/source_sync.go | 8 ++++---- services/forms/auth_form.go | 2 +- templates/admin/auth/edit.tmpl | 4 ++-- templates/admin/auth/source/ldap.tmpl | 4 ++-- 12 files changed, 36 insertions(+), 36 deletions(-) diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index 7dcb2a5ffc22..2db1e74f3cd9 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -90,7 +90,7 @@ var ( Usage: "The attribute of the user’s LDAP record containing the user’s public ssh key.", }, cli.StringFlag{ - Name: "jpeg-avatar-attribute", + Name: "avatar-attribute", Usage: "The attribute of the user’s LDAP record containing the user’s avatar.", }, } @@ -234,8 +234,8 @@ func parseLdapConfig(c *cli.Context, config *ldap.Source) error { if c.IsSet("public-ssh-key-attribute") { config.AttributeSSHPublicKey = c.String("public-ssh-key-attribute") } - if c.IsSet("jpeg-avatar-attribute") { - config.AttributeAvatarJPEG = c.String("jpeg-avatar-attribute") + if c.IsSet("avatar-attribute") { + config.AttributeAvatar = c.String("avatar-attribute") } if c.IsSet("page-size") { config.SearchPageSize = uint32(c.Uint("page-size")) diff --git a/cmd/admin_auth_ldap_test.go b/cmd/admin_auth_ldap_test.go index e964467735fb..41d43db52350 100644 --- a/cmd/admin_auth_ldap_test.go +++ b/cmd/admin_auth_ldap_test.go @@ -45,7 +45,7 @@ func TestAddLdapBindDn(t *testing.T) { "--surname-attribute", "sn-bind full", "--email-attribute", "mail-bind full", "--public-ssh-key-attribute", "publickey-bind full", - "--jpeg-avatar-attribute", "avatar-bind full", + "--avatar-attribute", "avatar-bind full", "--bind-dn", "cn=readonly,dc=full-domain-bind,dc=org", "--bind-password", "secret-bind-full", "--attributes-in-bind", @@ -72,7 +72,7 @@ func TestAddLdapBindDn(t *testing.T) { AttributeMail: "mail-bind full", AttributesInBind: true, AttributeSSHPublicKey: "publickey-bind full", - AttributeAvatarJPEG: "avatar-bind full", + AttributeAvatar: "avatar-bind full", SearchPageSize: 99, Filter: "(memberOf=cn=user-group,ou=example,dc=full-domain-bind,dc=org)", AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)", @@ -271,7 +271,7 @@ func TestAddLdapSimpleAuth(t *testing.T) { "--surname-attribute", "sn-simple full", "--email-attribute", "mail-simple full", "--public-ssh-key-attribute", "publickey-simple full", - "--jpeg-avatar-attribute", "avatar-simple full", + "--avatar-attribute", "avatar-simple full", "--user-dn", "cn=%s,ou=Users,dc=full-domain-simple,dc=org", }, loginSource: &models.LoginSource{ @@ -291,7 +291,7 @@ func TestAddLdapSimpleAuth(t *testing.T) { AttributeSurname: "sn-simple full", AttributeMail: "mail-simple full", AttributeSSHPublicKey: "publickey-simple full", - AttributeAvatarJPEG: "avatar-simple full", + AttributeAvatar: "avatar-simple full", Filter: "(&(objectClass=posixAccount)(full-simple-cn=%s))", AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-simple,dc=org)", RestrictedFilter: "(memberOf=cn=restricted-group,ou=example,dc=full-domain-simple,dc=org)", @@ -505,7 +505,7 @@ func TestUpdateLdapBindDn(t *testing.T) { "--surname-attribute", "sn-bind full", "--email-attribute", "mail-bind full", "--public-ssh-key-attribute", "publickey-bind full", - "--jpeg-avatar-attribute", "avatar-bind full", + "--avatar-attribute", "avatar-bind full", "--bind-dn", "cn=readonly,dc=full-domain-bind,dc=org", "--bind-password", "secret-bind-full", "--synchronize-users", @@ -539,7 +539,7 @@ func TestUpdateLdapBindDn(t *testing.T) { AttributeMail: "mail-bind full", AttributesInBind: false, AttributeSSHPublicKey: "publickey-bind full", - AttributeAvatarJPEG: "avatar-bind full", + AttributeAvatar: "avatar-bind full", SearchPageSize: 99, Filter: "(memberOf=cn=user-group,ou=example,dc=full-domain-bind,dc=org)", AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)", @@ -938,7 +938,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { "--surname-attribute", "sn-simple full", "--email-attribute", "mail-simple full", "--public-ssh-key-attribute", "publickey-simple full", - "--jpeg-avatar-attribute", "avatar-simple full", + "--avatar-attribute", "avatar-simple full", "--user-dn", "cn=%s,ou=Users,dc=full-domain-simple,dc=org", }, id: 7, @@ -959,7 +959,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { AttributeSurname: "sn-simple full", AttributeMail: "mail-simple full", AttributeSSHPublicKey: "publickey-simple full", - AttributeAvatarJPEG: "avatar-simple full", + AttributeAvatar: "avatar-simple full", Filter: "(&(objectClass=posixAccount)(full-simple-cn=%s))", AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-simple,dc=org)", RestrictedFilter: "(memberOf=cn=restricted-group,ou=example,dc=full-domain-simple,dc=org)", diff --git a/docs/content/doc/usage/command-line.en-us.md b/docs/content/doc/usage/command-line.en-us.md index 4932b2170af8..b12400c57ec1 100644 --- a/docs/content/doc/usage/command-line.en-us.md +++ b/docs/content/doc/usage/command-line.en-us.md @@ -152,7 +152,7 @@ Admin operations: - `--surname-attribute value`: The attribute of the user’s LDAP record containing the user’s surname. - `--email-attribute value`: The attribute of the user’s LDAP record containing the user’s email address. Required. - `--public-ssh-key-attribute value`: The attribute of the user’s LDAP record containing the user’s public ssh key. - - `--jpeg-avatar-attribute value`: The attribute of the user’s LDAP record containing the user’s avatar. + - `--avatar-attribute value`: The attribute of the user’s LDAP record containing the user’s avatar. - `--bind-dn value`: The DN to bind to the LDAP server with when searching for the user. - `--bind-password value`: The password for the Bind DN, if any. - `--attributes-in-bind`: Fetch attributes in bind DN context. @@ -178,7 +178,7 @@ Admin operations: - `--surname-attribute value`: The attribute of the user’s LDAP record containing the user’s surname. - `--email-attribute value`: The attribute of the user’s LDAP record containing the user’s email address. - `--public-ssh-key-attribute value`: The attribute of the user’s LDAP record containing the user’s public ssh key. - - `--jpeg-avatar-attribute value`: The attribute of the user’s LDAP record containing the user’s avatar. + - `--avatar-attribute value`: The attribute of the user’s LDAP record containing the user’s avatar. - `--bind-dn value`: The DN to bind to the LDAP server with when searching for the user. - `--bind-password value`: The password for the Bind DN, if any. - `--attributes-in-bind`: Fetch attributes in bind DN context. @@ -204,7 +204,7 @@ Admin operations: - `--surname-attribute value`: The attribute of the user’s LDAP record containing the user’s surname. - `--email-attribute value`: The attribute of the user’s LDAP record containing the user’s email address. Required. - `--public-ssh-key-attribute value`: The attribute of the user’s LDAP record containing the user’s public ssh key. - - `--jpeg-avatar-attribute value`: The attribute of the user’s LDAP record containing the user’s avatar. + - `--avatar-attribute value`: The attribute of the user’s LDAP record containing the user’s avatar. - `--user-dn value`: The user’s DN. Required. - Examples: - `gitea admin auth add-ldap-simple --name ldap --security-protocol unencrypted --host mydomain.org --port 389 --user-dn "cn=%s,ou=Users,dc=mydomain,dc=org" --user-filter "(&(objectClass=posixAccount)(cn=%s))" --email-attribute mail` @@ -226,7 +226,7 @@ Admin operations: - `--surname-attribute value`: The attribute of the user’s LDAP record containing the user’s surname. - `--email-attribute value`: The attribute of the user’s LDAP record containing the user’s email address. - `--public-ssh-key-attribute value`: The attribute of the user’s LDAP record containing the user’s public ssh key. - - `--jpeg-avatar-attribute value`: The attribute of the user’s LDAP record containing the user’s avatar. + - `--avatar-attribute value`: The attribute of the user’s LDAP record containing the user’s avatar. - `--user-dn value`: The user’s DN. - Examples: - `gitea admin auth update-ldap-simple --id 1 --name "my ldap auth source"` diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index a5d66b29b4a3..71f8bd13bcd2 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2416,7 +2416,7 @@ 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_avatar_jpeg = JPEG Avatar Attribute +auths.attribute_avatar = Avatar Attribute auths.attributes_in_bind = Fetch Attributes in Bind DN Context auths.allow_deactivate_all = Allow an empty search result to deactivate all users auths.use_paged_search = Use Paged Search diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go index 38f25d509283..ab54af612661 100644 --- a/routers/web/admin/auths.go +++ b/routers/web/admin/auths.go @@ -134,7 +134,7 @@ func parseLDAPConfig(form forms.AuthenticationForm) *ldap.Source { AttributeMail: form.AttributeMail, AttributesInBind: form.AttributesInBind, AttributeSSHPublicKey: form.AttributeSSHPublicKey, - AttributeAvatarJPEG: form.AttributeAvatarJPEG, + AttributeAvatar: form.AttributeAvatar, SearchPageSize: pageSize, Filter: form.Filter, GroupsEnabled: form.GroupsEnabled, diff --git a/services/auth/source/ldap/source.go b/services/auth/source/ldap/source.go index 6be05e5c2cef..80272912772d 100644 --- a/services/auth/source/ldap/source.go +++ b/services/auth/source/ldap/source.go @@ -41,7 +41,7 @@ type Source struct { AttributeMail string // E-mail attribute AttributesInBind bool // fetch attributes in bind context (not user) AttributeSSHPublicKey string // LDAP SSH Public Key attribute - AttributeAvatarJPEG string + AttributeAvatar string SearchPageSize uint32 // Search with paging page size Filter string // Query filter to validate entry AdminFilter string // Query filter to check if user is admin diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go index 4fadcf4dc8ff..0b4e70937e23 100644 --- a/services/auth/source/ldap/source_authenticate.go +++ b/services/auth/source/ldap/source_authenticate.go @@ -95,8 +95,8 @@ func (source *Source) Authenticate(user *models.User, login, password string) (* err = models.RewriteAllPublicKeys() } - if err == nil && source.AttributeAvatarJPEG != "" { - _ = user.UploadAvatar(sr.AvatarJPEG) + if err == nil && source.AttributeAvatar != "" { + _ = user.UploadAvatar(sr.Avatar) } return user, err diff --git a/services/auth/source/ldap/source_search.go b/services/auth/source/ldap/source_search.go index 156b6e7d8f37..7632c6301da1 100644 --- a/services/auth/source/ldap/source_search.go +++ b/services/auth/source/ldap/source_search.go @@ -26,7 +26,7 @@ type SearchResult struct { SSHPublicKey []string // SSH Public Key IsAdmin bool // if user is administrator IsRestricted bool // if user is restricted - AvatarJPEG []byte + Avatar []byte } func (ls *Source) sanitizedUserQuery(username string) (string, bool) { @@ -296,7 +296,7 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResul } var sshPublicKey []string - var avatarJPEG []byte + var Avatar []byte username := sr.Entries[0].GetAttributeValue(ls.AttributeUsername) firstname := sr.Entries[0].GetAttributeValue(ls.AttributeName) @@ -364,8 +364,8 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResul } } - if ls.AttributeAvatarJPEG != "" { - avatarJPEG = sr.Entries[0].GetRawAttributeValue(ls.AttributeAvatarJPEG) + if ls.AttributeAvatar != "" { + Avatar = sr.Entries[0].GetRawAttributeValue(ls.AttributeAvatar) } return &SearchResult{ @@ -376,7 +376,7 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResul SSHPublicKey: sshPublicKey, IsAdmin: isAdmin, IsRestricted: isRestricted, - AvatarJPEG: avatarJPEG, + Avatar: Avatar, } } @@ -447,8 +447,8 @@ func (ls *Source) SearchEntries() ([]*SearchResult, error) { if isAttributeSSHPublicKeySet { result[i].SSHPublicKey = v.GetAttributeValues(ls.AttributeSSHPublicKey) } - if ls.AttributeAvatarJPEG != "" { - result[i].AvatarJPEG = v.GetRawAttributeValue(ls.AttributeAvatarJPEG) + if ls.AttributeAvatar != "" { + result[i].Avatar = v.GetRawAttributeValue(ls.AttributeAvatar) } } diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go index 4fc49c038d1f..d524fd317c5e 100644 --- a/services/auth/source/ldap/source_sync.go +++ b/services/auth/source/ldap/source_sync.go @@ -110,8 +110,8 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { } } - if err == nil && source.AttributeAvatarJPEG != "" { - _ = usr.UploadAvatar(su.AvatarJPEG) + if err == nil && source.AttributeAvatar != "" { + _ = usr.UploadAvatar(su.Avatar) } } else if updateExisting { existingUsers = append(existingUsers, usr.ID) @@ -147,8 +147,8 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { log.Error("SyncExternalUsers[%s]: Error updating user %s: %v", source.loginSource.Name, usr.Name, err) } - if err == nil && source.AttributeAvatarJPEG != "" { - _ = usr.UploadAvatar(su.AvatarJPEG) + if err == nil && source.AttributeAvatar != "" { + _ = usr.UploadAvatar(su.Avatar) } } } diff --git a/services/forms/auth_form.go b/services/forms/auth_form.go index 2c45465d9491..d659836ba511 100644 --- a/services/forms/auth_form.go +++ b/services/forms/auth_form.go @@ -29,7 +29,7 @@ type AuthenticationForm struct { AttributeSurname string AttributeMail string AttributeSSHPublicKey string - AttributeAvatarJPEG string + AttributeAvatar string AttributesInBind bool UsePagedSearch bool SearchPageSize int diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl index cdde5b2435be..13c4142744c0 100644 --- a/templates/admin/auth/edit.tmpl +++ b/templates/admin/auth/edit.tmpl @@ -105,8 +105,8 @@
- - + +
diff --git a/templates/admin/auth/source/ldap.tmpl b/templates/admin/auth/source/ldap.tmpl index 16bda8937c18..937d8777e3b6 100644 --- a/templates/admin/auth/source/ldap.tmpl +++ b/templates/admin/auth/source/ldap.tmpl @@ -77,8 +77,8 @@
- - + +
From 929885e0c10d663b52662bdb63cc3bb5b0faa84b Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Wed, 8 Sep 2021 18:34:25 +0100 Subject: [PATCH 3/7] Always synchronize avatar if there is change Signed-off-by: Andrew Thornton --- models/user_avatar.go | 6 ++++++ services/auth/source/ldap/source_sync.go | 3 +++ 2 files changed, 9 insertions(+) diff --git a/models/user_avatar.go b/models/user_avatar.go index d336684a27d7..6eab0faa5c10 100644 --- a/models/user_avatar.go +++ b/models/user_avatar.go @@ -152,6 +152,12 @@ func (u *User) UploadAvatar(data []byte) error { return sess.Commit() } +// IsUploadAvatarChanged returns true if the current user's avatar would be changed with the provided data +func (u *User) IsUploadAvatarChanged(data []byte) bool { + avatarID := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%d-%x", u.ID, md5.Sum(data))))) + return !u.UseCustomAvatar || u.Avatar != avatarID +} + // DeleteAvatar deletes the user's custom avatar. func (u *User) DeleteAvatar() error { aPath := u.CustomAvatarRelativePath() diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go index d524fd317c5e..69c69975b4aa 100644 --- a/services/auth/source/ldap/source_sync.go +++ b/services/auth/source/ldap/source_sync.go @@ -146,10 +146,13 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { if err != nil { log.Error("SyncExternalUsers[%s]: Error updating user %s: %v", source.loginSource.Name, usr.Name, err) } + } + if usr.IsUploadAvatarChanged(su.Avatar) { if err == nil && source.AttributeAvatar != "" { _ = usr.UploadAvatar(su.Avatar) } + } } } From a3e051a306753f2d0f9d524ae1b3a5d6cfc525a0 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Wed, 15 Sep 2021 20:36:04 +0100 Subject: [PATCH 4/7] Actually get the avatar from the ldap Signed-off-by: Andrew Thornton --- services/auth/source/ldap/source_search.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/auth/source/ldap/source_search.go b/services/auth/source/ldap/source_search.go index de9b0e7e2028..2436286fdade 100644 --- a/services/auth/source/ldap/source_search.go +++ b/services/auth/source/ldap/source_search.go @@ -416,8 +416,11 @@ func (ls *Source) SearchEntries() ([]*SearchResult, error) { if isAttributeSSHPublicKeySet { attribs = append(attribs, ls.AttributeSSHPublicKey) } + if ls.AttributeAvatar != "" { + attribs = append(attribs, ls.AttributeAvatar) + } - 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) + log.Trace("Fetching attributes '%v', '%v', '%v', '%v', '%v', '%v' with filter %s and base %s", ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail, ls.AttributeSSHPublicKey, ls.AttributeAvatar, userFilter, ls.UserBase) search := ldap.NewSearchRequest( ls.UserBase, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, userFilter, attribs, nil) From 5f7352645f714c38c8fbbf557965c839e1907df1 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Wed, 15 Sep 2021 20:58:43 +0100 Subject: [PATCH 5/7] clean-up Signed-off-by: Andrew Thornton --- services/auth/source/ldap/source_search.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/services/auth/source/ldap/source_search.go b/services/auth/source/ldap/source_search.go index 2436286fdade..1f1cca270d40 100644 --- a/services/auth/source/ldap/source_search.go +++ b/services/auth/source/ldap/source_search.go @@ -267,7 +267,8 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResul return nil } - var isAttributeSSHPublicKeySet = len(strings.TrimSpace(ls.AttributeSSHPublicKey)) > 0 + isAttributeSSHPublicKeySet := len(strings.TrimSpace(ls.AttributeSSHPublicKey)) > 0 + isAtributeAvatarSet := len(strings.TrimSpace(ls.AttributeAvatar)) > 0 attribs := []string{ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail} if len(strings.TrimSpace(ls.UserUID)) > 0 { @@ -276,8 +277,11 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResul if isAttributeSSHPublicKeySet { attribs = append(attribs, ls.AttributeSSHPublicKey) } + if isAtributeAvatarSet { + attribs = append(attribs, ls.AttributeAvatar) + } - log.Trace("Fetching attributes '%v', '%v', '%v', '%v', '%v', '%v' with filter '%s' and base '%s'", ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail, ls.AttributeSSHPublicKey, ls.UserUID, userFilter, userDN) + log.Trace("Fetching attributes '%v', '%v', '%v', '%v', '%v', '%v', '%v' with filter '%s' and base '%s'", ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail, ls.AttributeSSHPublicKey, ls.AttributeAvatar, ls.UserUID, userFilter, userDN) search := ldap.NewSearchRequest( userDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, userFilter, attribs, nil) @@ -365,7 +369,7 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResul } } - if ls.AttributeAvatar != "" { + if isAtributeAvatarSet { Avatar = sr.Entries[0].GetRawAttributeValue(ls.AttributeAvatar) } @@ -410,13 +414,14 @@ func (ls *Source) SearchEntries() ([]*SearchResult, error) { userFilter := fmt.Sprintf(ls.Filter, "*") - var isAttributeSSHPublicKeySet = len(strings.TrimSpace(ls.AttributeSSHPublicKey)) > 0 + isAttributeSSHPublicKeySet := len(strings.TrimSpace(ls.AttributeSSHPublicKey)) > 0 + isAtributeAvatarSet := len(strings.TrimSpace(ls.AttributeAvatar)) > 0 attribs := []string{ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail} if isAttributeSSHPublicKeySet { attribs = append(attribs, ls.AttributeSSHPublicKey) } - if ls.AttributeAvatar != "" { + if isAtributeAvatarSet { attribs = append(attribs, ls.AttributeAvatar) } @@ -452,7 +457,7 @@ func (ls *Source) SearchEntries() ([]*SearchResult, error) { if isAttributeSSHPublicKeySet { result[i].SSHPublicKey = v.GetAttributeValues(ls.AttributeSSHPublicKey) } - if ls.AttributeAvatar != "" { + if isAtributeAvatarSet { result[i].Avatar = v.GetRawAttributeValue(ls.AttributeAvatar) } result[i].LowerName = strings.ToLower(result[i].Username) From 7927dd04ff3256d9441e912615edf5a646fe51d9 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Fri, 17 Sep 2021 23:53:48 +0100 Subject: [PATCH 6/7] use len()>0 rather than != "" Signed-off-by: Andrew Thornton --- services/auth/source/ldap/source_authenticate.go | 2 +- services/auth/source/ldap/source_sync.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go index 0b4e70937e23..e42d4d4296bb 100644 --- a/services/auth/source/ldap/source_authenticate.go +++ b/services/auth/source/ldap/source_authenticate.go @@ -95,7 +95,7 @@ func (source *Source) Authenticate(user *models.User, login, password string) (* err = models.RewriteAllPublicKeys() } - if err == nil && source.AttributeAvatar != "" { + if err == nil && len(source.AttributeAvatar) > 0 { _ = user.UploadAvatar(sr.Avatar) } diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go index edb6e8c92f3b..2df97aabd38c 100644 --- a/services/auth/source/ldap/source_sync.go +++ b/services/auth/source/ldap/source_sync.go @@ -121,7 +121,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { } } - if err == nil && source.AttributeAvatar != "" { + if err == nil && len(source.AttributeAvatar) > 0 { _ = usr.UploadAvatar(su.Avatar) } } else if updateExisting { @@ -158,7 +158,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { } if usr.IsUploadAvatarChanged(su.Avatar) { - if err == nil && source.AttributeAvatar != "" { + if err == nil && len(source.AttributeAvatar) > 0 { _ = usr.UploadAvatar(su.Avatar) } From 1f2b672ef16a87bda4d823e8bfaf7727a7f2c418 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Fri, 17 Sep 2021 23:56:50 +0100 Subject: [PATCH 7/7] slight shortcut in IsUploadAvatarChanged Signed-off-by: Andrew Thornton --- models/user_avatar.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/models/user_avatar.go b/models/user_avatar.go index 6eab0faa5c10..c01e0800aecf 100644 --- a/models/user_avatar.go +++ b/models/user_avatar.go @@ -154,8 +154,11 @@ func (u *User) UploadAvatar(data []byte) error { // IsUploadAvatarChanged returns true if the current user's avatar would be changed with the provided data func (u *User) IsUploadAvatarChanged(data []byte) bool { + if !u.UseCustomAvatar || len(u.Avatar) == 0 { + return true + } avatarID := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%d-%x", u.ID, md5.Sum(data))))) - return !u.UseCustomAvatar || u.Avatar != avatarID + return u.Avatar != avatarID } // DeleteAvatar deletes the user's custom avatar.