From 0981ec30c3d5218939d44fc2f40725b0b4a03684 Mon Sep 17 00:00:00 2001 From: zeripath Date: Tue, 14 Dec 2021 08:37:11 +0000 Subject: 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 * Fix regression from #16544 Signed-off-by: Andrew Thornton * Add scopes settings Signed-off-by: Andrew Thornton * fix trace logging in auth_openid Signed-off-by: Andrew Thornton * add required claim options Signed-off-by: Andrew Thornton * Move UpdateExternalUser to externalaccount Signed-off-by: Andrew Thornton * Allow OAuth2/OIDC to set Admin/Restricted status Signed-off-by: Andrew Thornton * Allow use of the same group claim name for the prohibit login value Signed-off-by: Andrew Thornton * fixup! Move UpdateExternalUser to externalaccount * as per wxiaoguang Signed-off-by: Andrew Thornton * add label back in Signed-off-by: Andrew Thornton * adjust localisation Signed-off-by: Andrew Thornton * placate lint Signed-off-by: Andrew Thornton Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: Lunny Xiao Co-authored-by: techknowlogick --- services/auth/source/oauth2/providers_custom.go | 32 +++++++++++++++---------- services/auth/source/oauth2/providers_openid.go | 7 +++++- services/auth/source/oauth2/providers_simple.go | 5 +++- services/auth/source/oauth2/source.go | 9 ++++++- services/externalaccount/link.go | 29 ++++++++++++++++++++++ services/externalaccount/user.go | 26 ++++++++++++++++---- services/forms/auth_form.go | 6 +++++ 7 files changed, 93 insertions(+), 21 deletions(-) create mode 100644 services/externalaccount/link.go (limited to 'services') diff --git a/services/auth/source/oauth2/providers_custom.go b/services/auth/source/oauth2/providers_custom.go index f2cff131f4..c3ebdf9df0 100644 --- a/services/auth/source/oauth2/providers_custom.go +++ b/services/auth/source/oauth2/providers_custom.go @@ -17,7 +17,7 @@ import ( ) // CustomProviderNewFn creates a goth.Provider using a custom url mapping -type CustomProviderNewFn func(clientID, secret, callbackURL string, custom *CustomURLMapping) (goth.Provider, error) +type CustomProviderNewFn func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error) // CustomProvider is a GothProvider that has CustomURL features type CustomProvider struct { @@ -35,7 +35,7 @@ func (c *CustomProvider) CustomURLSettings() *CustomURLSettings { func (c *CustomProvider) CreateGothProvider(providerName, callbackURL string, source *Source) (goth.Provider, error) { custom := c.customURLSettings.OverrideWith(source.CustomURLMapping) - return c.newFn(source.ClientID, source.ClientSecret, callbackURL, custom) + return c.newFn(source.ClientID, source.ClientSecret, callbackURL, custom, source.Scopes) } // NewCustomProvider is a constructor function for custom providers @@ -60,8 +60,7 @@ func init() { ProfileURL: availableAttribute(github.ProfileURL), EmailURL: availableAttribute(github.EmailURL), }, - func(clientID, secret, callbackURL string, custom *CustomURLMapping) (goth.Provider, error) { - scopes := []string{} + func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error) { if setting.OAuth2Client.EnableAutoRegistration { scopes = append(scopes, "user:email") } @@ -73,8 +72,9 @@ func init() { AuthURL: availableAttribute(gitlab.AuthURL), TokenURL: availableAttribute(gitlab.TokenURL), ProfileURL: availableAttribute(gitlab.ProfileURL), - }, func(clientID, secret, callbackURL string, custom *CustomURLMapping) (goth.Provider, error) { - return gitlab.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, "read_user"), nil + }, func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error) { + scopes = append(scopes, "read_user") + return gitlab.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, scopes...), nil })) RegisterGothProvider(NewCustomProvider( @@ -83,8 +83,8 @@ func init() { AuthURL: requiredAttribute(gitea.AuthURL), ProfileURL: requiredAttribute(gitea.ProfileURL), }, - func(clientID, secret, callbackURL string, custom *CustomURLMapping) (goth.Provider, error) { - return gitea.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL), nil + func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error) { + return gitea.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, scopes...), nil })) RegisterGothProvider(NewCustomProvider( @@ -93,25 +93,31 @@ func init() { AuthURL: requiredAttribute(nextcloud.AuthURL), ProfileURL: requiredAttribute(nextcloud.ProfileURL), }, - func(clientID, secret, callbackURL string, custom *CustomURLMapping) (goth.Provider, error) { - return nextcloud.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL), nil + func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error) { + return nextcloud.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, scopes...), nil })) RegisterGothProvider(NewCustomProvider( "mastodon", "Mastodon", &CustomURLSettings{ AuthURL: requiredAttribute(mastodon.InstanceURL), }, - func(clientID, secret, callbackURL string, custom *CustomURLMapping) (goth.Provider, error) { - return mastodon.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL), nil + func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error) { + return mastodon.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, scopes...), nil })) RegisterGothProvider(NewCustomProvider( "azureadv2", "Azure AD v2", &CustomURLSettings{ Tenant: requiredAttribute("organizations"), }, - func(clientID, secret, callbackURL string, custom *CustomURLMapping) (goth.Provider, error) { + func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error) { + azureScopes := make([]azureadv2.ScopeType, len(scopes)) + for i, scope := range scopes { + azureScopes[i] = azureadv2.ScopeType(scope) + } + return azureadv2.New(clientID, secret, callbackURL, azureadv2.ProviderOptions{ Tenant: azureadv2.TenantType(custom.Tenant), + Scopes: azureScopes, }), nil }, )) diff --git a/services/auth/source/oauth2/providers_openid.go b/services/auth/source/oauth2/providers_openid.go index 7c3836503c..838311b4a1 100644 --- a/services/auth/source/oauth2/providers_openid.go +++ b/services/auth/source/oauth2/providers_openid.go @@ -33,7 +33,12 @@ func (o *OpenIDProvider) Image() string { // CreateGothProvider creates a GothProvider from this Provider func (o *OpenIDProvider) CreateGothProvider(providerName, callbackURL string, source *Source) (goth.Provider, error) { - provider, err := openidConnect.New(source.ClientID, source.ClientSecret, callbackURL, source.OpenIDConnectAutoDiscoveryURL, setting.OAuth2Client.OpenIDConnectScopes...) + scopes := setting.OAuth2Client.OpenIDConnectScopes + if len(scopes) == 0 { + scopes = append(scopes, source.Scopes...) + } + + provider, err := openidConnect.New(source.ClientID, source.ClientSecret, callbackURL, source.OpenIDConnectAutoDiscoveryURL, scopes...) if err != nil { log.Warn("Failed to create OpenID Connect Provider with name '%s' with url '%s': %v", providerName, source.OpenIDConnectAutoDiscoveryURL, err) } diff --git a/services/auth/source/oauth2/providers_simple.go b/services/auth/source/oauth2/providers_simple.go index 5a7062e6c3..a4d61eb2f3 100644 --- a/services/auth/source/oauth2/providers_simple.go +++ b/services/auth/source/oauth2/providers_simple.go @@ -31,7 +31,10 @@ type SimpleProvider struct { // CreateGothProvider creates a GothProvider from this Provider func (c *SimpleProvider) CreateGothProvider(providerName, callbackURL string, source *Source) (goth.Provider, error) { - return c.newFn(source.ClientID, source.ClientSecret, callbackURL, c.scopes...), nil + scopes := make([]string, len(c.scopes)+len(source.Scopes)) + copy(scopes, c.scopes) + copy(scopes[len(c.scopes):], source.Scopes) + return c.newFn(source.ClientID, source.ClientSecret, callbackURL, scopes...), nil } // NewSimpleProvider is a constructor function for simple providers diff --git a/services/auth/source/oauth2/source.go b/services/auth/source/oauth2/source.go index bedaed7ef3..68ff08d1ee 100644 --- a/services/auth/source/oauth2/source.go +++ b/services/auth/source/oauth2/source.go @@ -24,7 +24,14 @@ type Source struct { OpenIDConnectAutoDiscoveryURL string CustomURLMapping *CustomURLMapping IconURL string - SkipLocalTwoFA bool `json:",omitempty"` + + Scopes []string + RequiredClaimName string + RequiredClaimValue string + GroupClaimName string + AdminGroup string + RestrictedGroup string + SkipLocalTwoFA bool `json:",omitempty"` // reference to the loginSource loginSource *login.Source diff --git a/services/externalaccount/link.go b/services/externalaccount/link.go new file mode 100644 index 0000000000..e71a37090f --- /dev/null +++ b/services/externalaccount/link.go @@ -0,0 +1,29 @@ +// Copyright 2021 The Gitea 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 externalaccount + +import ( + "fmt" + + user_model "code.gitea.io/gitea/models/user" + "github.com/markbates/goth" +) + +// Store represents a thing that stores things +type Store interface { + Get(interface{}) interface{} + Set(interface{}, interface{}) error + Release() error +} + +// LinkAccountFromStore links the provided user with a stored external user +func LinkAccountFromStore(store Store, user *user_model.User) error { + gothUser := store.Get("linkAccountGothUser") + if gothUser == nil { + return fmt.Errorf("not in LinkAccount session") + } + + return LinkAccountToUser(user, gothUser.(goth.User)) +} diff --git a/services/externalaccount/user.go b/services/externalaccount/user.go index f7280e90e4..8fd0680a1f 100644 --- a/services/externalaccount/user.go +++ b/services/externalaccount/user.go @@ -15,14 +15,12 @@ import ( "github.com/markbates/goth" ) -// LinkAccountToUser link the gothUser to the user -func LinkAccountToUser(user *user_model.User, gothUser goth.User) error { +func toExternalLoginUser(user *user_model.User, gothUser goth.User) (*user_model.ExternalLoginUser, error) { loginSource, err := login.GetActiveOAuth2LoginSourceByName(gothUser.Provider) if err != nil { - return err + return nil, err } - - externalLoginUser := &user_model.ExternalLoginUser{ + return &user_model.ExternalLoginUser{ ExternalID: gothUser.UserID, UserID: user.ID, LoginSourceID: loginSource.ID, @@ -40,6 +38,14 @@ func LinkAccountToUser(user *user_model.User, gothUser goth.User) error { AccessTokenSecret: gothUser.AccessTokenSecret, RefreshToken: gothUser.RefreshToken, ExpiresAt: gothUser.ExpiresAt, + }, nil +} + +// LinkAccountToUser link the gothUser to the user +func LinkAccountToUser(user *user_model.User, gothUser goth.User) error { + externalLoginUser, err := toExternalLoginUser(user, gothUser) + if err != nil { + return err } if err := user_model.LinkExternalToUser(user, externalLoginUser); err != nil { @@ -62,3 +68,13 @@ func LinkAccountToUser(user *user_model.User, gothUser goth.User) error { return nil } + +// UpdateExternalUser updates external user's information +func UpdateExternalUser(user *user_model.User, gothUser goth.User) error { + externalLoginUser, err := toExternalLoginUser(user, gothUser) + if err != nil { + return err + } + + return user_model.UpdateExternalUserByExternalID(externalLoginUser) +} diff --git a/services/forms/auth_form.go b/services/forms/auth_form.go index 2c6966d266..d096292601 100644 --- a/services/forms/auth_form.go +++ b/services/forms/auth_form.go @@ -67,6 +67,12 @@ type AuthenticationForm struct { Oauth2EmailURL string Oauth2IconURL string Oauth2Tenant string + Oauth2Scopes string + Oauth2RequiredClaimName string + Oauth2RequiredClaimValue string + Oauth2GroupClaimName string + Oauth2AdminGroup string + Oauth2RestrictedGroup string SkipLocalTwoFA bool SSPIAutoCreateUsers bool SSPIAutoActivateUsers bool -- cgit v1.2.3