Skip to content

Commit 950f2e2

Browse files
willemvdlunny
authored andcommitted
Additional OAuth2 providers (#1010)
* add google+ * sort signin oauth2 providers based on the name so order is always the same * update auth tip for google+ * add gitlab provider * add bitbucket provider (and some go fmt) * add twitter provider * add facebook provider * add dropbox provider * add openid connect provider incl. new format of tips section in "Add New Source" * lower the amount of disk storage for each session to prevent issues while building cross platform (and disk overflow) * imports according to goimport and code style * make it possible to set custom urls to gitlab and github provider (only these could have a different host) * split up oauth2 into multiple files * small typo in comment * fix indention * fix indentation * fix new line before external import * fix layout of signin part * update "broken" dependency
1 parent 2368bbb commit 950f2e2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+4165
-160
lines changed

models/error_oauth2.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2017 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package models
6+
7+
import "fmt"
8+
9+
// ErrOpenIDConnectInitialize represents a "OpenIDConnectInitialize" kind of error.
10+
type ErrOpenIDConnectInitialize struct {
11+
OpenIDConnectAutoDiscoveryURL string
12+
ProviderName string
13+
Cause error
14+
}
15+
16+
// IsErrOpenIDConnectInitialize checks if an error is a ExternalLoginUserAlreadyExist.
17+
func IsErrOpenIDConnectInitialize(err error) bool {
18+
_, ok := err.(ErrOpenIDConnectInitialize)
19+
return ok
20+
}
21+
22+
func (err ErrOpenIDConnectInitialize) Error() string {
23+
return fmt.Sprintf("Failed to initialize OpenID Connect Provider with name '%s' with url '%s': %v", err.ProviderName, err.OpenIDConnectAutoDiscoveryURL, err.Cause)
24+
}

models/login_source.go

Lines changed: 31 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,11 @@ func (cfg *PAMConfig) ToDB() ([]byte, error) {
121121

122122
// OAuth2Config holds configuration for the OAuth2 login source.
123123
type OAuth2Config struct {
124-
Provider string
125-
ClientID string
126-
ClientSecret string
124+
Provider string
125+
ClientID string
126+
ClientSecret string
127+
OpenIDConnectAutoDiscoveryURL string
128+
CustomURLMapping *oauth2.CustomURLMapping
127129
}
128130

129131
// FromDB fills up an OAuth2Config from serialized format.
@@ -294,9 +296,15 @@ func CreateLoginSource(source *LoginSource) error {
294296
}
295297

296298
_, err = x.Insert(source)
297-
if err == nil && source.IsOAuth2() {
299+
if err == nil && source.IsOAuth2() && source.IsActived {
298300
oAuth2Config := source.OAuth2()
299-
oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret)
301+
err = oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret, oAuth2Config.OpenIDConnectAutoDiscoveryURL, oAuth2Config.CustomURLMapping)
302+
err = wrapOpenIDConnectInitializeError(err, source.Name, oAuth2Config)
303+
304+
if err != nil {
305+
// remove the LoginSource in case of errors while registering OAuth2 providers
306+
x.Delete(source)
307+
}
300308
}
301309
return err
302310
}
@@ -321,11 +329,25 @@ func GetLoginSourceByID(id int64) (*LoginSource, error) {
321329

322330
// UpdateSource updates a LoginSource record in DB.
323331
func UpdateSource(source *LoginSource) error {
332+
var originalLoginSource *LoginSource
333+
if source.IsOAuth2() {
334+
// keep track of the original values so we can restore in case of errors while registering OAuth2 providers
335+
var err error
336+
if originalLoginSource, err = GetLoginSourceByID(source.ID); err != nil {
337+
return err
338+
}
339+
}
340+
324341
_, err := x.Id(source.ID).AllCols().Update(source)
325-
if err == nil && source.IsOAuth2() {
342+
if err == nil && source.IsOAuth2() && source.IsActived {
326343
oAuth2Config := source.OAuth2()
327-
oauth2.RemoveProvider(source.Name)
328-
oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret)
344+
err = oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret, oAuth2Config.OpenIDConnectAutoDiscoveryURL, oAuth2Config.CustomURLMapping)
345+
err = wrapOpenIDConnectInitializeError(err, source.Name, oAuth2Config)
346+
347+
if err != nil {
348+
// restore original values since we cannot update the provider it self
349+
x.Id(source.ID).AllCols().Update(originalLoginSource)
350+
}
329351
}
330352
return err
331353
}
@@ -580,27 +602,6 @@ func LoginViaPAM(user *User, login, password string, sourceID int64, cfg *PAMCon
580602
return user, CreateUser(user)
581603
}
582604

