Skip to content

Commit

Permalink
Account autocreation from LDAP after reverse proxy authentication
Browse files Browse the repository at this point in the history
Gitea allows autocreation of account from external source after successful
basic auth but not after successful reverse proxy auth. This mod adds such
feature.

Unfortunaltely gitea does not sync all user attributes from LDAP for
existing users on login like cron.sync_external_users does so changes
of first name, surname, e-mail are not updated from LDAP on login for
exiting users - only after first login and after sync_external_users task.

Related: gogs/gogs#2498
Author-Change-Id: IB#1104925
  • Loading branch information
pboguslawski committed Sep 28, 2020
1 parent 95ff559 commit bd8361d
Show file tree
Hide file tree
Showing 9 changed files with 48 additions and 25 deletions.
16 changes: 8 additions & 8 deletions models/login_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -456,8 +456,8 @@ func composeFullName(firstname, surname, username string) string {

// LoginViaLDAP queries if login/password is valid against the LDAP directory pool,
// and create a local user if success when enabled.
func LoginViaLDAP(user *User, login, password string, source *LoginSource) (*User, error) {
sr := source.Cfg.(*LDAPConfig).SearchEntry(login, password, source.Type == LoginDLDAP)
func LoginViaLDAP(user *User, login, password string, alreadyAuthenticated bool, source *LoginSource) (*User, error) {
sr := source.Cfg.(*LDAPConfig).SearchEntry(login, password, source.Type == LoginDLDAP, alreadyAuthenticated)
if sr == nil {
// User not in LDAP, do nothing
return nil, ErrUserNotExist{0, login, 0}
Expand Down Expand Up @@ -701,15 +701,15 @@ func LoginViaPAM(user *User, login, password string, sourceID int64, cfg *PAMCon
}

// ExternalUserLogin attempts a login using external source types.
func ExternalUserLogin(user *User, login, password string, source *LoginSource) (*User, error) {
func ExternalUserLogin(user *User, login, password string, alreadyAuthenticated bool, source *LoginSource) (*User, error) {
if !source.IsActived {
return nil, ErrLoginSourceNotActived
}

var err error
switch source.Type {
case LoginLDAP, LoginDLDAP:
user, err = LoginViaLDAP(user, login, password, source)
user, err = LoginViaLDAP(user, login, password, alreadyAuthenticated, source)
case LoginSMTP:
user, err = LoginViaSMTP(user, login, password, source.ID, source.Cfg.(*SMTPConfig))
case LoginPAM:
Expand All @@ -731,8 +731,8 @@ func ExternalUserLogin(user *User, login, password string, source *LoginSource)
return user, nil
}

// UserSignIn validates user name and password.
func UserSignIn(username, password string) (*User, error) {
// UserSignIn validates user name and password. Password verification in LDAP skipped if already authenticated.
func UserSignIn(username, password string, alreadyAuthenticated bool) (*User, error) {
var user *User
if strings.Contains(username, "@") {
user = &User{Email: strings.ToLower(strings.TrimSpace(username))}
Expand Down Expand Up @@ -793,7 +793,7 @@ func UserSignIn(username, password string) (*User, error) {
return nil, ErrLoginSourceNotExist{user.LoginSource}
}

return ExternalUserLogin(user, user.LoginName, password, &source)
return ExternalUserLogin(user, user.LoginName, password, alreadyAuthenticated, &source)
}
}

Expand All @@ -807,7 +807,7 @@ func UserSignIn(username, password string) (*User, error) {
// don't try to authenticate against OAuth2 and SSPI sources here
continue
}
authUser, err := ExternalUserLogin(nil, username, password, source)
authUser, err := ExternalUserLogin(nil, username, password, alreadyAuthenticated, source)
if err == nil {
return authUser, nil
}
Expand Down
18 changes: 14 additions & 4 deletions modules/auth/ldap/ldap.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,12 +230,22 @@ func checkRestricted(l *ldap.Conn, ls *Source, userDN string) bool {
}

// 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) *SearchResult {
func (ls *Source) SearchEntry(name, passwd string, directBind bool, alreadyAuthenticated bool) *SearchResult {
// See https://tools.ietf.org/search/rfc4513#section-5.1.2
if len(passwd) == 0 {
if len(passwd) == 0 && !alreadyAuthenticated {
log.Debug("Auth. failed for %s, password cannot be empty", name)
return nil
}
if directBind && alreadyAuthenticated {
log.Debug("Cannot bind using user %s credentials - user already authenticated. BindDN must be used.", name)
return nil
}

if !ls.AttributesInBind && alreadyAuthenticated {
log.Debug("Cannot get attributes using user %s credentials - user already authenticated; --attributes-in-bind must be used.", name)
return nil
}

l, err := dial(ls)
if err != nil {
log.Error("LDAP Connect error, %s:%v", ls.Host, err)
Expand Down Expand Up @@ -393,8 +403,8 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResul
isRestricted = checkRestricted(l, ls, userDN)
}

if !directBind && ls.AttributesInBind {
// binds user (checking password) after looking-up attributes in BindDN context
if !directBind && ls.AttributesInBind && !alreadyAuthenticated {
// binds user (checking password) after looking-up attributes in BindDN context if not already authenticated
err = bindUser(l, userDN, passwd)
if err != nil {
return nil
Expand Down
2 changes: 1 addition & 1 deletion modules/auth/sso/basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func (b *Basic) VerifyAuthData(ctx *macaron.Context, sess session.Store) *models
}

if u == nil {
u, err = models.UserSignIn(uname, passwd)
u, err = models.UserSignIn(uname, passwd, false)
if err != nil {
if !models.IsErrUserNotExist(err) {
log.Error("UserSignIn: %v", err)
Expand Down
25 changes: 19 additions & 6 deletions modules/auth/sso/reverseproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,26 @@ func (r *ReverseProxy) VerifyAuthData(ctx *macaron.Context, sess session.Store)
return nil
}

user, err := models.GetUserByName(username)
if err != nil {
if models.IsErrUserNotExist(err) && r.isAutoRegisterAllowed() {
return r.newUser(ctx)
var user *models.User
var err error

if r.isAutoRegisterAllowed() {
// Use auto registration from reverse proxy if ENABLE_REVERSE_PROXY_AUTO_REGISTRATION enabled.
if user, err = models.GetUserByName(username); err != nil {
if models.IsErrUserNotExist(err) && r.isAutoRegisterAllowed() {
return r.newUser(ctx)
}
log.Error("GetUserByName: %v", err)
return nil
}
} else {
// Use auto registration from other backends if ENABLE_REVERSE_PROXY_AUTO_REGISTRATION not enabled.
if user, err = models.UserSignIn(username, "", true); err != nil {
if !models.IsErrUserNotExist(err) {
log.Error("UserSignIn: %v", err)
}
return nil
}
log.Error("GetUserByName: %v", err)
return nil
}

return user
Expand Down
2 changes: 1 addition & 1 deletion routers/org/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ func SettingsDelete(ctx *context.Context) {

org := ctx.Org.Organization
if ctx.Req.Method == "POST" {
if _, err := models.UserSignIn(ctx.User.Name, ctx.Query("password")); err != nil {
if _, err := models.UserSignIn(ctx.User.Name, ctx.Query("password"), false); err != nil {
if models.IsErrUserNotExist(err) {
ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_password"), tplSettingsDelete, nil)
} else {
Expand Down
2 changes: 1 addition & 1 deletion routers/repo/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ func HTTP(ctx *context.Context) {

if authUser == nil {
// Check username and password
authUser, err = models.UserSignIn(authUsername, authPasswd)
authUser, err = models.UserSignIn(authUsername, authPasswd, false)
if err != nil {
if models.IsErrUserProhibitLogin(err) {
ctx.HandleText(http.StatusForbidden, "User is not permitted to login")
Expand Down
4 changes: 2 additions & 2 deletions routers/user/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ func SignInPost(ctx *context.Context, form auth.SignInForm) {
return
}

u, err := models.UserSignIn(form.UserName, form.Password)
u, err := models.UserSignIn(form.UserName, form.Password, false)
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplSignIn, &form)
Expand Down Expand Up @@ -805,7 +805,7 @@ func LinkAccountPostSignIn(ctx *context.Context, signInForm auth.SignInForm) {
return
}

u, err := models.UserSignIn(signInForm.UserName, signInForm.Password)
u, err := models.UserSignIn(signInForm.UserName, signInForm.Password, false)
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.Data["user_exists"] = true
Expand Down
2 changes: 1 addition & 1 deletion routers/user/auth_openid.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ func ConnectOpenIDPost(ctx *context.Context, form auth.ConnectOpenIDForm) {
ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp
ctx.Data["OpenID"] = oid

u, err := models.UserSignIn(form.UserName, form.Password)
u, err := models.UserSignIn(form.UserName, form.Password, false)
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplConnectOID, &form)
Expand Down
2 changes: 1 addition & 1 deletion routers/user/setting/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ func DeleteAccount(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsAccount"] = true

if _, err := models.UserSignIn(ctx.User.Name, ctx.Query("password")); err != nil {
if _, err := models.UserSignIn(ctx.User.Name, ctx.Query("password"), false); err != nil {
if models.IsErrUserNotExist(err) {
loadAccountData(ctx)

Expand Down

0 comments on commit bd8361d

Please sign in to comment.