mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	LDAP user synchronization (#1478)
This commit is contained in:
		
				
					committed by
					
						
						Kim "BKC" Carlbäcker
					
				
			
			
				
	
			
			
			
						parent
						
							fd76f090a2
						
					
				
				
					commit
					524885dd65
				
			
							
								
								
									
										10
									
								
								conf/app.ini
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								conf/app.ini
									
									
									
									
										vendored
									
									
								
							@@ -442,6 +442,16 @@ SCHEDULE = @every 24h
 | 
				
			|||||||
; Archives created more than OLDER_THAN ago are subject to deletion
 | 
					; Archives created more than OLDER_THAN ago are subject to deletion
 | 
				
			||||||
OLDER_THAN = 24h
 | 
					OLDER_THAN = 24h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					; Synchronize external user data (only LDAP user synchronization is supported)
 | 
				
			||||||
 | 
					[cron.sync_external_users]
 | 
				
			||||||
 | 
					; Syncronize external user data when starting server (default false)
 | 
				
			||||||
 | 
					RUN_AT_START = false
 | 
				
			||||||
 | 
					; Interval as a duration between each synchronization (default every 24h)
 | 
				
			||||||
 | 
					SCHEDULE = @every 24h
 | 
				
			||||||
 | 
					; Create new users, update existing user data and disable users that are not in external source anymore (default)
 | 
				
			||||||
 | 
					;   or only create new users if UPDATE_EXISTING is set to false
 | 
				
			||||||
 | 
					UPDATE_EXISTING = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[git]
 | 
					[git]
 | 
				
			||||||
; Disables highlight of added and removed changes
 | 
					; Disables highlight of added and removed changes
 | 
				
			||||||
DISABLE_DIFF_HIGHLIGHT = false
 | 
					DISABLE_DIFF_HIGHLIGHT = false
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -144,6 +144,7 @@ type LoginSource struct {
 | 
				
			|||||||
	Type          LoginType
 | 
						Type          LoginType
 | 
				
			||||||
	Name          string          `xorm:"UNIQUE"`
 | 
						Name          string          `xorm:"UNIQUE"`
 | 
				
			||||||
	IsActived     bool            `xorm:"INDEX NOT NULL DEFAULT false"`
 | 
						IsActived     bool            `xorm:"INDEX NOT NULL DEFAULT false"`
 | 
				
			||||||
 | 
						IsSyncEnabled bool            `xorm:"INDEX NOT NULL DEFAULT false"`
 | 
				
			||||||
	Cfg           core.Conversion `xorm:"TEXT"`
 | 
						Cfg           core.Conversion `xorm:"TEXT"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Created     time.Time `xorm:"-"`
 | 
						Created     time.Time `xorm:"-"`
 | 
				
			||||||
@@ -294,6 +295,10 @@ func CreateLoginSource(source *LoginSource) error {
 | 
				
			|||||||
	} else if has {
 | 
						} else if has {
 | 
				
			||||||
		return ErrLoginSourceAlreadyExist{source.Name}
 | 
							return ErrLoginSourceAlreadyExist{source.Name}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						// Synchronization is only aviable with LDAP for now
 | 
				
			||||||
 | 
						if !source.IsLDAP() {
 | 
				
			||||||
 | 
							source.IsSyncEnabled = false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err = x.Insert(source)
 | 
						_, err = x.Insert(source)
 | 
				
			||||||
	if err == nil && source.IsOAuth2() && source.IsActived {
 | 
						if err == nil && source.IsOAuth2() && source.IsActived {
 | 
				
			||||||
@@ -405,8 +410,8 @@ func composeFullName(firstname, surname, username string) string {
 | 
				
			|||||||
// LoginViaLDAP queries if login/password is valid against the LDAP directory pool,
 | 
					// LoginViaLDAP queries if login/password is valid against the LDAP directory pool,
 | 
				
			||||||
// and create a local user if success when enabled.
 | 
					// and create a local user if success when enabled.
 | 
				
			||||||
func LoginViaLDAP(user *User, login, password string, source *LoginSource, autoRegister bool) (*User, error) {
 | 
					func LoginViaLDAP(user *User, login, password string, source *LoginSource, autoRegister bool) (*User, error) {
 | 
				
			||||||
	username, fn, sn, mail, isAdmin, succeed := source.Cfg.(*LDAPConfig).SearchEntry(login, password, source.Type == LoginDLDAP)
 | 
						sr := source.Cfg.(*LDAPConfig).SearchEntry(login, password, source.Type == LoginDLDAP)
 | 
				
			||||||
	if !succeed {
 | 
						if sr == nil {
 | 
				
			||||||
		// User not in LDAP, do nothing
 | 
							// User not in LDAP, do nothing
 | 
				
			||||||
		return nil, ErrUserNotExist{0, login, 0}
 | 
							return nil, ErrUserNotExist{0, login, 0}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -416,28 +421,28 @@ func LoginViaLDAP(user *User, login, password string, source *LoginSource, autoR
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Fallback.
 | 
						// Fallback.
 | 
				
			||||||
	if len(username) == 0 {
 | 
						if len(sr.Username) == 0 {
 | 
				
			||||||
		username = login
 | 
							sr.Username = login
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// Validate username make sure it satisfies requirement.
 | 
						// Validate username make sure it satisfies requirement.
 | 
				
			||||||
	if binding.AlphaDashDotPattern.MatchString(username) {
 | 
						if binding.AlphaDashDotPattern.MatchString(sr.Username) {
 | 
				
			||||||
		return nil, fmt.Errorf("Invalid pattern for attribute 'username' [%s]: must be valid alpha or numeric or dash(-_) or dot characters", username)
 | 
							return nil, fmt.Errorf("Invalid pattern for attribute 'username' [%s]: must be valid alpha or numeric or dash(-_) or dot characters", sr.Username)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if len(mail) == 0 {
 | 
						if len(sr.Mail) == 0 {
 | 
				
			||||||
		mail = fmt.Sprintf("%s@localhost", username)
 | 
							sr.Mail = fmt.Sprintf("%s@localhost", sr.Username)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	user = &User{
 | 
						user = &User{
 | 
				
			||||||
		LowerName:   strings.ToLower(username),
 | 
							LowerName:   strings.ToLower(sr.Username),
 | 
				
			||||||
		Name:        username,
 | 
							Name:        sr.Username,
 | 
				
			||||||
		FullName:    composeFullName(fn, sn, username),
 | 
							FullName:    composeFullName(sr.Name, sr.Surname, sr.Username),
 | 
				
			||||||
		Email:       mail,
 | 
							Email:       sr.Mail,
 | 
				
			||||||
		LoginType:   source.Type,
 | 
							LoginType:   source.Type,
 | 
				
			||||||
		LoginSource: source.ID,
 | 
							LoginSource: source.ID,
 | 
				
			||||||
		LoginName:   login,
 | 
							LoginName:   login,
 | 
				
			||||||
		IsActive:    true,
 | 
							IsActive:    true,
 | 
				
			||||||
		IsAdmin:     isAdmin,
 | 
							IsAdmin:     sr.IsAdmin,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return user, CreateUser(user)
 | 
						return user, CreateUser(user)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -110,6 +110,8 @@ var migrations = []Migration{
 | 
				
			|||||||
	NewMigration("add commit status table", addCommitStatus),
 | 
						NewMigration("add commit status table", addCommitStatus),
 | 
				
			||||||
	// v30 -> 31
 | 
						// v30 -> 31
 | 
				
			||||||
	NewMigration("add primary key to external login user", addExternalLoginUserPK),
 | 
						NewMigration("add primary key to external login user", addExternalLoginUserPK),
 | 
				
			||||||
 | 
						// 31 -> 32
 | 
				
			||||||
 | 
						NewMigration("add field for login source synchronization", addLoginSourceSyncEnabledColumn),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Migrate database to current version
 | 
					// Migrate database to current version
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										35
									
								
								models/migrations/v31.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								models/migrations/v31.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					// Copyright 2017 The Gogs 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"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-xorm/core"
 | 
				
			||||||
 | 
						"github.com/go-xorm/xorm"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func addLoginSourceSyncEnabledColumn(x *xorm.Engine) error {
 | 
				
			||||||
 | 
						// LoginSource see models/login_source.go
 | 
				
			||||||
 | 
						type LoginSource struct {
 | 
				
			||||||
 | 
							ID            int64 `xorm:"pk autoincr"`
 | 
				
			||||||
 | 
							Type          int
 | 
				
			||||||
 | 
							Name          string          `xorm:"UNIQUE"`
 | 
				
			||||||
 | 
							IsActived     bool            `xorm:"INDEX NOT NULL DEFAULT false"`
 | 
				
			||||||
 | 
							IsSyncEnabled bool            `xorm:"INDEX NOT NULL DEFAULT false"`
 | 
				
			||||||
 | 
							Cfg           core.Conversion `xorm:"TEXT"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Created     time.Time `xorm:"-"`
 | 
				
			||||||
 | 
							CreatedUnix int64     `xorm:"INDEX"`
 | 
				
			||||||
 | 
							Updated     time.Time `xorm:"-"`
 | 
				
			||||||
 | 
							UpdatedUnix int64     `xorm:"INDEX"`
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := x.Sync2(new(LoginSource)); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("Sync2: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										127
									
								
								models/user.go
									
									
									
									
									
								
							
							
						
						
									
										127
									
								
								models/user.go
									
									
									
									
									
								
							@@ -50,6 +50,8 @@ const (
 | 
				
			|||||||
	UserTypeOrganization
 | 
						UserTypeOrganization
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const syncExternalUsers = "sync_external_users"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
	// ErrUserNotKeyOwner user does not own this key error
 | 
						// ErrUserNotKeyOwner user does not own this key error
 | 
				
			||||||
	ErrUserNotKeyOwner = errors.New("User does not own this public key")
 | 
						ErrUserNotKeyOwner = errors.New("User does not own this public key")
 | 
				
			||||||
@@ -1322,3 +1324,128 @@ func GetWatchedRepos(userID int64, private bool) ([]*Repository, error) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	return repos, nil
 | 
						return repos, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SyncExternalUsers is used to synchronize users with external authorization source
 | 
				
			||||||
 | 
					func SyncExternalUsers() {
 | 
				
			||||||
 | 
						if taskStatusTable.IsRunning(syncExternalUsers) {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						taskStatusTable.Start(syncExternalUsers)
 | 
				
			||||||
 | 
						defer taskStatusTable.Stop(syncExternalUsers)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Trace("Doing: SyncExternalUsers")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ls, err := LoginSources()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error(4, "SyncExternalUsers: %v", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						updateExisting := setting.Cron.SyncExternalUsers.UpdateExisting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, s := range ls {
 | 
				
			||||||
 | 
							if !s.IsActived || !s.IsSyncEnabled {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if s.IsLDAP() {
 | 
				
			||||||
 | 
								log.Trace("Doing: SyncExternalUsers[%s]", s.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								var existingUsers []int64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Find all users with this login type
 | 
				
			||||||
 | 
								var users []User
 | 
				
			||||||
 | 
								x.Where("login_type = ?", LoginLDAP).
 | 
				
			||||||
 | 
									And("login_source = ?", s.ID).
 | 
				
			||||||
 | 
									Find(&users)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								sr := s.LDAP().SearchEntries()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for _, su := range sr {
 | 
				
			||||||
 | 
									if len(su.Username) == 0 {
 | 
				
			||||||
 | 
										continue
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if len(su.Mail) == 0 {
 | 
				
			||||||
 | 
										su.Mail = fmt.Sprintf("%s@localhost", su.Username)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									var usr *User
 | 
				
			||||||
 | 
									// Search for existing user
 | 
				
			||||||
 | 
									for _, du := range users {
 | 
				
			||||||
 | 
										if du.LowerName == strings.ToLower(su.Username) {
 | 
				
			||||||
 | 
											usr = &du
 | 
				
			||||||
 | 
											break
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									fullName := composeFullName(su.Name, su.Surname, su.Username)
 | 
				
			||||||
 | 
									// If no existing user found, create one
 | 
				
			||||||
 | 
									if usr == nil {
 | 
				
			||||||
 | 
										log.Trace("SyncExternalUsers[%s]: Creating user %s", s.Name, su.Username)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										usr = &User{
 | 
				
			||||||
 | 
											LowerName:   strings.ToLower(su.Username),
 | 
				
			||||||
 | 
											Name:        su.Username,
 | 
				
			||||||
 | 
											FullName:    fullName,
 | 
				
			||||||
 | 
											LoginType:   s.Type,
 | 
				
			||||||
 | 
											LoginSource: s.ID,
 | 
				
			||||||
 | 
											LoginName:   su.Username,
 | 
				
			||||||
 | 
											Email:       su.Mail,
 | 
				
			||||||
 | 
											IsAdmin:     su.IsAdmin,
 | 
				
			||||||
 | 
											IsActive:    true,
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										err = CreateUser(usr)
 | 
				
			||||||
 | 
										if err != nil {
 | 
				
			||||||
 | 
											log.Error(4, "SyncExternalUsers[%s]: Error creating 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 = UpdateUser(usr)
 | 
				
			||||||
 | 
											if err != nil {
 | 
				
			||||||
 | 
												log.Error(4, "SyncExternalUsers[%s]: Error updating user %s: %v", s.Name, usr.Name, err)
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Deactivate users not present in LDAP
 | 
				
			||||||
 | 
								if updateExisting {
 | 
				
			||||||
 | 
									for _, usr := range users {
 | 
				
			||||||
 | 
										found := false
 | 
				
			||||||
 | 
										for _, uid := range existingUsers {
 | 
				
			||||||
 | 
											if usr.ID == uid {
 | 
				
			||||||
 | 
												found = true
 | 
				
			||||||
 | 
												break
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										if !found {
 | 
				
			||||||
 | 
											log.Trace("SyncExternalUsers[%s]: Deactivating user %s", s.Name, usr.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											usr.IsActive = false
 | 
				
			||||||
 | 
											err = UpdateUser(&usr)
 | 
				
			||||||
 | 
											if err != nil {
 | 
				
			||||||
 | 
												log.Error(4, "SyncExternalUsers[%s]: Error deactivating user %s: %v", s.Name, usr.Name, err)
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,6 +28,7 @@ type AuthenticationForm struct {
 | 
				
			|||||||
	Filter                        string
 | 
						Filter                        string
 | 
				
			||||||
	AdminFilter                   string
 | 
						AdminFilter                   string
 | 
				
			||||||
	IsActive                      bool
 | 
						IsActive                      bool
 | 
				
			||||||
 | 
						IsSyncEnabled                 bool
 | 
				
			||||||
	SMTPAuth                      string
 | 
						SMTPAuth                      string
 | 
				
			||||||
	SMTPHost                      string
 | 
						SMTPHost                      string
 | 
				
			||||||
	SMTPPort                      int
 | 
						SMTPPort                      int
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -47,6 +47,15 @@ type Source struct {
 | 
				
			|||||||
	Enabled           bool   // if this source is disabled
 | 
						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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (ls *Source) sanitizedUserQuery(username string) (string, bool) {
 | 
					func (ls *Source) sanitizedUserQuery(username string) (string, bool) {
 | 
				
			||||||
	// See http://tools.ietf.org/search/rfc4515
 | 
						// See http://tools.ietf.org/search/rfc4515
 | 
				
			||||||
	badCharacters := "\x00()*\\"
 | 
						badCharacters := "\x00()*\\"
 | 
				
			||||||
@@ -149,18 +158,39 @@ func bindUser(l *ldap.Conn, userDN, passwd string) error {
 | 
				
			|||||||
	return err
 | 
						return err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func checkAdmin(l *ldap.Conn, ls *Source, userDN string) bool {
 | 
				
			||||||
 | 
						if len(ls.AdminFilter) > 0 {
 | 
				
			||||||
 | 
							log.Trace("Checking admin with filter %s and base %s", ls.AdminFilter, userDN)
 | 
				
			||||||
 | 
							search := ldap.NewSearchRequest(
 | 
				
			||||||
 | 
								userDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, ls.AdminFilter,
 | 
				
			||||||
 | 
								[]string{ls.AttributeName},
 | 
				
			||||||
 | 
								nil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							sr, err := l.Search(search)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error(4, "LDAP Admin Search failed unexpectedly! (%v)", err)
 | 
				
			||||||
 | 
							} else if len(sr.Entries) < 1 {
 | 
				
			||||||
 | 
								log.Error(4, "LDAP Admin Search failed")
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SearchEntry : search an LDAP source if an entry (name, passwd) is valid and in the specific filter
 | 
					// SearchEntry : search an LDAP source if an entry (name, passwd) is valid and in the specific filter
 | 
				
			||||||
func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, string, string, string, bool, bool) {
 | 
					func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResult {
 | 
				
			||||||
	// See https://tools.ietf.org/search/rfc4513#section-5.1.2
 | 
						// See https://tools.ietf.org/search/rfc4513#section-5.1.2
 | 
				
			||||||
	if len(passwd) == 0 {
 | 
						if len(passwd) == 0 {
 | 
				
			||||||
		log.Debug("Auth. failed for %s, password cannot be empty")
 | 
							log.Debug("Auth. failed for %s, password cannot be empty")
 | 
				
			||||||
		return "", "", "", "", false, false
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	l, err := dial(ls)
 | 
						l, err := dial(ls)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Error(4, "LDAP Connect error, %s:%v", ls.Host, err)
 | 
							log.Error(4, "LDAP Connect error, %s:%v", ls.Host, err)
 | 
				
			||||||
		ls.Enabled = false
 | 
							ls.Enabled = false
 | 
				
			||||||
		return "", "", "", "", false, false
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	defer l.Close()
 | 
						defer l.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -171,7 +201,7 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str
 | 
				
			|||||||
		var ok bool
 | 
							var ok bool
 | 
				
			||||||
		userDN, ok = ls.sanitizedUserDN(name)
 | 
							userDN, ok = ls.sanitizedUserDN(name)
 | 
				
			||||||
		if !ok {
 | 
							if !ok {
 | 
				
			||||||
			return "", "", "", "", false, false
 | 
								return nil
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		log.Trace("LDAP will use BindDN.")
 | 
							log.Trace("LDAP will use BindDN.")
 | 
				
			||||||
@@ -179,7 +209,7 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str
 | 
				
			|||||||
		var found bool
 | 
							var found bool
 | 
				
			||||||
		userDN, found = ls.findUserDN(l, name)
 | 
							userDN, found = ls.findUserDN(l, name)
 | 
				
			||||||
		if !found {
 | 
							if !found {
 | 
				
			||||||
			return "", "", "", "", false, false
 | 
								return nil
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -187,13 +217,13 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str
 | 
				
			|||||||
		// binds user (checking password) before looking-up attributes in user context
 | 
							// binds user (checking password) before looking-up attributes in user context
 | 
				
			||||||
		err = bindUser(l, userDN, passwd)
 | 
							err = bindUser(l, userDN, passwd)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return "", "", "", "", false, false
 | 
								return nil
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	userFilter, ok := ls.sanitizedUserQuery(name)
 | 
						userFilter, ok := ls.sanitizedUserQuery(name)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return "", "", "", "", false, false
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Trace("Fetching attributes '%v', '%v', '%v', '%v' with filter %s and base %s", ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail, userFilter, userDN)
 | 
						log.Trace("Fetching attributes '%v', '%v', '%v', '%v' with filter %s and base %s", ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail, userFilter, userDN)
 | 
				
			||||||
@@ -205,7 +235,7 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str
 | 
				
			|||||||
	sr, err := l.Search(search)
 | 
						sr, err := l.Search(search)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Error(4, "LDAP Search failed unexpectedly! (%v)", err)
 | 
							log.Error(4, "LDAP Search failed unexpectedly! (%v)", err)
 | 
				
			||||||
		return "", "", "", "", false, false
 | 
							return nil
 | 
				
			||||||
	} else if len(sr.Entries) < 1 {
 | 
						} else if len(sr.Entries) < 1 {
 | 
				
			||||||
		if directBind {
 | 
							if directBind {
 | 
				
			||||||
			log.Error(4, "User filter inhibited user login.")
 | 
								log.Error(4, "User filter inhibited user login.")
 | 
				
			||||||
@@ -213,39 +243,78 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str
 | 
				
			|||||||
			log.Error(4, "LDAP Search failed unexpectedly! (0 entries)")
 | 
								log.Error(4, "LDAP Search failed unexpectedly! (0 entries)")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return "", "", "", "", false, false
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	username := sr.Entries[0].GetAttributeValue(ls.AttributeUsername)
 | 
						username := sr.Entries[0].GetAttributeValue(ls.AttributeUsername)
 | 
				
			||||||
	firstname := sr.Entries[0].GetAttributeValue(ls.AttributeName)
 | 
						firstname := sr.Entries[0].GetAttributeValue(ls.AttributeName)
 | 
				
			||||||
	surname := sr.Entries[0].GetAttributeValue(ls.AttributeSurname)
 | 
						surname := sr.Entries[0].GetAttributeValue(ls.AttributeSurname)
 | 
				
			||||||
	mail := sr.Entries[0].GetAttributeValue(ls.AttributeMail)
 | 
						mail := sr.Entries[0].GetAttributeValue(ls.AttributeMail)
 | 
				
			||||||
 | 
						isAdmin := checkAdmin(l, ls, userDN)
 | 
				
			||||||
	isAdmin := false
 | 
					 | 
				
			||||||
	if len(ls.AdminFilter) > 0 {
 | 
					 | 
				
			||||||
		log.Trace("Checking admin with filter %s and base %s", ls.AdminFilter, userDN)
 | 
					 | 
				
			||||||
		search = ldap.NewSearchRequest(
 | 
					 | 
				
			||||||
			userDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, ls.AdminFilter,
 | 
					 | 
				
			||||||
			[]string{ls.AttributeName},
 | 
					 | 
				
			||||||
			nil)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		sr, err = l.Search(search)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Error(4, "LDAP Admin Search failed unexpectedly! (%v)", err)
 | 
					 | 
				
			||||||
		} else if len(sr.Entries) < 1 {
 | 
					 | 
				
			||||||
			log.Error(4, "LDAP Admin Search failed")
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			isAdmin = true
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !directBind && ls.AttributesInBind {
 | 
						if !directBind && ls.AttributesInBind {
 | 
				
			||||||
		// binds user (checking password) after looking-up attributes in BindDN context
 | 
							// binds user (checking password) after looking-up attributes in BindDN context
 | 
				
			||||||
		err = bindUser(l, userDN, passwd)
 | 
							err = bindUser(l, userDN, passwd)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return "", "", "", "", false, false
 | 
								return nil
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return username, firstname, surname, mail, isAdmin, true
 | 
						return &SearchResult{
 | 
				
			||||||
 | 
							Username: username,
 | 
				
			||||||
 | 
							Name:     firstname,
 | 
				
			||||||
 | 
							Surname:  surname,
 | 
				
			||||||
 | 
							Mail:     mail,
 | 
				
			||||||
 | 
							IsAdmin:  isAdmin,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SearchEntries : search an LDAP source for all users matching userFilter
 | 
				
			||||||
 | 
					func (ls *Source) SearchEntries() []*SearchResult {
 | 
				
			||||||
 | 
						l, err := dial(ls)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error(4, "LDAP Connect error, %s:%v", ls.Host, err)
 | 
				
			||||||
 | 
							ls.Enabled = false
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer l.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if ls.BindDN != "" && ls.BindPassword != "" {
 | 
				
			||||||
 | 
							err := l.Bind(ls.BindDN, ls.BindPassword)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Debug("Failed to bind as BindDN[%s]: %v", ls.BindDN, err)
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							log.Trace("Bound as BindDN %s", ls.BindDN)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							log.Trace("Proceeding with anonymous LDAP search.")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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)
 | 
				
			||||||
 | 
						search := ldap.NewSearchRequest(
 | 
				
			||||||
 | 
							ls.UserBase, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, userFilter,
 | 
				
			||||||
 | 
							[]string{ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail},
 | 
				
			||||||
 | 
							nil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sr, err := l.Search(search)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error(4, "LDAP Search failed unexpectedly! (%v)", err)
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						result := make([]*SearchResult, len(sr.Entries))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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),
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return result
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -66,6 +66,17 @@ func NewContext() {
 | 
				
			|||||||
			go models.DeleteOldRepositoryArchives()
 | 
								go models.DeleteOldRepositoryArchives()
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if setting.Cron.SyncExternalUsers.Enabled {
 | 
				
			||||||
 | 
							entry, err = c.AddFunc("Synchronize external users", setting.Cron.SyncExternalUsers.Schedule, models.SyncExternalUsers)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Fatal(4, "Cron[Synchronize external users]: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if setting.Cron.SyncExternalUsers.RunAtStart {
 | 
				
			||||||
 | 
								entry.Prev = time.Now()
 | 
				
			||||||
 | 
								entry.ExecTimes++
 | 
				
			||||||
 | 
								go models.SyncExternalUsers()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	c.Start()
 | 
						c.Start()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -336,6 +336,12 @@ var (
 | 
				
			|||||||
			Schedule   string
 | 
								Schedule   string
 | 
				
			||||||
			OlderThan  time.Duration
 | 
								OlderThan  time.Duration
 | 
				
			||||||
		} `ini:"cron.archive_cleanup"`
 | 
							} `ini:"cron.archive_cleanup"`
 | 
				
			||||||
 | 
							SyncExternalUsers struct {
 | 
				
			||||||
 | 
								Enabled        bool
 | 
				
			||||||
 | 
								RunAtStart     bool
 | 
				
			||||||
 | 
								Schedule       string
 | 
				
			||||||
 | 
								UpdateExisting bool
 | 
				
			||||||
 | 
							} `ini:"cron.sync_external_users"`
 | 
				
			||||||
	}{
 | 
						}{
 | 
				
			||||||
		UpdateMirror: struct {
 | 
							UpdateMirror: struct {
 | 
				
			||||||
			Enabled    bool
 | 
								Enabled    bool
 | 
				
			||||||
@@ -379,6 +385,17 @@ var (
 | 
				
			|||||||
			Schedule:   "@every 24h",
 | 
								Schedule:   "@every 24h",
 | 
				
			||||||
			OlderThan:  24 * time.Hour,
 | 
								OlderThan:  24 * time.Hour,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							SyncExternalUsers: struct {
 | 
				
			||||||
 | 
								Enabled        bool
 | 
				
			||||||
 | 
								RunAtStart     bool
 | 
				
			||||||
 | 
								Schedule       string
 | 
				
			||||||
 | 
								UpdateExisting bool
 | 
				
			||||||
 | 
							}{
 | 
				
			||||||
 | 
								Enabled:        true,
 | 
				
			||||||
 | 
								RunAtStart:     false,
 | 
				
			||||||
 | 
								Schedule:       "@every 24h",
 | 
				
			||||||
 | 
								UpdateExisting: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Git settings
 | 
						// Git settings
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1065,7 +1065,8 @@ dashboard.resync_all_hooks = Resync pre-receive, update and post-receive hooks o
 | 
				
			|||||||
dashboard.resync_all_hooks_success = All repositories' pre-receive, update and post-receive hooks have been resynced successfully.
 | 
					dashboard.resync_all_hooks_success = All repositories' pre-receive, update and post-receive hooks have been resynced successfully.
 | 
				
			||||||
dashboard.reinit_missing_repos = Reinitialize all lost Git repositories for which records exist
 | 
					dashboard.reinit_missing_repos = Reinitialize all lost Git repositories for which records exist
 | 
				
			||||||
dashboard.reinit_missing_repos_success = All lost Git repositories for which records existed have been reinitialized successfully.
 | 
					dashboard.reinit_missing_repos_success = All lost Git repositories for which records existed have been reinitialized successfully.
 | 
				
			||||||
 | 
					dashboard.sync_external_users = Synchronize external user data
 | 
				
			||||||
 | 
					dashboard.sync_external_users_started = External user synchronization started
 | 
				
			||||||
dashboard.server_uptime = Server Uptime
 | 
					dashboard.server_uptime = Server Uptime
 | 
				
			||||||
dashboard.current_goroutine = Current Goroutines
 | 
					dashboard.current_goroutine = Current Goroutines
 | 
				
			||||||
dashboard.current_memory_usage = Current Memory Usage
 | 
					dashboard.current_memory_usage = Current Memory Usage
 | 
				
			||||||
@@ -1147,6 +1148,7 @@ auths.new = Add New Source
 | 
				
			|||||||
auths.name = Name
 | 
					auths.name = Name
 | 
				
			||||||
auths.type = Type
 | 
					auths.type = Type
 | 
				
			||||||
auths.enabled = Enabled
 | 
					auths.enabled = Enabled
 | 
				
			||||||
 | 
					auths.syncenabled = Enable user synchronization
 | 
				
			||||||
auths.updated = Updated
 | 
					auths.updated = Updated
 | 
				
			||||||
auths.auth_type = Authentication Type
 | 
					auths.auth_type = Authentication Type
 | 
				
			||||||
auths.auth_name = Authentication Name
 | 
					auths.auth_name = Authentication Name
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -121,6 +121,7 @@ const (
 | 
				
			|||||||
	syncSSHAuthorizedKey
 | 
						syncSSHAuthorizedKey
 | 
				
			||||||
	syncRepositoryUpdateHook
 | 
						syncRepositoryUpdateHook
 | 
				
			||||||
	reinitMissingRepository
 | 
						reinitMissingRepository
 | 
				
			||||||
 | 
						syncExternalUsers
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Dashboard show admin panel dashboard
 | 
					// Dashboard show admin panel dashboard
 | 
				
			||||||
@@ -157,6 +158,9 @@ func Dashboard(ctx *context.Context) {
 | 
				
			|||||||
		case reinitMissingRepository:
 | 
							case reinitMissingRepository:
 | 
				
			||||||
			success = ctx.Tr("admin.dashboard.reinit_missing_repos_success")
 | 
								success = ctx.Tr("admin.dashboard.reinit_missing_repos_success")
 | 
				
			||||||
			err = models.ReinitMissingRepositories()
 | 
								err = models.ReinitMissingRepositories()
 | 
				
			||||||
 | 
							case syncExternalUsers:
 | 
				
			||||||
 | 
								success = ctx.Tr("admin.dashboard.sync_external_users_started")
 | 
				
			||||||
 | 
								go models.SyncExternalUsers()
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -74,6 +74,7 @@ func NewAuthSource(ctx *context.Context) {
 | 
				
			|||||||
	ctx.Data["CurrentSecurityProtocol"] = models.SecurityProtocolNames[ldap.SecurityProtocolUnencrypted]
 | 
						ctx.Data["CurrentSecurityProtocol"] = models.SecurityProtocolNames[ldap.SecurityProtocolUnencrypted]
 | 
				
			||||||
	ctx.Data["smtp_auth"] = "PLAIN"
 | 
						ctx.Data["smtp_auth"] = "PLAIN"
 | 
				
			||||||
	ctx.Data["is_active"] = true
 | 
						ctx.Data["is_active"] = true
 | 
				
			||||||
 | 
						ctx.Data["is_sync_enabled"] = true
 | 
				
			||||||
	ctx.Data["AuthSources"] = authSources
 | 
						ctx.Data["AuthSources"] = authSources
 | 
				
			||||||
	ctx.Data["SecurityProtocols"] = securityProtocols
 | 
						ctx.Data["SecurityProtocols"] = securityProtocols
 | 
				
			||||||
	ctx.Data["SMTPAuths"] = models.SMTPAuths
 | 
						ctx.Data["SMTPAuths"] = models.SMTPAuths
 | 
				
			||||||
@@ -189,6 +190,7 @@ func NewAuthSourcePost(ctx *context.Context, form auth.AuthenticationForm) {
 | 
				
			|||||||
		Type:          models.LoginType(form.Type),
 | 
							Type:          models.LoginType(form.Type),
 | 
				
			||||||
		Name:          form.Name,
 | 
							Name:          form.Name,
 | 
				
			||||||
		IsActived:     form.IsActive,
 | 
							IsActived:     form.IsActive,
 | 
				
			||||||
 | 
							IsSyncEnabled: form.IsSyncEnabled,
 | 
				
			||||||
		Cfg:           config,
 | 
							Cfg:           config,
 | 
				
			||||||
	}); err != nil {
 | 
						}); err != nil {
 | 
				
			||||||
		if models.IsErrLoginSourceAlreadyExist(err) {
 | 
							if models.IsErrLoginSourceAlreadyExist(err) {
 | 
				
			||||||
@@ -273,6 +275,7 @@ func EditAuthSourcePost(ctx *context.Context, form auth.AuthenticationForm) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	source.Name = form.Name
 | 
						source.Name = form.Name
 | 
				
			||||||
	source.IsActived = form.IsActive
 | 
						source.IsActived = form.IsActive
 | 
				
			||||||
 | 
						source.IsSyncEnabled = form.IsSyncEnabled
 | 
				
			||||||
	source.Cfg = config
 | 
						source.Cfg = config
 | 
				
			||||||
	if err := models.UpdateSource(source); err != nil {
 | 
						if err := models.UpdateSource(source); err != nil {
 | 
				
			||||||
		if models.IsErrOpenIDConnectInitialize(err) {
 | 
							if models.IsErrOpenIDConnectInitialize(err) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -211,6 +211,14 @@
 | 
				
			|||||||
						<input name="skip_verify" type="checkbox" {{if .Source.SkipVerify}}checked{{end}}>
 | 
											<input name="skip_verify" type="checkbox" {{if .Source.SkipVerify}}checked{{end}}>
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
 | 
									{{if .Source.IsLDAP}}
 | 
				
			||||||
 | 
									<div class="inline field">
 | 
				
			||||||
 | 
										<div class="ui checkbox">
 | 
				
			||||||
 | 
											<label><strong>{{.i18n.Tr "admin.auths.syncenabled"}}</strong></label>
 | 
				
			||||||
 | 
											<input name="is_sync_enabled" type="checkbox" {{if .Source.IsSyncEnabled}}checked{{end}}>
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
 | 
									{{end}}
 | 
				
			||||||
				<div class="inline field">
 | 
									<div class="inline field">
 | 
				
			||||||
					<div class="ui checkbox">
 | 
										<div class="ui checkbox">
 | 
				
			||||||
						<label><strong>{{.i18n.Tr "admin.auths.activated"}}</strong></label>
 | 
											<label><strong>{{.i18n.Tr "admin.auths.activated"}}</strong></label>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -61,6 +61,12 @@
 | 
				
			|||||||
						<input name="skip_verify" type="checkbox" {{if .skip_verify}}checked{{end}}>
 | 
											<input name="skip_verify" type="checkbox" {{if .skip_verify}}checked{{end}}>
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
 | 
									<div class="ldap inline field {{if not (eq .type 2)}}hide{{end}}">
 | 
				
			||||||
 | 
										<div class="ui checkbox">
 | 
				
			||||||
 | 
											<label><strong>{{.i18n.Tr "admin.auths.syncenabled"}}</strong></label>
 | 
				
			||||||
 | 
											<input name="is_sync_enabled" type="checkbox" {{if .is_sync_enabled}}checked{{end}}>
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
				<div class="inline field">
 | 
									<div class="inline field">
 | 
				
			||||||
					<div class="ui checkbox">
 | 
										<div class="ui checkbox">
 | 
				
			||||||
						<label><strong>{{.i18n.Tr "admin.auths.activated"}}</strong></label>
 | 
											<label><strong>{{.i18n.Tr "admin.auths.activated"}}</strong></label>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -45,6 +45,10 @@
 | 
				
			|||||||
						<td>{{.i18n.Tr "admin.dashboard.reinit_missing_repos"}}</td>
 | 
											<td>{{.i18n.Tr "admin.dashboard.reinit_missing_repos"}}</td>
 | 
				
			||||||
						<td><i class="fa fa-caret-square-o-right"></i> <a href="{{AppSubUrl}}/admin?op=7">{{.i18n.Tr "admin.dashboard.operation_run"}}</a></td>
 | 
											<td><i class="fa fa-caret-square-o-right"></i> <a href="{{AppSubUrl}}/admin?op=7">{{.i18n.Tr "admin.dashboard.operation_run"}}</a></td>
 | 
				
			||||||
					</tr>
 | 
										</tr>
 | 
				
			||||||
 | 
										<tr>
 | 
				
			||||||
 | 
											<td>{{.i18n.Tr "admin.dashboard.sync_external_users"}}</td>
 | 
				
			||||||
 | 
											<td><i class="fa fa-caret-square-o-right"></i> <a href="{{AppSubUrl}}/admin?op=8">{{.i18n.Tr "admin.dashboard.operation_run"}}</a></td>
 | 
				
			||||||
 | 
										</tr>
 | 
				
			||||||
				</tbody>
 | 
									</tbody>
 | 
				
			||||||
			</table>
 | 
								</table>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user