583-
// ________ _____ __ .__ ________
584-
// \_____ \ / _ \ __ ___/ |_| |__ \_____ \
585-
// / | \ / /_\ \| | \ __\ | \ / ____/
586-
// / | \/ | \ | /| | | Y \/ \
587-
// \_______ /\____|__ /____/ |__| |___| /\_______ \
588-
// \/ \/ \/ \/
589-
590-
// OAuth2Provider describes the display values of a single OAuth2 provider
591-
type OAuth2Provider struct {
592-
Name string
593-
DisplayName string
594-
Image string
595-
}
596-
597-
// OAuth2Providers contains the map of registered OAuth2 providers in Gitea (based on goth)
598-
// key is used to map the OAuth2Provider with the goth provider type (also in LoginSource.OAuth2Config.Provider)
599-
// value is used to store display data
600-
var OAuth2Providers = map[string]OAuth2Provider{
601-
"github": {Name: "github", DisplayName: "GitHub", Image: "/img/github.png"},
602-
}
603-
604605
// ExternalUserLogin attempts a login using external source types.
605606
func ExternalUserLogin(user *User, login, password string, source *LoginSource, autoRegister bool) (*User, error) {
606607
if !source.IsActived {
@@ -684,59 +685,4 @@ func UserSignIn(username, password string) (*User, error) {
684685
}
685686

686687
return nil, ErrUserNotExist{user.ID, user.Name, 0}
687-
}
688-
689-
// GetActiveOAuth2ProviderLoginSources returns all actived LoginOAuth2 sources
690-
func GetActiveOAuth2ProviderLoginSources() ([]*LoginSource, error) {
691-
sources := make([]*LoginSource, 0, 1)
692-
if err := x.UseBool().Find(&sources, &LoginSource{IsActived: true, Type: LoginOAuth2}); err != nil {
693-
return nil, err
694-
}
695-
return sources, nil
696-
}
697-
698-
// GetActiveOAuth2LoginSourceByName returns a OAuth2 LoginSource based on the given name
699-
func GetActiveOAuth2LoginSourceByName(name string) (*LoginSource, error) {
700-
loginSource := &LoginSource{
701-
Name: name,
702-
Type: LoginOAuth2,
703-
IsActived: true,
704-
}
705-
706-
has, err := x.UseBool().Get(loginSource)
707-
if !has || err != nil {
708-
return nil, err
709-
}
710-
711-
return loginSource, nil
712-
}
713-
714-
// GetActiveOAuth2Providers returns the map of configured active OAuth2 providers
715-
// key is used as technical name (like in the callbackURL)
716-
// values to display
717-
func GetActiveOAuth2Providers() (map[string]OAuth2Provider, error) {
718-
// Maybe also separate used and unused providers so we can force the registration of only 1 active provider for each type
719-
720-
loginSources, err := GetActiveOAuth2ProviderLoginSources()
721-
if err != nil {
722-
return nil, err
723-
}
724-
725-
providers := make(map[string]OAuth2Provider)
726-
for _, source := range loginSources {
727-
providers[source.Name] = OAuth2Providers[source.OAuth2().Provider]
728-
}
729-
730-
return providers, nil
731-
}
732-
733-
// InitOAuth2 initialize the OAuth2 lib and register all active OAuth2 providers in the library
734-
func InitOAuth2() {
735-
oauth2.Init()
736-
loginSources, _ := GetActiveOAuth2ProviderLoginSources()
737-
738-
for _, source := range loginSources {
739-
oAuth2Config := source.OAuth2()
740-
oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret)
741-
}
742-
}
688+
}

models/oauth2.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright 2017 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package models
6+
7+
import (
8+
"sort"
9+
"code.gitea.io/gitea/modules/auth/oauth2"
10+
)
11+
12+
// OAuth2Provider describes the display values of a single OAuth2 provider
13+
type OAuth2Provider struct {
14+
Name string
15+
DisplayName string
16+
Image string
17+
CustomURLMapping *oauth2.CustomURLMapping
18+
}
19+
20+
// OAuth2Providers contains the map of registered OAuth2 providers in Gitea (based on goth)
21+
// key is used to map the OAuth2Provider with the goth provider type (also in LoginSource.OAuth2Config.Provider)
22+
// value is used to store display data
23+
var OAuth2Providers = map[string]OAuth2Provider{
24+
"bitbucket": {Name: "bitbucket", DisplayName: "Bitbucket", Image: "/img/auth/bitbucket.png"},
25+
"dropbox": {Name: "dropbox", DisplayName: "Dropbox", Image: "/img/auth/dropbox.png"},
26+
"facebook": {Name: "facebook", DisplayName: "Facebook", Image: "/img/auth/facebook.png"},
27+
"github": {Name: "github", DisplayName: "GitHub", Image: "/img/auth/github.png",
28+
CustomURLMapping: &oauth2.CustomURLMapping{
29+
TokenURL: oauth2.GetDefaultTokenURL("github"),
30+
AuthURL: oauth2.GetDefaultAuthURL("github"),
31+
ProfileURL: oauth2.GetDefaultProfileURL("github"),
32+
EmailURL: oauth2.GetDefaultEmailURL("github"),
33+
},
34+
},
35+
"gitlab": {Name: "gitlab", DisplayName: "GitLab", Image: "/img/auth/gitlab.png",
36+
CustomURLMapping: &oauth2.CustomURLMapping{
37+
TokenURL: oauth2.GetDefaultTokenURL("gitlab"),
38+
AuthURL: oauth2.GetDefaultAuthURL("gitlab"),
39+
ProfileURL: oauth2.GetDefaultProfileURL("gitlab"),
40+
},
41+
},
42+
"gplus": {Name: "gplus", DisplayName: "Google+", Image: "/img/auth/google_plus.png"},
43+
"openidConnect": {Name: "openidConnect", DisplayName: "OpenID Connect", Image: "/img/auth/openid_connect.png"},
44+
"twitter": {Name: "twitter", DisplayName: "Twitter", Image: "/img/auth/twitter.png"},
45+
}
46+
47+
// OAuth2DefaultCustomURLMappings contains the map of default URL's for OAuth2 providers that are allowed to have custom urls
48+
// key is used to map the OAuth2Provider
49+
// value is the mapping as defined for the OAuth2Provider
50+
var OAuth2DefaultCustomURLMappings = map[string]*oauth2.CustomURLMapping {
51+
"github": OAuth2Providers["github"].CustomURLMapping,
52+
"gitlab": OAuth2Providers["gitlab"].CustomURLMapping,
53+
}
54+
55+
// GetActiveOAuth2ProviderLoginSources returns all actived LoginOAuth2 sources
56+
func GetActiveOAuth2ProviderLoginSources() ([]*LoginSource, error) {
57+
sources := make([]*LoginSource, 0, 1)
58+
if err := x.UseBool().Find(&sources, &LoginSource{IsActived: true, Type: LoginOAuth2}); err != nil {
59+
return nil, err
60+
}
61+
return sources, nil
62+
}
63+
64+
// GetActiveOAuth2LoginSourceByName returns a OAuth2 LoginSource based on the given name
65+
func GetActiveOAuth2LoginSourceByName(name string) (*LoginSource, error) {
66+
loginSource := &LoginSource{
67+
Name: name,
68+
Type: LoginOAuth2,
69+
IsActived: true,
70+
}
71+
72+
has, err := x.UseBool().Get(loginSource)
73+
if !has || err != nil {
74+
return nil, err
75+
}
76+
77+
return loginSource, nil
78+
}
79+
80+
// GetActiveOAuth2Providers returns the map of configured active OAuth2 providers
81+
// key is used as technical name (like in the callbackURL)
82+
// values to display
83+
func GetActiveOAuth2Providers() ([]string, map[string]OAuth2Provider, error) {
84+
// Maybe also separate used and unused providers so we can force the registration of only 1 active provider for each type
85+
86+
loginSources, err := GetActiveOAuth2ProviderLoginSources()
87+
if err != nil {
88+
return nil, nil, err
89+
}
90+
91+
var orderedKeys []string
92+
providers := make(map[string]OAuth2Provider)
93+
for _, source := range loginSources {
94+
providers[source.Name] = OAuth2Providers[source.OAuth2().Provider]
95+
orderedKeys = append(orderedKeys, source.Name)
96+
}
97+
98+
sort.Strings(orderedKeys)
99+
100+
return orderedKeys, providers, nil
101+
}
102+
103+
// InitOAuth2 initialize the OAuth2 lib and register all active OAuth2 providers in the library
104+
func InitOAuth2() {
105+
oauth2.Init()
106+
loginSources, _ := GetActiveOAuth2ProviderLoginSources()
107+
108+
for _, source := range loginSources {
109+
oAuth2Config := source.OAuth2()
110+
oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret, oAuth2Config.OpenIDConnectAutoDiscoveryURL, oAuth2Config.CustomURLMapping)
111+
}
112+
}
113+
114+
// wrapOpenIDConnectInitializeError is used to wrap the error but this cannot be done in modules/auth/oauth2
115+
// inside oauth2: import cycle not allowed models -> modules/auth/oauth2 -> models
116+
func wrapOpenIDConnectInitializeError(err error, providerName string, oAuth2Config *OAuth2Config) error {
117+
if err != nil && "openidConnect" == oAuth2Config.Provider {
118+
err = ErrOpenIDConnectInitialize{ProviderName: providerName, OpenIDConnectAutoDiscoveryURL: oAuth2Config.OpenIDConnectAutoDiscoveryURL, Cause: err}
119+
}
120+
return err
121+
}
122+

