forked from kevadesu/forgejo
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>
This commit is contained in:
parent
b4782e24d2
commit
0981ec30c3
17 changed files with 344 additions and 88 deletions
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue