diff options
author | zeripath <art27@cantab.net> | 2021-08-06 02:11:08 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-05 21:11:08 -0400 |
commit | ab9bb54144f136bbbba2ce2e94fd88c0be0ee1cf (patch) | |
tree | f8b283e33e70cbc3d827879c4774de2b41450ab0 /services | |
parent | 7e7006e00d8d0d5ce4c871685d421269049e4b39 (diff) | |
download | gitea-ab9bb54144f136bbbba2ce2e94fd88c0be0ee1cf.tar.gz gitea-ab9bb54144f136bbbba2ce2e94fd88c0be0ee1cf.zip |
Add microsoft oauth2 providers (#16544)
* Clean up oauth2 providers
Signed-off-by: Andrew Thornton <art27@cantab.net>
* Add AzureAD, AzureADv2, MicrosoftOnline OAuth2 providers
Signed-off-by: Andrew Thornton <art27@cantab.net>
* Apply suggestions from code review
* remove unused Scopes
Signed-off-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Diffstat (limited to 'services')
-rw-r--r-- | services/auth/source/oauth2/providers.go | 252 | ||||
-rw-r--r-- | services/auth/source/oauth2/providers_base.go | 33 | ||||
-rw-r--r-- | services/auth/source/oauth2/providers_custom.go | 118 | ||||
-rw-r--r-- | services/auth/source/oauth2/providers_openid.go | 52 | ||||
-rw-r--r-- | services/auth/source/oauth2/providers_simple.go | 108 | ||||
-rw-r--r-- | services/auth/source/oauth2/source_name.go | 19 | ||||
-rw-r--r-- | services/auth/source/oauth2/source_register.go | 4 | ||||
-rw-r--r-- | services/auth/source/oauth2/urlmapping.go | 80 | ||||
-rw-r--r-- | services/forms/auth_form.go | 1 |
9 files changed, 475 insertions, 192 deletions
diff --git a/services/auth/source/oauth2/providers.go b/services/auth/source/oauth2/providers.go index 8df8d62961..2196e30492 100644 --- a/services/auth/source/oauth2/providers.go +++ b/services/auth/source/oauth2/providers.go @@ -13,80 +13,72 @@ import ( "code.gitea.io/gitea/modules/setting" "github.com/markbates/goth" - "github.com/markbates/goth/providers/bitbucket" - "github.com/markbates/goth/providers/discord" - "github.com/markbates/goth/providers/dropbox" - "github.com/markbates/goth/providers/facebook" - "github.com/markbates/goth/providers/gitea" - "github.com/markbates/goth/providers/github" - "github.com/markbates/goth/providers/gitlab" - "github.com/markbates/goth/providers/google" - "github.com/markbates/goth/providers/mastodon" - "github.com/markbates/goth/providers/nextcloud" - "github.com/markbates/goth/providers/openidConnect" - "github.com/markbates/goth/providers/twitter" - "github.com/markbates/goth/providers/yandex" ) -// Provider describes the display values of a single OAuth2 provider -type Provider struct { - Name string - DisplayName string - Image string - CustomURLMapping *CustomURLMapping +// Provider is an interface for describing a single OAuth2 provider +type Provider interface { + Name() string + DisplayName() string + Image() string + CustomURLSettings() *CustomURLSettings +} + +// GothProviderCreator provides a function to create a goth.Provider +type GothProviderCreator interface { + CreateGothProvider(providerName, callbackURL string, source *Source) (goth.Provider, error) +} + +// GothProvider is an interface for describing a single OAuth2 provider +type GothProvider interface { + Provider + GothProviderCreator +} + +// ImagedProvider provide an overrided image setting for the provider +type ImagedProvider struct { + GothProvider + image string +} + +// Image returns the image path for this provider +func (i *ImagedProvider) Image() string { + return i.image +} + +// NewImagedProvider is a constructor function for the ImagedProvider +func NewImagedProvider(image string, provider GothProvider) *ImagedProvider { + return &ImagedProvider{ + GothProvider: provider, + image: image, + } } // Providers contains the map of registered OAuth2 providers in Gitea (based on goth) // key is used to map the OAuth2Provider with the goth provider type (also in LoginSource.OAuth2Config.Provider) // value is used to store display data -var Providers = map[string]Provider{ - "bitbucket": {Name: "bitbucket", DisplayName: "Bitbucket", Image: "/assets/img/auth/bitbucket.png"}, - "dropbox": {Name: "dropbox", DisplayName: "Dropbox", Image: "/assets/img/auth/dropbox.png"}, - "facebook": {Name: "facebook", DisplayName: "Facebook", Image: "/assets/img/auth/facebook.png"}, - "github": { - Name: "github", DisplayName: "GitHub", Image: "/assets/img/auth/github.png", - CustomURLMapping: &CustomURLMapping{ - TokenURL: github.TokenURL, - AuthURL: github.AuthURL, - ProfileURL: github.ProfileURL, - EmailURL: github.EmailURL, - }, - }, - "gitlab": { - Name: "gitlab", DisplayName: "GitLab", Image: "/assets/img/auth/gitlab.png", - CustomURLMapping: &CustomURLMapping{ - TokenURL: gitlab.TokenURL, - AuthURL: gitlab.AuthURL, - ProfileURL: gitlab.ProfileURL, - }, - }, - "gplus": {Name: "gplus", DisplayName: "Google", Image: "/assets/img/auth/google.png"}, - "openidConnect": {Name: "openidConnect", DisplayName: "OpenID Connect", Image: "/assets/img/auth/openid_connect.svg"}, - "twitter": {Name: "twitter", DisplayName: "Twitter", Image: "/assets/img/auth/twitter.png"}, - "discord": {Name: "discord", DisplayName: "Discord", Image: "/assets/img/auth/discord.png"}, - "gitea": { - Name: "gitea", DisplayName: "Gitea", Image: "/assets/img/auth/gitea.png", - CustomURLMapping: &CustomURLMapping{ - TokenURL: gitea.TokenURL, - AuthURL: gitea.AuthURL, - ProfileURL: gitea.ProfileURL, - }, - }, - "nextcloud": { - Name: "nextcloud", DisplayName: "Nextcloud", Image: "/assets/img/auth/nextcloud.png", - CustomURLMapping: &CustomURLMapping{ - TokenURL: nextcloud.TokenURL, - AuthURL: nextcloud.AuthURL, - ProfileURL: nextcloud.ProfileURL, - }, - }, - "yandex": {Name: "yandex", DisplayName: "Yandex", Image: "/assets/img/auth/yandex.png"}, - "mastodon": { - Name: "mastodon", DisplayName: "Mastodon", Image: "/assets/img/auth/mastodon.png", - CustomURLMapping: &CustomURLMapping{ - AuthURL: mastodon.InstanceURL, - }, - }, +var gothProviders = map[string]GothProvider{} + +// RegisterGothProvider registers a GothProvider +func RegisterGothProvider(provider GothProvider) { + if _, has := gothProviders[provider.Name()]; has { + log.Fatal("Duplicate oauth2provider type provided: %s", provider.Name()) + } + gothProviders[provider.Name()] = provider +} + +// GetOAuth2Providers returns the map of unconfigured OAuth2 providers +// key is used as technical name (like in the callbackURL) +// values to display +func GetOAuth2Providers() []Provider { + providers := make([]Provider, 0, len(gothProviders)) + + for _, provider := range gothProviders { + providers = append(providers, provider) + } + sort.Slice(providers, func(i, j int) bool { + return providers[i].Name() < providers[j].Name() + }) + return providers } // GetActiveOAuth2Providers returns the map of configured active OAuth2 providers @@ -103,9 +95,9 @@ func GetActiveOAuth2Providers() ([]string, map[string]Provider, error) { var orderedKeys []string providers := make(map[string]Provider) for _, source := range loginSources { - prov := Providers[source.Cfg.(*Source).Provider] + prov := gothProviders[source.Cfg.(*Source).Provider] if source.Cfg.(*Source).IconURL != "" { - prov.Image = source.Cfg.(*Source).IconURL + prov = &ImagedProvider{prov, source.Cfg.(*Source).IconURL} } providers[source.Name] = prov orderedKeys = append(orderedKeys, source.Name) @@ -116,9 +108,9 @@ func GetActiveOAuth2Providers() ([]string, map[string]Provider, error) { return orderedKeys, providers, nil } -// RegisterProvider register a OAuth2 provider in goth lib -func RegisterProvider(providerName, providerType, clientID, clientSecret, openIDConnectAutoDiscoveryURL string, customURLMapping *CustomURLMapping) error { - provider, err := createProvider(providerName, providerType, clientID, clientSecret, openIDConnectAutoDiscoveryURL, customURLMapping) +// RegisterProviderWithGothic register a OAuth2 provider in goth lib +func RegisterProviderWithGothic(providerName string, source *Source) error { + provider, err := createProvider(providerName, source) if err == nil && provider != nil { gothRWMutex.Lock() @@ -130,8 +122,8 @@ func RegisterProvider(providerName, providerType, clientID, clientSecret, openID return err } -// RemoveProvider removes the given OAuth2 provider from the goth lib -func RemoveProvider(providerName string) { +// RemoveProviderFromGothic removes the given OAuth2 provider from the goth lib +func RemoveProviderFromGothic(providerName string) { gothRWMutex.Lock() defer gothRWMutex.Unlock() @@ -147,114 +139,20 @@ func ClearProviders() { } // used to create different types of goth providers -func createProvider(providerName, providerType, clientID, clientSecret, openIDConnectAutoDiscoveryURL string, customURLMapping *CustomURLMapping) (goth.Provider, error) { +func createProvider(providerName string, source *Source) (goth.Provider, error) { callbackURL := setting.AppURL + "user/oauth2/" + url.PathEscape(providerName) + "/callback" var provider goth.Provider var err error - switch providerType { - case "bitbucket": - provider = bitbucket.New(clientID, clientSecret, callbackURL, "account") - case "dropbox": - provider = dropbox.New(clientID, clientSecret, callbackURL) - case "facebook": - provider = facebook.New(clientID, clientSecret, callbackURL, "email") - case "github": - authURL := github.AuthURL - tokenURL := github.TokenURL - profileURL := github.ProfileURL - emailURL := github.EmailURL - if customURLMapping != nil { - if len(customURLMapping.AuthURL) > 0 { - authURL = customURLMapping.AuthURL - } - if len(customURLMapping.TokenURL) > 0 { - tokenURL = customURLMapping.TokenURL - } - if len(customURLMapping.ProfileURL) > 0 { - profileURL = customURLMapping.ProfileURL - } - if len(customURLMapping.EmailURL) > 0 { - emailURL = customURLMapping.EmailURL - } - } - scopes := []string{} - if setting.OAuth2Client.EnableAutoRegistration { - scopes = append(scopes, "user:email") - } - provider = github.NewCustomisedURL(clientID, clientSecret, callbackURL, authURL, tokenURL, profileURL, emailURL, scopes...) - case "gitlab": - authURL := gitlab.AuthURL - tokenURL := gitlab.TokenURL - profileURL := gitlab.ProfileURL - if customURLMapping != nil { - if len(customURLMapping.AuthURL) > 0 { - authURL = customURLMapping.AuthURL - } - if len(customURLMapping.TokenURL) > 0 { - tokenURL = customURLMapping.TokenURL - } - if len(customURLMapping.ProfileURL) > 0 { - profileURL = customURLMapping.ProfileURL - } - } - provider = gitlab.NewCustomisedURL(clientID, clientSecret, callbackURL, authURL, tokenURL, profileURL, "read_user") - case "gplus": // named gplus due to legacy gplus -> google migration (Google killed Google+). This ensures old connections still work - scopes := []string{"email"} - if setting.OAuth2Client.UpdateAvatar || setting.OAuth2Client.EnableAutoRegistration { - scopes = append(scopes, "profile") - } - provider = google.New(clientID, clientSecret, callbackURL, scopes...) - case "openidConnect": - if provider, err = openidConnect.New(clientID, clientSecret, callbackURL, openIDConnectAutoDiscoveryURL, setting.OAuth2Client.OpenIDConnectScopes...); err != nil { - log.Warn("Failed to create OpenID Connect Provider with name '%s' with url '%s': %v", providerName, openIDConnectAutoDiscoveryURL, err) - } - case "twitter": - provider = twitter.NewAuthenticate(clientID, clientSecret, callbackURL) - case "discord": - provider = discord.New(clientID, clientSecret, callbackURL, discord.ScopeIdentify, discord.ScopeEmail) - case "gitea": - authURL := gitea.AuthURL - tokenURL := gitea.TokenURL - profileURL := gitea.ProfileURL - if customURLMapping != nil { - if len(customURLMapping.AuthURL) > 0 { - authURL = customURLMapping.AuthURL - } - if len(customURLMapping.TokenURL) > 0 { - tokenURL = customURLMapping.TokenURL - } - if len(customURLMapping.ProfileURL) > 0 { - profileURL = customURLMapping.ProfileURL - } - } - provider = gitea.NewCustomisedURL(clientID, clientSecret, callbackURL, authURL, tokenURL, profileURL) - case "nextcloud": - authURL := nextcloud.AuthURL - tokenURL := nextcloud.TokenURL - profileURL := nextcloud.ProfileURL - if customURLMapping != nil { - if len(customURLMapping.AuthURL) > 0 { - authURL = customURLMapping.AuthURL - } - if len(customURLMapping.TokenURL) > 0 { - tokenURL = customURLMapping.TokenURL - } - if len(customURLMapping.ProfileURL) > 0 { - profileURL = customURLMapping.ProfileURL - } - } - provider = nextcloud.NewCustomisedURL(clientID, clientSecret, callbackURL, authURL, tokenURL, profileURL) - case "yandex": - // See https://tech.yandex.com/passport/doc/dg/reference/response-docpage/ - provider = yandex.New(clientID, clientSecret, callbackURL, "login:email", "login:info", "login:avatar") - case "mastodon": - instanceURL := mastodon.InstanceURL - if customURLMapping != nil && len(customURLMapping.AuthURL) > 0 { - instanceURL = customURLMapping.AuthURL - } - provider = mastodon.NewCustomisedURL(clientID, clientSecret, callbackURL, instanceURL) + p, ok := gothProviders[source.Provider] + if !ok { + return nil, models.ErrLoginSourceNotActived + } + + provider, err = p.CreateGothProvider(providerName, callbackURL, source) + if err != nil { + return provider, err } // always set the name if provider is created so we can support multiple setups of 1 provider diff --git a/services/auth/source/oauth2/providers_base.go b/services/auth/source/oauth2/providers_base.go new file mode 100644 index 0000000000..b6b6d0bbd2 --- /dev/null +++ b/services/auth/source/oauth2/providers_base.go @@ -0,0 +1,33 @@ +// 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 oauth2 + +// BaseProvider represents a common base for Provider +type BaseProvider struct { + name string + displayName string +} + +// Name provides the technical name for this provider +func (b *BaseProvider) Name() string { + return b.name +} + +// DisplayName returns the friendly name for this provider +func (b *BaseProvider) DisplayName() string { + return b.displayName +} + +// Image returns an image path for this provider +func (b *BaseProvider) Image() string { + return "/assets/img/auth/" + b.name + ".png" +} + +// CustomURLSettings returns the custom url settings for this provider +func (b *BaseProvider) CustomURLSettings() *CustomURLSettings { + return nil +} + +var _ (Provider) = &BaseProvider{} diff --git a/services/auth/source/oauth2/providers_custom.go b/services/auth/source/oauth2/providers_custom.go new file mode 100644 index 0000000000..de1a1690cb --- /dev/null +++ b/services/auth/source/oauth2/providers_custom.go @@ -0,0 +1,118 @@ +// 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 oauth2 + +import ( + "code.gitea.io/gitea/modules/setting" + + "github.com/markbates/goth" + "github.com/markbates/goth/providers/azureadv2" + "github.com/markbates/goth/providers/gitea" + "github.com/markbates/goth/providers/github" + "github.com/markbates/goth/providers/gitlab" + "github.com/markbates/goth/providers/mastodon" + "github.com/markbates/goth/providers/nextcloud" +) + +// CustomProviderNewFn creates a goth.Provider using a custom url mapping +type CustomProviderNewFn func(clientID, secret, callbackURL string, custom *CustomURLMapping) (goth.Provider, error) + +// CustomProvider is a GothProvider that has CustomURL features +type CustomProvider struct { + BaseProvider + customURLSettings *CustomURLSettings + newFn CustomProviderNewFn +} + +// CustomURLSettings returns the CustomURLSettings for this provider +func (c *CustomProvider) CustomURLSettings() *CustomURLSettings { + return c.customURLSettings +} + +// CreateGothProvider creates a GothProvider from this Provider +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) +} + +// NewCustomProvider is a constructor function for custom providers +func NewCustomProvider(name, displayName string, customURLSetting *CustomURLSettings, newFn CustomProviderNewFn) *CustomProvider { + return &CustomProvider{ + BaseProvider: BaseProvider{ + name: name, + displayName: displayName, + }, + customURLSettings: customURLSetting, + newFn: newFn, + } +} + +var _ (GothProvider) = &CustomProvider{} + +func init() { + RegisterGothProvider(NewCustomProvider( + "github", "GitHub", &CustomURLSettings{ + TokenURL: availableAttribute(gitea.TokenURL), + AuthURL: availableAttribute(github.AuthURL), + ProfileURL: availableAttribute(github.ProfileURL), + EmailURL: availableAttribute(github.EmailURL), + }, + func(clientID, secret, callbackURL string, custom *CustomURLMapping) (goth.Provider, error) { + scopes := []string{} + if setting.OAuth2Client.EnableAutoRegistration { + scopes = append(scopes, "user:email") + } + return github.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, custom.EmailURL, scopes...), nil + })) + + RegisterGothProvider(NewCustomProvider( + "gitlab", "GitLab", &CustomURLSettings{ + 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 + })) + + RegisterGothProvider(NewCustomProvider( + "gitea", "Gitea", &CustomURLSettings{ + TokenURL: requiredAttribute(gitea.TokenURL), + 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 + })) + + RegisterGothProvider(NewCustomProvider( + "nextcloud", "Nextcloud", &CustomURLSettings{ + TokenURL: requiredAttribute(nextcloud.TokenURL), + 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 + })) + + 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 + })) + + RegisterGothProvider(NewCustomProvider( + "azureadv2", "Azure AD v2", &CustomURLSettings{ + Tenant: requiredAttribute("organizations"), + }, + func(clientID, secret, callbackURL string, custom *CustomURLMapping) (goth.Provider, error) { + return azureadv2.New(clientID, secret, callbackURL, azureadv2.ProviderOptions{ + Tenant: azureadv2.TenantType(custom.Tenant), + }), nil + }, + )) +} diff --git a/services/auth/source/oauth2/providers_openid.go b/services/auth/source/oauth2/providers_openid.go new file mode 100644 index 0000000000..b725cf9605 --- /dev/null +++ b/services/auth/source/oauth2/providers_openid.go @@ -0,0 +1,52 @@ +// 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 oauth2 + +import ( + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + + "github.com/markbates/goth" + "github.com/markbates/goth/providers/openidConnect" +) + +// OpenIDProvider is a GothProvider for OpenID +type OpenIDProvider struct { +} + +// Name provides the technical name for this provider +func (o *OpenIDProvider) Name() string { + return "openidconnect" +} + +// DisplayName returns the friendly name for this provider +func (o *OpenIDProvider) DisplayName() string { + return "OpenID Connect" +} + +// Image returns an image path for this provider +func (o *OpenIDProvider) Image() string { + return "/assets/img/auth/openid_connect.svg" +} + +// 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...) + if err != nil { + log.Warn("Failed to create OpenID Connect Provider with name '%s' with url '%s': %v", providerName, source.OpenIDConnectAutoDiscoveryURL, err) + } + return provider, err +} + +// CustomURLSettings returns the custom url settings for this provider +func (o *OpenIDProvider) CustomURLSettings() *CustomURLSettings { + return nil +} + +var _ (GothProvider) = &OpenIDProvider{} + +func init() { + RegisterGothProvider(&OpenIDProvider{}) +} diff --git a/services/auth/source/oauth2/providers_simple.go b/services/auth/source/oauth2/providers_simple.go new file mode 100644 index 0000000000..5a7062e6c3 --- /dev/null +++ b/services/auth/source/oauth2/providers_simple.go @@ -0,0 +1,108 @@ +// 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 oauth2 + +import ( + "code.gitea.io/gitea/modules/setting" + + "github.com/markbates/goth" + "github.com/markbates/goth/providers/azuread" + "github.com/markbates/goth/providers/bitbucket" + "github.com/markbates/goth/providers/discord" + "github.com/markbates/goth/providers/dropbox" + "github.com/markbates/goth/providers/facebook" + "github.com/markbates/goth/providers/google" + "github.com/markbates/goth/providers/microsoftonline" + "github.com/markbates/goth/providers/twitter" + "github.com/markbates/goth/providers/yandex" +) + +// SimpleProviderNewFn create goth.Providers without custom url features +type SimpleProviderNewFn func(clientKey, secret, callbackURL string, scopes ...string) goth.Provider + +// SimpleProvider is a GothProvider which does not have custom url features +type SimpleProvider struct { + BaseProvider + scopes []string + newFn SimpleProviderNewFn +} + +// 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 +} + +// NewSimpleProvider is a constructor function for simple providers +func NewSimpleProvider(name, displayName string, scopes []string, newFn SimpleProviderNewFn) *SimpleProvider { + return &SimpleProvider{ + BaseProvider: BaseProvider{ + name: name, + displayName: displayName, + }, + scopes: scopes, + newFn: newFn, + } +} + +var _ (GothProvider) = &SimpleProvider{} + +func init() { + RegisterGothProvider( + NewSimpleProvider("bitbucket", "Bitbucket", []string{"account"}, + func(clientKey, secret, callbackURL string, scopes ...string) goth.Provider { + return bitbucket.New(clientKey, secret, callbackURL, scopes...) + })) + + RegisterGothProvider( + NewSimpleProvider("dropbox", "Dropbox", nil, + func(clientKey, secret, callbackURL string, scopes ...string) goth.Provider { + return dropbox.New(clientKey, secret, callbackURL, scopes...) + })) + + RegisterGothProvider(NewSimpleProvider("facebook", "Facebook", nil, + func(clientKey, secret, callbackURL string, scopes ...string) goth.Provider { + return facebook.New(clientKey, secret, callbackURL, scopes...) + })) + + // named gplus due to legacy gplus -> google migration (Google killed Google+). This ensures old connections still work + RegisterGothProvider(NewSimpleProvider("gplus", "Google", []string{"email"}, + func(clientKey, secret, callbackURL string, scopes ...string) goth.Provider { + if setting.OAuth2Client.UpdateAvatar || setting.OAuth2Client.EnableAutoRegistration { + scopes = append(scopes, "profile") + } + return google.New(clientKey, secret, callbackURL, scopes...) + })) + + RegisterGothProvider(NewSimpleProvider("twitter", "Twitter", nil, + func(clientKey, secret, callbackURL string, scopes ...string) goth.Provider { + return twitter.New(clientKey, secret, callbackURL) + })) + + RegisterGothProvider(NewSimpleProvider("discord", "Discord", []string{discord.ScopeIdentify, discord.ScopeEmail}, + func(clientKey, secret, callbackURL string, scopes ...string) goth.Provider { + return discord.New(clientKey, secret, callbackURL, scopes...) + })) + + // See https://tech.yandex.com/passport/doc/dg/reference/response-docpage/ + RegisterGothProvider(NewSimpleProvider("yandex", "Yandex", []string{"login:email", "login:info", "login:avatar"}, + func(clientKey, secret, callbackURL string, scopes ...string) goth.Provider { + return yandex.New(clientKey, secret, callbackURL, scopes...) + })) + + RegisterGothProvider(NewSimpleProvider( + "azuread", "Azure AD", nil, + func(clientID, secret, callbackURL string, scopes ...string) goth.Provider { + return azuread.New(clientID, secret, callbackURL, nil, scopes...) + }, + )) + + RegisterGothProvider(NewSimpleProvider( + "microsoftonline", "Microsoft Online", nil, + func(clientID, secret, callbackURL string, scopes ...string) goth.Provider { + return microsoftonline.New(clientID, secret, callbackURL, scopes...) + }, + )) + +} diff --git a/services/auth/source/oauth2/source_name.go b/services/auth/source/oauth2/source_name.go new file mode 100644 index 0000000000..0b794ad650 --- /dev/null +++ b/services/auth/source/oauth2/source_name.go @@ -0,0 +1,19 @@ +// 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 oauth2 + +// Name returns the provider name of this source +func (source *Source) Name() string { + return source.Provider +} + +// DisplayName returns the display name of this source +func (source *Source) DisplayName() string { + provider, has := gothProviders[source.Provider] + if !has { + return source.Provider + } + return provider.DisplayName() +} diff --git a/services/auth/source/oauth2/source_register.go b/services/auth/source/oauth2/source_register.go index b61cc3fe79..24c61a9a56 100644 --- a/services/auth/source/oauth2/source_register.go +++ b/services/auth/source/oauth2/source_register.go @@ -10,13 +10,13 @@ import ( // RegisterSource causes an OAuth2 configuration to be registered func (source *Source) RegisterSource() error { - err := RegisterProvider(source.loginSource.Name, source.Provider, source.ClientID, source.ClientSecret, source.OpenIDConnectAutoDiscoveryURL, source.CustomURLMapping) + err := RegisterProviderWithGothic(source.loginSource.Name, source) return wrapOpenIDConnectInitializeError(err, source.loginSource.Name, source) } // UnregisterSource causes an OAuth2 configuration to be unregistered func (source *Source) UnregisterSource() error { - RemoveProvider(source.loginSource.Name) + RemoveProviderFromGothic(source.loginSource.Name) return nil } diff --git a/services/auth/source/oauth2/urlmapping.go b/services/auth/source/oauth2/urlmapping.go index 68829fba21..43c8dde9a5 100644 --- a/services/auth/source/oauth2/urlmapping.go +++ b/services/auth/source/oauth2/urlmapping.go @@ -6,19 +6,73 @@ package oauth2 // CustomURLMapping describes the urls values to use when customizing OAuth2 provider URLs type CustomURLMapping struct { - AuthURL string - TokenURL string - ProfileURL string - EmailURL string + AuthURL string `json:",omitempty"` + TokenURL string `json:",omitempty"` + ProfileURL string `json:",omitempty"` + EmailURL string `json:",omitempty"` + Tenant string `json:",omitempty"` } -// DefaultCustomURLMappings contains the map of default URL's for OAuth2 providers that are allowed to have custom urls -// key is used to map the OAuth2Provider -// value is the mapping as defined for the OAuth2Provider -var DefaultCustomURLMappings = map[string]*CustomURLMapping{ - "github": Providers["github"].CustomURLMapping, - "gitlab": Providers["gitlab"].CustomURLMapping, - "gitea": Providers["gitea"].CustomURLMapping, - "nextcloud": Providers["nextcloud"].CustomURLMapping, - "mastodon": Providers["mastodon"].CustomURLMapping, +// CustomURLSettings describes the urls values and availability to use when customizing OAuth2 provider URLs +type CustomURLSettings struct { + AuthURL Attribute `json:",omitempty"` + TokenURL Attribute `json:",omitempty"` + ProfileURL Attribute `json:",omitempty"` + EmailURL Attribute `json:",omitempty"` + Tenant Attribute `json:",omitempty"` +} + +// Attribute describes the availability, and required status for a custom url configuration +type Attribute struct { + Value string + Available bool + Required bool +} + +func availableAttribute(value string) Attribute { + return Attribute{Value: value, Available: true} +} + +func requiredAttribute(value string) Attribute { + return Attribute{Value: value, Available: true, Required: true} +} + +// Required is true if any attribute is required +func (c *CustomURLSettings) Required() bool { + if c == nil { + return false + } + if c.AuthURL.Required || c.EmailURL.Required || c.ProfileURL.Required || c.TokenURL.Required || c.Tenant.Required { + return true + } + return false +} + +// OverrideWith copies the current customURLMapping and overrides it with values from the provided mapping +func (c *CustomURLSettings) OverrideWith(override *CustomURLMapping) *CustomURLMapping { + custom := &CustomURLMapping{ + AuthURL: c.AuthURL.Value, + TokenURL: c.TokenURL.Value, + ProfileURL: c.ProfileURL.Value, + EmailURL: c.EmailURL.Value, + Tenant: c.Tenant.Value, + } + if override != nil { + if len(override.AuthURL) > 0 && c.AuthURL.Available { + custom.AuthURL = override.AuthURL + } + if len(override.TokenURL) > 0 && c.TokenURL.Available { + custom.TokenURL = override.TokenURL + } + if len(override.ProfileURL) > 0 && c.ProfileURL.Available { + custom.ProfileURL = override.ProfileURL + } + if len(override.EmailURL) > 0 && c.EmailURL.Available { + custom.EmailURL = override.EmailURL + } + if len(override.Tenant) > 0 && c.Tenant.Available { + custom.Tenant = override.Tenant + } + } + return custom } diff --git a/services/forms/auth_form.go b/services/forms/auth_form.go index 30621cadff..b78fa9217e 100644 --- a/services/forms/auth_form.go +++ b/services/forms/auth_form.go @@ -62,6 +62,7 @@ type AuthenticationForm struct { Oauth2ProfileURL string Oauth2EmailURL string Oauth2IconURL string + Oauth2Tenant string SSPIAutoCreateUsers bool SSPIAutoActivateUsers bool SSPIStripDomainNames bool |