modules/auth/auth_form.go

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,34 +11,40 @@ import (
1111

1212
// AuthenticationForm form for authentication
1313
type AuthenticationForm struct {
14-
ID int64
15-
Type int `binding:"Range(2,6)"`
16-
Name string `binding:"Required;MaxSize(30)"`
17-
Host string
18-
Port int
19-
BindDN string
20-
BindPassword string
21-
UserBase string
22-
UserDN string
23-
AttributeUsername string
24-
AttributeName string
25-
AttributeSurname string
26-
AttributeMail string
27-
AttributesInBind bool
28-
Filter string
29-
AdminFilter string
30-
IsActive bool
31-
SMTPAuth string
32-
SMTPHost string
33-
SMTPPort int
34-
AllowedDomains string
35-
SecurityProtocol int `binding:"Range(0,2)"`
36-
TLS bool
37-
SkipVerify bool
38-
PAMServiceName string
39-
Oauth2Provider string
40-
Oauth2Key string
41-
Oauth2Secret string
14+
ID int64
15+
Type int `binding:"Range(2,6)"`
16+
Name string `binding:"Required;MaxSize(30)"`
17+
Host string
18+
Port int
19+
BindDN string
20+
BindPassword string
21+
UserBase string
22+
UserDN string
23+
AttributeUsername string
24+
AttributeName string
25+
AttributeSurname string
26+
AttributeMail string
27+
AttributesInBind bool
28+
Filter string
29+
AdminFilter string
30+
IsActive bool
31+
SMTPAuth string
32+
SMTPHost string
33+
SMTPPort int
34+
AllowedDomains string
35+
SecurityProtocol int `binding:"Range(0,2)"`
36+
TLS bool
37+
SkipVerify bool
38+
PAMServiceName string
39+
Oauth2Provider string
40+
Oauth2Key string
41+
Oauth2Secret string
42+
OpenIDConnectAutoDiscoveryURL string
43+
Oauth2UseCustomURL bool
44+
Oauth2TokenURL string
45+
Oauth2AuthURL string
46+
Oauth2ProfileURL string
47+
Oauth2EmailURL string
4248
}
4349

4450
// Validate validates fields

0 commit comments

Comments
 (0)