diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index e95e1d15c64c..517904957f1d 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -93,6 +93,10 @@ var ( Name: "skip-local-2fa", Usage: "Set to true to skip local 2fa for users authenticated by this source", }, + cli.StringFlag{ + Name: "avatar-attribute", + Usage: "The attribute of the user’s LDAP record containing the user’s avatar.", + }, } ldapBindDnCLIFlags = append(commonLdapCLIFlags, @@ -234,6 +238,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("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 c26cbdaf39c6..db1ba13bcd5b 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", + "--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", + 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)", @@ -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", + "--avatar-attribute", "avatar-simple full", "--user-dn", "cn=%s,ou=Users,dc=full-domain-simple,dc=org", }, loginSource: &login.Source{ @@ -288,6 +291,7 @@ func TestAddLdapSimpleAuth(t *testing.T) { AttributeSurname: "sn-simple full", AttributeMail: "mail-simple full", AttributeSSHPublicKey: "publickey-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)", @@ -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", + "--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", + 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)", @@ -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", + "--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", + 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 0bc8d70fdb53..b12400c57ec1 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. + - `--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. + - `--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. + - `--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. + - `--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/models/user_avatar.go b/models/user_avatar.go index cef21d5ea5eb..b8296568c272 100644 --- a/models/user_avatar.go +++ b/models/user_avatar.go @@ -132,6 +132,15 @@ 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 { + 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.Avatar != avatarID +} + // DeleteAvatar deletes the user's custom avatar. func (u *User) DeleteAvatar() error { aPath := u.CustomAvatarRelativePath() diff --git a/modules/git/repo.go b/modules/git/repo.go index e7d42dacb165..89af7aa9e1da 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -425,14 +425,24 @@ func (repo *Repository) CreateBundle(ctx context.Context, commit string, out io. } defer os.RemoveAll(tmp) - tmpFile := filepath.Join(tmp, "bundle") - args := []string{ - "bundle", - "create", - tmpFile, - commit, + env := append(os.Environ(), "GIT_OBJECT_DIRECTORY="+filepath.Join(repo.Path, "objects")) + _, err = NewCommandContext(ctx, "init", "--bare").RunInDirWithEnv(tmp, env) + if err != nil { + return err + } + + _, err = NewCommandContext(ctx, "reset", "--soft", commit).RunInDirWithEnv(tmp, env) + if err != nil { + return err } - _, err = NewCommandContext(ctx, args...).RunInDir(repo.Path) + + _, err = NewCommandContext(ctx, "branch", "-m", "bundle").RunInDirWithEnv(tmp, env) + if err != nil { + return err + } + + tmpFile := filepath.Join(tmp, "bundle") + _, err = NewCommandContext(ctx, "bundle", "create", tmpFile, "bundle", "HEAD").RunInDirWithEnv(tmp, env) if err != nil { return err } diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 2ee0f5b56664..3d43b99c31db 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2421,6 +2421,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 = 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/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index c6ea930cd0bd..4e61469f4136 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -508,7 +508,7 @@ public_profile=Открытый профиль biography_placeholder=Расскажите немного о себе profile_desc=Ваш адрес электронной почты будет использован для уведомлений и других операций. password_username_disabled=Нелокальным пользователям запрещено изменение их имени пользователя. Для получения более подробной информации обратитесь к администратору сайта. -full_name=ФИО +full_name=Имя и фамилия website=Веб-сайт location=Местоположение update_theme=Обновить тему @@ -1202,8 +1202,8 @@ pulls.merged_by_fake=слито %[1]s пользователем %[2]s issues.closed_by=закрыт %[1]s пользователем %[3]s issues.opened_by_fake=открыт %[1]s пользователем %[2]s issues.closed_by_fake=закрыт %[1]s пользователем %[2]s -issues.previous=Предыдущая страница -issues.next=Следующая страница +issues.previous=Предыдущая +issues.next=Следующая issues.open_title=Открыто issues.closed_title=Закрыто issues.num_comments=комментариев: %d diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go index 1b005e5c7bce..803dcafa28bd 100644 --- a/routers/web/admin/auths.go +++ b/routers/web/admin/auths.go @@ -136,6 +136,7 @@ func parseLDAPConfig(form forms.AuthenticationForm) *ldap.Source { AttributeMail: form.AttributeMail, AttributesInBind: form.AttributesInBind, AttributeSSHPublicKey: form.AttributeSSHPublicKey, + AttributeAvatar: form.AttributeAvatar, SearchPageSize: pageSize, Filter: form.Filter, GroupsEnabled: form.GroupsEnabled, @@ -161,6 +162,7 @@ func parseSMTPConfig(form forms.AuthenticationForm) *smtp.Source { SkipVerify: form.SkipVerify, HeloHostname: form.HeloHostname, DisableHelo: form.DisableHelo, + SkipLocalTwoFA: form.SkipLocalTwoFA, } } @@ -244,8 +246,9 @@ func NewAuthSourcePost(ctx *context.Context) { hasTLS = true case login.PAM: config = &pamService.Source{ - ServiceName: form.PAMServiceName, - EmailDomain: form.PAMEmailDomain, + ServiceName: form.PAMServiceName, + EmailDomain: form.PAMEmailDomain, + SkipLocalTwoFA: form.SkipLocalTwoFA, } case login.OAuth2: config = parseOAuth2Config(form) diff --git a/routers/web/user/setting/keys.go b/routers/web/user/setting/keys.go index bb7a50841bb8..22a0fe474171 100644 --- a/routers/web/user/setting/keys.go +++ b/routers/web/user/setting/keys.go @@ -209,7 +209,7 @@ func DeleteKey(ctx *context.Context) { return } if external { - ctx.Flash.Error(ctx.Tr("setting.ssh_externally_managed")) + ctx.Flash.Error(ctx.Tr("settings.ssh_externally_managed")) ctx.Redirect(setting.AppSubURL + "/user/settings/keys") return } diff --git a/routers/web/user/setting/security_twofa.go b/routers/web/user/setting/security_twofa.go index 5b1cbab17fe7..94f975f9fe6f 100644 --- a/routers/web/user/setting/security_twofa.go +++ b/routers/web/user/setting/security_twofa.go @@ -32,7 +32,7 @@ func RegenerateScratchTwoFactor(ctx *context.Context) { t, err := login.GetTwoFactorByUID(ctx.User.ID) if err != nil { if login.IsErrTwoFactorNotEnrolled(err) { - ctx.Flash.Error(ctx.Tr("setting.twofa_not_enrolled")) + ctx.Flash.Error(ctx.Tr("settings.twofa_not_enrolled")) ctx.Redirect(setting.AppSubURL + "/user/settings/security") } ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err) @@ -62,7 +62,7 @@ func DisableTwoFactor(ctx *context.Context) { t, err := login.GetTwoFactorByUID(ctx.User.ID) if err != nil { if login.IsErrTwoFactorNotEnrolled(err) { - ctx.Flash.Error(ctx.Tr("setting.twofa_not_enrolled")) + ctx.Flash.Error(ctx.Tr("settings.twofa_not_enrolled")) ctx.Redirect(setting.AppSubURL + "/user/settings/security") } ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err) @@ -150,7 +150,7 @@ func EnrollTwoFactor(ctx *context.Context) { if t != nil { // already enrolled - we should redirect back! log.Warn("Trying to re-enroll %-v in twofa when already enrolled", ctx.User) - ctx.Flash.Error(ctx.Tr("setting.twofa_is_enrolled")) + ctx.Flash.Error(ctx.Tr("settings.twofa_is_enrolled")) ctx.Redirect(setting.AppSubURL + "/user/settings/security") return } @@ -175,7 +175,7 @@ func EnrollTwoFactorPost(ctx *context.Context) { t, err := login.GetTwoFactorByUID(ctx.User.ID) if t != nil { // already enrolled - ctx.Flash.Error(ctx.Tr("setting.twofa_is_enrolled")) + ctx.Flash.Error(ctx.Tr("settings.twofa_is_enrolled")) ctx.Redirect(setting.AppSubURL + "/user/settings/security") return } diff --git a/services/archiver/archiver.go b/services/archiver/archiver.go index 6d4d46e4e02c..d602b9ed7fd5 100644 --- a/services/archiver/archiver.go +++ b/services/archiver/archiver.go @@ -136,9 +136,11 @@ func doArchive(r *ArchiveRequest) (*models.RepoArchiver, error) { if err == nil { if archiver.Status == models.RepoArchiverGenerating { archiver.Status = models.RepoArchiverReady - return archiver, models.UpdateRepoArchiverStatus(ctx, archiver) + if err = models.UpdateRepoArchiverStatus(ctx, archiver); err != nil { + return nil, err + } } - return archiver, nil + return archiver, committer.Commit() } if !errors.Is(err, os.ErrNotExist) { diff --git a/services/auth/source/ldap/source.go b/services/auth/source/ldap/source.go index 82ff7313b288..3e751f512b12 100644 --- a/services/auth/source/ldap/source.go +++ b/services/auth/source/ldap/source.go @@ -42,6 +42,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 + 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 @@ -53,7 +54,7 @@ type Source struct { GroupFilter string // Group Name Filter GroupMemberUID string // Group Attribute containing array of UserUID UserUID string // User Attribute listed in Group - SkipLocalTwoFA bool // Skip Local 2fa for users authenticated with this source + SkipLocalTwoFA bool `json:",omitempty"` // Skip Local 2fa for users authenticated with this source // reference to the loginSource loginSource *login.Source diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go index f302a9d5837f..2719b5b7154f 100644 --- a/services/auth/source/ldap/source_authenticate.go +++ b/services/auth/source/ldap/source_authenticate.go @@ -96,6 +96,10 @@ func (source *Source) Authenticate(user *models.User, userName, password string) err = models.RewriteAllPublicKeys() } + if err == nil && len(source.AttributeAvatar) > 0 { + _ = 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 9fe2443768d8..1f1cca270d40 100644 --- a/services/auth/source/ldap/source_search.go +++ b/services/auth/source/ldap/source_search.go @@ -27,6 +27,7 @@ type SearchResult struct { IsAdmin bool // if user is administrator IsRestricted bool // if user is restricted LowerName string // Lowername + Avatar []byte } func (ls *Source) sanitizedUserQuery(username string) (string, bool) { @@ -266,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 { @@ -275,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) @@ -296,6 +301,7 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResul } var sshPublicKey []string + var Avatar []byte username := sr.Entries[0].GetAttributeValue(ls.AttributeUsername) firstname := sr.Entries[0].GetAttributeValue(ls.AttributeName) @@ -363,6 +369,10 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResul } } + if isAtributeAvatarSet { + Avatar = sr.Entries[0].GetRawAttributeValue(ls.AttributeAvatar) + } + return &SearchResult{ LowerName: strings.ToLower(username), Username: username, @@ -372,6 +382,7 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResul SSHPublicKey: sshPublicKey, IsAdmin: isAdmin, IsRestricted: isRestricted, + Avatar: Avatar, } } @@ -403,14 +414,18 @@ 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 isAtributeAvatarSet { + 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) @@ -442,8 +457,10 @@ func (ls *Source) SearchEntries() ([]*SearchResult, error) { if isAttributeSSHPublicKeySet { result[i].SSHPublicKey = v.GetAttributeValues(ls.AttributeSSHPublicKey) } + if isAtributeAvatarSet { + result[i].Avatar = v.GetRawAttributeValue(ls.AttributeAvatar) + } result[i].LowerName = strings.ToLower(result[i].Username) - } return result, nil diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go index f03e29f92049..2df97aabd38c 100644 --- a/services/auth/source/ldap/source_sync.go +++ b/services/auth/source/ldap/source_sync.go @@ -112,12 +112,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 && len(source.AttributeAvatar) > 0 { + _ = usr.UploadAvatar(su.Avatar) + } } else if updateExisting { // Synchronize SSH Public Key if that attribute is set if isAttributeSSHPublicKeySet && models.SynchronizePublicKeys(usr, source.loginSource, su.SSHPublicKey) { @@ -150,6 +156,13 @@ 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 usr.IsUploadAvatarChanged(su.Avatar) { + if err == nil && len(source.AttributeAvatar) > 0 { + _ = usr.UploadAvatar(su.Avatar) + } + + } } } diff --git a/services/auth/source/oauth2/source.go b/services/auth/source/oauth2/source.go index 49bb9a0148fb..60845e3b0f83 100644 --- a/services/auth/source/oauth2/source.go +++ b/services/auth/source/oauth2/source.go @@ -25,7 +25,7 @@ type Source struct { OpenIDConnectAutoDiscoveryURL string CustomURLMapping *CustomURLMapping IconURL string - SkipLocalTwoFA bool + SkipLocalTwoFA bool `json:",omitempty"` // reference to the loginSource loginSource *login.Source diff --git a/services/auth/source/pam/source.go b/services/auth/source/pam/source.go index 0bfa7cdb06b8..73850cd9a280 100644 --- a/services/auth/source/pam/source.go +++ b/services/auth/source/pam/source.go @@ -19,8 +19,9 @@ import ( // Source holds configuration for the PAM login source. type Source struct { - ServiceName string // pam service (e.g. system-auth) - EmailDomain string + ServiceName string // pam service (e.g. system-auth) + EmailDomain string + SkipLocalTwoFA bool `json:",omitempty"` // Skip Local 2fa for users authenticated with this source // reference to the loginSource loginSource *login.Source diff --git a/services/auth/source/pam/source_authenticate.go b/services/auth/source/pam/source_authenticate.go index ad6fbb5cce8f..cb5ffc2861f3 100644 --- a/services/auth/source/pam/source_authenticate.go +++ b/services/auth/source/pam/source_authenticate.go @@ -69,3 +69,8 @@ func (source *Source) Authenticate(user *models.User, userName, password string) return user, nil } + +// IsSkipLocalTwoFA returns if this source should skip local 2fa for password authentication +func (source *Source) IsSkipLocalTwoFA() bool { + return source.SkipLocalTwoFA +} diff --git a/services/auth/source/smtp/source.go b/services/auth/source/smtp/source.go index 487375c3044b..52e2505670b5 100644 --- a/services/auth/source/smtp/source.go +++ b/services/auth/source/smtp/source.go @@ -27,6 +27,7 @@ type Source struct { SkipVerify bool HeloHostname string DisableHelo bool + SkipLocalTwoFA bool `json:",omitempty"` // reference to the loginSource loginSource *login.Source diff --git a/services/auth/source/smtp/source_authenticate.go b/services/auth/source/smtp/source_authenticate.go index f50baa56a253..f51c884c3a40 100644 --- a/services/auth/source/smtp/source_authenticate.go +++ b/services/auth/source/smtp/source_authenticate.go @@ -85,3 +85,8 @@ func (source *Source) Authenticate(user *models.User, userName, password string) return user, nil } + +// IsSkipLocalTwoFA returns if this source should skip local 2fa for password authentication +func (source *Source) IsSkipLocalTwoFA() bool { + return source.SkipLocalTwoFA +} diff --git a/services/forms/auth_form.go b/services/forms/auth_form.go index 229728cf7daf..2c6966d26687 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 + AttributeAvatar string AttributesInBind bool UsePagedSearch bool SearchPageSize int diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl index 9ff80663846b..2f77e9bd801d 100644 --- a/templates/admin/auth/edit.tmpl +++ b/templates/admin/auth/edit.tmpl @@ -104,6 +104,10 @@ +
+ + +
@@ -215,6 +219,13 @@

{{.i18n.Tr "admin.auths.allowed_domains_helper"}}

+
+
+ + +

{{.i18n.Tr "admin.auths.skip_local_two_fa_helper"}}

+
+
{{end}} @@ -228,6 +239,13 @@
+
+
+ + +

{{.i18n.Tr "admin.auths.skip_local_two_fa_helper"}}

+
+
{{end}} diff --git a/templates/admin/auth/new.tmpl b/templates/admin/auth/new.tmpl index ba1f145a4a3b..13e1366c874e 100644 --- a/templates/admin/auth/new.tmpl +++ b/templates/admin/auth/new.tmpl @@ -41,6 +41,13 @@ +
+
+ + +

{{.i18n.Tr "admin.auths.skip_local_two_fa_helper"}}

+
+
{{ template "admin/auth/source/oauth" . }} diff --git a/templates/admin/auth/source/ldap.tmpl b/templates/admin/auth/source/ldap.tmpl index b8018f5b548c..b553502b9402 100644 --- a/templates/admin/auth/source/ldap.tmpl +++ b/templates/admin/auth/source/ldap.tmpl @@ -76,6 +76,10 @@ +
+ + +
diff --git a/templates/admin/auth/source/smtp.tmpl b/templates/admin/auth/source/smtp.tmpl index b0f643b8ca68..8572d6dc56eb 100644 --- a/templates/admin/auth/source/smtp.tmpl +++ b/templates/admin/auth/source/smtp.tmpl @@ -49,4 +49,11 @@

{{.i18n.Tr "admin.auths.allowed_domains_helper"}}

+
+
+ + +

{{.i18n.Tr "admin.auths.skip_local_two_fa_helper"}}

+
+