mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	LDAP: Optional user name attribute specification
Consider following LDAP search query example:
    (&(objectClass=Person)(|(uid=%s)(mail=%s)))
Right now on first login attempt Gogs will use the text supplied on login form
as the newly created user name. In example query above the text matches against
both e-mail or user name. So if user puts the e-mail then the new Gogs user
name will be e-mail which may be undesired.
Using optional user name attribute setting we can explicitly say we want Gogs
user name to be certain LDAP attribute eg. `uid`, so even user will use e-mail
to login 1st time, the new account will receive correct user name.
			
			
This commit is contained in:
		| @@ -878,6 +878,8 @@ auths.bind_password = Bind Password | |||||||
| auths.bind_password_helper = Warning: This password is stored in plain text. Do not use a high privileged account. | auths.bind_password_helper = Warning: This password is stored in plain text. Do not use a high privileged account. | ||||||
| auths.user_base = User Search Base | auths.user_base = User Search Base | ||||||
| auths.user_dn = User DN | auths.user_dn = User DN | ||||||
|  | auths.attribute_username = Username attribute | ||||||
|  | auths.attribute_username_placeholder = Leave empty to use sign-in form field value for user name. | ||||||
| auths.attribute_name = First name attribute | auths.attribute_name = First name attribute | ||||||
| auths.attribute_surname = Surname attribute | auths.attribute_surname = Surname attribute | ||||||
| auths.attribute_mail = E-mail attribute | auths.attribute_mail = E-mail attribute | ||||||
|   | |||||||
| @@ -225,16 +225,16 @@ func DeleteSource(source *LoginSource) error { | |||||||
| // |_______ \/_______  /\____|__  /____| | // |_______ \/_______  /\____|__  /____| | ||||||
| //         \/        \/         \/ | //         \/        \/         \/ | ||||||
|  |  | ||||||
| // LoginUserLDAPSource queries if name/passwd can login against the LDAP directory pool, | // LoginUserLDAPSource queries if loginName/passwd can login against the LDAP directory pool, | ||||||
| // and create a local user if success when enabled. | // and create a local user if success when enabled. | ||||||
| // It returns the same LoginUserPlain semantic. | // It returns the same LoginUserPlain semantic. | ||||||
| func LoginUserLDAPSource(u *User, name, passwd string, source *LoginSource, autoRegister bool) (*User, error) { | func LoginUserLDAPSource(u *User, loginName, passwd string, source *LoginSource, autoRegister bool) (*User, error) { | ||||||
| 	cfg := source.Cfg.(*LDAPConfig) | 	cfg := source.Cfg.(*LDAPConfig) | ||||||
| 	directBind := (source.Type == DLDAP) | 	directBind := (source.Type == DLDAP) | ||||||
| 	fn, sn, mail, admin, logged := cfg.SearchEntry(name, passwd, directBind) | 	name, fn, sn, mail, admin, logged := cfg.SearchEntry(loginName, passwd, directBind) | ||||||
| 	if !logged { | 	if !logged { | ||||||
| 		// User not in LDAP, do nothing | 		// User not in LDAP, do nothing | ||||||
| 		return nil, ErrUserNotExist{0, name} | 		return nil, ErrUserNotExist{0, loginName} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if !autoRegister { | 	if !autoRegister { | ||||||
| @@ -242,6 +242,9 @@ func LoginUserLDAPSource(u *User, name, passwd string, source *LoginSource, auto | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Fallback. | 	// Fallback. | ||||||
|  | 	if len(name) == 0 { | ||||||
|  | 		name = loginName | ||||||
|  | 	} | ||||||
| 	if len(mail) == 0 { | 	if len(mail) == 0 { | ||||||
| 		mail = fmt.Sprintf("%s@localhost", name) | 		mail = fmt.Sprintf("%s@localhost", name) | ||||||
| 	} | 	} | ||||||
| @@ -249,10 +252,10 @@ func LoginUserLDAPSource(u *User, name, passwd string, source *LoginSource, auto | |||||||
| 	u = &User{ | 	u = &User{ | ||||||
| 		LowerName:   strings.ToLower(name), | 		LowerName:   strings.ToLower(name), | ||||||
| 		Name:        name, | 		Name:        name, | ||||||
| 		FullName:    strings.TrimSpace(fn + " " + sn), | 		FullName:    composeFullName(fn, sn, name), | ||||||
| 		LoginType:   source.Type, | 		LoginType:   source.Type, | ||||||
| 		LoginSource: source.ID, | 		LoginSource: source.ID, | ||||||
| 		LoginName:   name, | 		LoginName:   loginName, | ||||||
| 		Email:       mail, | 		Email:       mail, | ||||||
| 		IsAdmin:     admin, | 		IsAdmin:     admin, | ||||||
| 		IsActive:    true, | 		IsActive:    true, | ||||||
| @@ -260,6 +263,19 @@ func LoginUserLDAPSource(u *User, name, passwd string, source *LoginSource, auto | |||||||
| 	return u, CreateUser(u) | 	return u, CreateUser(u) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func composeFullName(firstName, surename, userName string) string { | ||||||
|  | 	switch { | ||||||
|  | 	case len(firstName) == 0 && len(surename) == 0: | ||||||
|  | 		return userName | ||||||
|  | 	case len(firstName) == 0: | ||||||
|  | 		return surename | ||||||
|  | 	case len(surename) == 0: | ||||||
|  | 		return firstName | ||||||
|  | 	default: | ||||||
|  | 		return firstName + " " + surename | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| //   _________   __________________________ | //   _________   __________________________ | ||||||
| //  /   _____/  /     \__    ___/\______   \ | //  /   _____/  /     \__    ___/\______   \ | ||||||
| //  \_____  \  /  \ /  \|    |    |     ___/ | //  \_____  \  /  \ /  \|    |    |     ___/ | ||||||
|   | |||||||
| @@ -19,6 +19,7 @@ type AuthenticationForm struct { | |||||||
| 	BindPassword      string | 	BindPassword      string | ||||||
| 	UserBase          string | 	UserBase          string | ||||||
| 	UserDN            string `form:"user_dn"` | 	UserDN            string `form:"user_dn"` | ||||||
|  | 	AttributeUsername string | ||||||
| 	AttributeName     string | 	AttributeName     string | ||||||
| 	AttributeSurname  string | 	AttributeSurname  string | ||||||
| 	AttributeMail     string | 	AttributeMail     string | ||||||
|   | |||||||
| @@ -27,6 +27,7 @@ type Source struct { | |||||||
| 	BindPassword      string // Bind DN password | 	BindPassword      string // Bind DN password | ||||||
| 	UserBase          string // Base search path for users | 	UserBase          string // Base search path for users | ||||||
| 	UserDN            string // Template for the DN of the user for simple auth | 	UserDN            string // Template for the DN of the user for simple auth | ||||||
|  | 	AttributeUsername string // Username attribute | ||||||
| 	AttributeName     string // First name attribute | 	AttributeName     string // First name attribute | ||||||
| 	AttributeSurname  string // Surname attribute | 	AttributeSurname  string // Surname attribute | ||||||
| 	AttributeMail     string // E-mail attribute | 	AttributeMail     string // E-mail attribute | ||||||
| @@ -109,7 +110,7 @@ func (ls *Source) FindUserDN(name string) (string, bool) { | |||||||
| } | } | ||||||
|  |  | ||||||
| // 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, bool, bool) { | func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, string, string, string, bool, bool) { | ||||||
| 	var userDN string | 	var userDN string | ||||||
| 	if directBind { | 	if directBind { | ||||||
| 		log.Trace("LDAP will bind directly via UserDN template: %s", ls.UserDN) | 		log.Trace("LDAP will bind directly via UserDN template: %s", ls.UserDN) | ||||||
| @@ -117,7 +118,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 "", "", "", "", false, false | ||||||
| 		} | 		} | ||||||
| 	} else { | 	} else { | ||||||
| 		log.Trace("LDAP will use BindDN.") | 		log.Trace("LDAP will use BindDN.") | ||||||
| @@ -125,7 +126,7 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str | |||||||
| 		var found bool | 		var found bool | ||||||
| 		userDN, found = ls.FindUserDN(name) | 		userDN, found = ls.FindUserDN(name) | ||||||
| 		if !found { | 		if !found { | ||||||
| 			return "", "", "", false, false | 			return "", "", "", "", false, false | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -133,7 +134,7 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str | |||||||
| 	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 "", "", "", "", false, false | ||||||
| 	} | 	} | ||||||
| 	defer l.Close() | 	defer l.Close() | ||||||
|  |  | ||||||
| @@ -141,13 +142,13 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str | |||||||
| 	err = l.Bind(userDN, passwd) | 	err = l.Bind(userDN, passwd) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Debug("LDAP auth. failed for %s, reason: %v", userDN, err) | 		log.Debug("LDAP auth. failed for %s, reason: %v", userDN, err) | ||||||
| 		return "", "", "", false, false | 		return "", "", "", "", false, false | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	log.Trace("Bound successfully with userDN: %s", userDN) | 	log.Trace("Bound successfully with userDN: %s", userDN) | ||||||
| 	userFilter, ok := ls.sanitizedUserQuery(name) | 	userFilter, ok := ls.sanitizedUserQuery(name) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return "", "", "", false, false | 		return "", "", "", "", false, false | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	search := ldap.NewSearchRequest( | 	search := ldap.NewSearchRequest( | ||||||
| @@ -158,7 +159,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 "", "", "", "", false, false | ||||||
| 	} 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.") | ||||||
| @@ -166,9 +167,10 @@ 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 "", "", "", "", false, false | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	username_attr := sr.Entries[0].GetAttributeValue(ls.AttributeUsername) | ||||||
| 	name_attr := sr.Entries[0].GetAttributeValue(ls.AttributeName) | 	name_attr := sr.Entries[0].GetAttributeValue(ls.AttributeName) | ||||||
| 	sn_attr := sr.Entries[0].GetAttributeValue(ls.AttributeSurname) | 	sn_attr := sr.Entries[0].GetAttributeValue(ls.AttributeSurname) | ||||||
| 	mail_attr := sr.Entries[0].GetAttributeValue(ls.AttributeMail) | 	mail_attr := sr.Entries[0].GetAttributeValue(ls.AttributeMail) | ||||||
| @@ -190,7 +192,7 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return name_attr, sn_attr, mail_attr, admin_attr, true | 	return username_attr, name_attr, sn_attr, mail_attr, admin_attr, true | ||||||
| } | } | ||||||
|  |  | ||||||
| func ldapDial(ls *Source) (*ldap.Conn, error) { | func ldapDial(ls *Source) (*ldap.Conn, error) { | ||||||
|   | |||||||
| @@ -77,6 +77,7 @@ func parseLDAPConfig(form auth.AuthenticationForm) *models.LDAPConfig { | |||||||
| 			UserDN:            form.UserDN, | 			UserDN:            form.UserDN, | ||||||
| 			BindPassword:      form.BindPassword, | 			BindPassword:      form.BindPassword, | ||||||
| 			UserBase:          form.UserBase, | 			UserBase:          form.UserBase, | ||||||
|  | 			AttributeUsername: form.AttributeUsername, | ||||||
| 			AttributeName:     form.AttributeName, | 			AttributeName:     form.AttributeName, | ||||||
| 			AttributeSurname:  form.AttributeSurname, | 			AttributeSurname:  form.AttributeSurname, | ||||||
| 			AttributeMail:     form.AttributeMail, | 			AttributeMail:     form.AttributeMail, | ||||||
|   | |||||||
| @@ -63,6 +63,10 @@ | |||||||
|               <label for="admin_filter">{{.i18n.Tr "admin.auths.admin_filter"}}</label> |               <label for="admin_filter">{{.i18n.Tr "admin.auths.admin_filter"}}</label> | ||||||
|               <input id="admin_filter" name="admin_filter" value="{{$cfg.AdminFilter}}"> |               <input id="admin_filter" name="admin_filter" value="{{$cfg.AdminFilter}}"> | ||||||
|             </div> |             </div> | ||||||
|  |             <div class="field"> | ||||||
|  |               <label for="attribute_username">{{.i18n.Tr "admin.auths.attribute_username"}}</label> | ||||||
|  |               <input id="attribute_username" name="attribute_username" value="{{$cfg.AttributeUsername}}" placeholder="{{.i18n.Tr "admin.auths.attribute_username_placeholder"}}"> | ||||||
|  |             </div> | ||||||
|             <div class="field"> |             <div class="field"> | ||||||
|               <label for="attribute_name">{{.i18n.Tr "admin.auths.attribute_name"}}</label> |               <label for="attribute_name">{{.i18n.Tr "admin.auths.attribute_name"}}</label> | ||||||
|               <input id="attribute_name" name="attribute_name" value="{{$cfg.AttributeName}}"> |               <input id="attribute_name" name="attribute_name" value="{{$cfg.AttributeName}}"> | ||||||
|   | |||||||
| @@ -66,6 +66,10 @@ | |||||||
|                   <label for="admin_filter">{{.i18n.Tr "admin.auths.admin_filter"}}</label> |                   <label for="admin_filter">{{.i18n.Tr "admin.auths.admin_filter"}}</label> | ||||||
|                   <input id="admin_filter" name="admin_filter" value="{{.admin_filter}}"> |                   <input id="admin_filter" name="admin_filter" value="{{.admin_filter}}"> | ||||||
|                 </div> |                 </div> | ||||||
|  |                 <div class="field"> | ||||||
|  |                   <label for="attribute_username">{{.i18n.Tr "admin.auths.attribute_username"}}</label> | ||||||
|  |                   <input id="attribute_username" name="attribute_username" value="{{.attribute_username}}" placeholder="{{.i18n.Tr "admin.auths.attribute_username_placeholder"}}"> | ||||||
|  |                 </div> | ||||||
|                 <div class="field"> |                 <div class="field"> | ||||||
|                   <label for="attribute_name">{{.i18n.Tr "admin.auths.attribute_name"}}</label> |                   <label for="attribute_name">{{.i18n.Tr "admin.auths.attribute_name"}}</label> | ||||||
|                   <input id="attribute_name" name="attribute_name" value="{{.attribute_name}}"> |                   <input id="attribute_name" name="attribute_name" value="{{.attribute_name}}"> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user