summaryrefslogtreecommitdiffstats
path: root/routers
diff options
context:
space:
mode:
authorzeripath <art27@cantab.net>2021-12-14 08:37:11 +0000
committerGitHub <noreply@github.com>2021-12-14 16:37:11 +0800
commit0981ec30c3d5218939d44fc2f40725b0b4a03684 (patch)
tree5479fb309f9800310cf2268d493e1cd33abfeac6 /routers
parentb4782e24d2821bbb5647eff2eaf5c338e92324db (diff)
downloadgitea-0981ec30c3d5218939d44fc2f40725b0b4a03684.tar.gz
gitea-0981ec30c3d5218939d44fc2f40725b0b4a03684.zip
Add Option to synchronize Admin & Restricted states from OIDC/OAuth2 along with Setting Scopes (#16766)
* Add setting to OAuth handlers to override local 2FA settings This PR adds a setting to OAuth and OpenID login sources to allow the source to override local 2FA requirements. Fix #13939 Signed-off-by: Andrew Thornton <art27@cantab.net> * Fix regression from #16544 Signed-off-by: Andrew Thornton <art27@cantab.net> * Add scopes settings Signed-off-by: Andrew Thornton <art27@cantab.net> * fix trace logging in auth_openid Signed-off-by: Andrew Thornton <art27@cantab.net> * add required claim options Signed-off-by: Andrew Thornton <art27@cantab.net> * Move UpdateExternalUser to externalaccount Signed-off-by: Andrew Thornton <art27@cantab.net> * Allow OAuth2/OIDC to set Admin/Restricted status Signed-off-by: Andrew Thornton <art27@cantab.net> * Allow use of the same group claim name for the prohibit login value Signed-off-by: Andrew Thornton <art27@cantab.net> * fixup! Move UpdateExternalUser to externalaccount * as per wxiaoguang Signed-off-by: Andrew Thornton <art27@cantab.net> * add label back in Signed-off-by: Andrew Thornton <art27@cantab.net> * adjust localisation Signed-off-by: Andrew Thornton <art27@cantab.net> * placate lint Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Diffstat (limited to 'routers')
-rw-r--r--routers/web/admin/auths.go6
-rw-r--r--routers/web/user/auth.go123
-rw-r--r--routers/web/user/auth_openid.go14
3 files changed, 113 insertions, 30 deletions
diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go
index 5fd15b5c5a..b288273871 100644
--- a/routers/web/admin/auths.go
+++ b/routers/web/admin/auths.go
@@ -11,6 +11,7 @@ import (
"net/url"
"regexp"
"strconv"
+ "strings"
"code.gitea.io/gitea/models/login"
"code.gitea.io/gitea/modules/auth/pam"
@@ -187,6 +188,9 @@ func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source {
OpenIDConnectAutoDiscoveryURL: form.OpenIDConnectAutoDiscoveryURL,
CustomURLMapping: customURLMapping,
IconURL: form.Oauth2IconURL,
+ Scopes: strings.Split(form.Oauth2Scopes, ","),
+ RequiredClaimName: form.Oauth2RequiredClaimName,
+ RequiredClaimValue: form.Oauth2RequiredClaimValue,
SkipLocalTwoFA: form.SkipLocalTwoFA,
}
}
@@ -329,8 +333,8 @@ func EditAuthSource(ctx *context.Context) {
break
}
}
-
}
+
ctx.HTML(http.StatusOK, tplAuthEdit)
}
diff --git a/routers/web/user/auth.go b/routers/web/user/auth.go
index 42cd977b54..55a4b11007 100644
--- a/routers/web/user/auth.go
+++ b/routers/web/user/auth.go
@@ -320,16 +320,8 @@ func TwoFactorPost(ctx *context.Context) {
}
if ctx.Session.Get("linkAccount") != nil {
- gothUser := ctx.Session.Get("linkAccountGothUser")
- if gothUser == nil {
- ctx.ServerError("UserSignIn", errors.New("not in LinkAccount session"))
- return
- }
-
- err = externalaccount.LinkAccountToUser(u, gothUser.(goth.User))
- if err != nil {
+ if err := externalaccount.LinkAccountFromStore(ctx.Session, u); err != nil {
ctx.ServerError("UserSignIn", err)
- return
}
}
@@ -506,16 +498,8 @@ func U2FSign(ctx *context.Context) {
}
if ctx.Session.Get("linkAccount") != nil {
- gothUser := ctx.Session.Get("linkAccountGothUser")
- if gothUser == nil {
- ctx.ServerError("UserSignIn", errors.New("not in LinkAccount session"))
- return
- }
-
- err = externalaccount.LinkAccountToUser(user, gothUser.(goth.User))
- if err != nil {
+ if err := externalaccount.LinkAccountFromStore(ctx.Session, user); err != nil {
ctx.ServerError("UserSignIn", err)
- return
}
}
redirect := handleSignInFull(ctx, user, remember, false)
@@ -653,6 +637,13 @@ func SignInOAuthCallback(ctx *context.Context) {
u, gothUser, err := oAuth2UserLoginCallback(loginSource, ctx.Req, ctx.Resp)
if err != nil {
+ if user_model.IsErrUserProhibitLogin(err) {
+ uplerr := err.(*user_model.ErrUserProhibitLogin)
+ log.Info("Failed authentication attempt for %s from %s: %v", uplerr.Name, ctx.RemoteAddr(), err)
+ ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
+ ctx.HTML(http.StatusOK, "user/auth/prohibit_login")
+ return
+ }
ctx.ServerError("UserSignIn", err)
return
}
@@ -690,6 +681,8 @@ func SignInOAuthCallback(ctx *context.Context) {
IsRestricted: setting.Service.DefaultUserIsRestricted,
}
+ setUserGroupClaims(loginSource, u, &gothUser)
+
if !createAndHandleCreatedUser(ctx, base.TplName(""), nil, u, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled) {
// error already handled
return
@@ -704,6 +697,53 @@ func SignInOAuthCallback(ctx *context.Context) {
handleOAuth2SignIn(ctx, loginSource, u, gothUser)
}
+func claimValueToStringSlice(claimValue interface{}) []string {
+ var groups []string
+
+ switch rawGroup := claimValue.(type) {
+ case []string:
+ groups = rawGroup
+ default:
+ str := fmt.Sprintf("%s", rawGroup)
+ groups = strings.Split(str, ",")
+ }
+ return groups
+}
+
+func setUserGroupClaims(loginSource *login.Source, u *user_model.User, gothUser *goth.User) bool {
+
+ source := loginSource.Cfg.(*oauth2.Source)
+ if source.GroupClaimName == "" || (source.AdminGroup == "" && source.RestrictedGroup == "") {
+ return false
+ }
+
+ groupClaims, has := gothUser.RawData[source.GroupClaimName]
+ if !has {
+ return false
+ }
+
+ groups := claimValueToStringSlice(groupClaims)
+
+ wasAdmin, wasRestricted := u.IsAdmin, u.IsRestricted
+
+ if source.AdminGroup != "" {
+ u.IsAdmin = false
+ }
+ if source.RestrictedGroup != "" {
+ u.IsRestricted = false
+ }
+
+ for _, g := range groups {
+ if source.AdminGroup != "" && g == source.AdminGroup {
+ u.IsAdmin = true
+ } else if source.RestrictedGroup != "" && g == source.RestrictedGroup {
+ u.IsRestricted = true
+ }
+ }
+
+ return wasAdmin != u.IsAdmin || wasRestricted != u.IsRestricted
+}
+
func getUserName(gothUser *goth.User) string {
switch setting.OAuth2Client.Username {
case setting.OAuth2UsernameEmail:
@@ -774,13 +814,21 @@ func handleOAuth2SignIn(ctx *context.Context, source *login.Source, u *user_mode
// Register last login
u.SetLastLogin()
- if err := user_model.UpdateUserCols(db.DefaultContext, u, "last_login_unix"); err != nil {
+
+ // Update GroupClaims
+ changed := setUserGroupClaims(source, u, &gothUser)
+ cols := []string{"last_login_unix"}
+ if changed {
+ cols = append(cols, "is_admin", "is_restricted")
+ }
+
+ if err := user_model.UpdateUserCols(db.DefaultContext, u, cols...); err != nil {
ctx.ServerError("UpdateUserCols", err)
return
}
// update external user information
- if err := user_model.UpdateExternalUser(u, gothUser); err != nil {
+ if err := externalaccount.UpdateExternalUser(u, gothUser); err != nil {
log.Error("UpdateExternalUser failed: %v", err)
}
@@ -794,6 +842,14 @@ func handleOAuth2SignIn(ctx *context.Context, source *login.Source, u *user_mode
return
}
+ changed := setUserGroupClaims(source, u, &gothUser)
+ if changed {
+ if err := user_model.UpdateUserCols(db.DefaultContext, u, "is_admin", "is_restricted"); err != nil {
+ ctx.ServerError("UpdateUserCols", err)
+ return
+ }
+ }
+
// User needs to use 2FA, save data and redirect to 2FA page.
if err := ctx.Session.Set("twofaUid", u.ID); err != nil {
log.Error("Error setting twofaUid in session: %v", err)
@@ -818,7 +874,9 @@ func handleOAuth2SignIn(ctx *context.Context, source *login.Source, u *user_mode
// OAuth2UserLoginCallback attempts to handle the callback from the OAuth2 provider and if successful
// login the user
func oAuth2UserLoginCallback(loginSource *login.Source, request *http.Request, response http.ResponseWriter) (*user_model.User, goth.User, error) {
- gothUser, err := loginSource.Cfg.(*oauth2.Source).Callback(request, response)
+ oauth2Source := loginSource.Cfg.(*oauth2.Source)
+
+ gothUser, err := oauth2Source.Callback(request, response)
if err != nil {
if err.Error() == "securecookie: the value is too long" || strings.Contains(err.Error(), "Data too long") {
log.Error("OAuth2 Provider %s returned too long a token. Current max: %d. Either increase the [OAuth2] MAX_TOKEN_LENGTH or reduce the information returned from the OAuth2 provider", loginSource.Name, setting.OAuth2.MaxTokenLength)
@@ -827,6 +885,27 @@ func oAuth2UserLoginCallback(loginSource *login.Source, request *http.Request, r
return nil, goth.User{}, err
}
+ if oauth2Source.RequiredClaimName != "" {
+ claimInterface, has := gothUser.RawData[oauth2Source.RequiredClaimName]
+ if !has {
+ return nil, goth.User{}, user_model.ErrUserProhibitLogin{Name: gothUser.UserID}
+ }
+
+ if oauth2Source.RequiredClaimValue != "" {
+ groups := claimValueToStringSlice(claimInterface)
+ found := false
+ for _, group := range groups {
+ if group == oauth2Source.RequiredClaimValue {
+ found = true
+ break
+ }
+ }
+ if !found {
+ return nil, goth.User{}, user_model.ErrUserProhibitLogin{Name: gothUser.UserID}
+ }
+ }
+ }
+
user := &user_model.User{
LoginName: gothUser.UserID,
LoginType: login.OAuth2,
@@ -1354,7 +1433,7 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.
// update external user information
if gothUser != nil {
- if err := user_model.UpdateExternalUser(u, *gothUser); err != nil {
+ if err := externalaccount.UpdateExternalUser(u, *gothUser); err != nil {
log.Error("UpdateExternalUser failed: %v", err)
}
}
diff --git a/routers/web/user/auth_openid.go b/routers/web/user/auth_openid.go
index 68e166d12c..884ec29c21 100644
--- a/routers/web/user/auth_openid.go
+++ b/routers/web/user/auth_openid.go
@@ -144,10 +144,10 @@ func SignInOpenIDPost(ctx *context.Context) {
// signInOpenIDVerify handles response from OpenID provider
func signInOpenIDVerify(ctx *context.Context) {
- log.Trace("Incoming call to: " + ctx.Req.URL.String())
+ log.Trace("Incoming call to: %s", ctx.Req.URL.String())
fullURL := setting.AppURL + ctx.Req.URL.String()[1:]
- log.Trace("Full URL: " + fullURL)
+ log.Trace("Full URL: %s", fullURL)
var id, err = openid.Verify(fullURL)
if err != nil {
@@ -157,7 +157,7 @@ func signInOpenIDVerify(ctx *context.Context) {
return
}
- log.Trace("Verified ID: " + id)
+ log.Trace("Verified ID: %s", id)
/* Now we should seek for the user and log him in, or prompt
* to register if not found */
@@ -180,7 +180,7 @@ func signInOpenIDVerify(ctx *context.Context) {
return
}
- log.Trace("User with openid " + id + " does not exist, should connect or register")
+ log.Trace("User with openid: %s does not exist, should connect or register", id)
parsedURL, err := url.Parse(fullURL)
if err != nil {
@@ -199,7 +199,7 @@ func signInOpenIDVerify(ctx *context.Context) {
email := values.Get("openid.sreg.email")
nickname := values.Get("openid.sreg.nickname")
- log.Trace("User has email=" + email + " and nickname=" + nickname)
+ log.Trace("User has email=%s and nickname=%s", email, nickname)
if email != "" {
u, err = user_model.GetUserByEmail(email)
@@ -213,7 +213,7 @@ func signInOpenIDVerify(ctx *context.Context) {
log.Error("signInOpenIDVerify: %v", err)
}
if u != nil {
- log.Trace("Local user " + u.LowerName + " has OpenID provided email " + email)
+ log.Trace("Local user %s has OpenID provided email %s", u.LowerName, email)
}
}
@@ -228,7 +228,7 @@ func signInOpenIDVerify(ctx *context.Context) {
}
}
if u != nil {
- log.Trace("Local user " + u.LowerName + " has OpenID provided nickname " + nickname)
+ log.Trace("Local user %s has OpenID provided nickname %s", u.LowerName, nickname)
}
}