Skip to content

Additional OAuth2 providers #1010

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
May 1, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b98777f
add google+
willemvd Feb 21, 2017
a24e238
sort signin oauth2 providers based on the name so order is always the…
willemvd Feb 21, 2017
02638f0
update auth tip for google+
willemvd Feb 21, 2017
6c48e16
add gitlab provider
willemvd Feb 21, 2017
6a5b0c3
add bitbucket provider (and some go fmt)
willemvd Feb 21, 2017
213e67b
add twitter provider
willemvd Feb 21, 2017
92aec11
add facebook provider
willemvd Feb 21, 2017
9e797b2
add dropbox provider
willemvd Feb 21, 2017
107eeea
add openid connect provider incl. new format of tips section in "Add …
willemvd Feb 22, 2017
94c8a48
lower the amount of disk storage for each session to prevent issues w…
willemvd Feb 22, 2017
8b8b5f4
imports according to goimport and code style
willemvd Feb 22, 2017
e2544ae
make it possible to set custom urls to gitlab and github provider (on…
willemvd Feb 24, 2017
ab95945
split up oauth2 into multiple files
willemvd Feb 24, 2017
1c030e9
Merge branch 'upstream-master' into additional-oauth2-providers
willemvd Feb 27, 2017
cb3b4e7
Merge branch 'upstream-master' into additional-oauth2-providers
willemvd Mar 10, 2017
6aeea3f
small typo in comment
willemvd Mar 10, 2017
da3a2b9
Merge branch 'upstream-master' into additional-oauth2-providers
willemvd Mar 15, 2017
e0ad523
Merge branch 'upstream-master' into additional-oauth2-providers
willemvd Mar 15, 2017
675e61e
fix merge master
willemvd Mar 17, 2017
33ad313
Merge branch 'upstream-master' into additional-oauth2-providers
willemvd Mar 28, 2017
92e802d
fix indention
willemvd Mar 28, 2017
7c63b03
Merge branch 'upstream-master' into additional-oauth2-providers
willemvd Apr 6, 2017
5f0067e
fix indentation
willemvd Apr 28, 2017
922c5d4
fix new line before external import
willemvd Apr 28, 2017
e18dfe5
fix layout of signin part
willemvd Apr 28, 2017
2342225
Merge branch 'upstream-master' into additional-oauth2-providers
willemvd Apr 28, 2017
fe450e2
update "broken" dependency
willemvd May 1, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions models/error_oauth2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright 2017 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 models

import "fmt"

// ErrOpenIDConnectInitialize represents a "OpenIDConnectInitialize" kind of error.
type ErrOpenIDConnectInitialize struct {
OpenIDConnectAutoDiscoveryURL string
ProviderName string
Cause error
}

// IsErrOpenIDConnectInitialize checks if an error is a ExternalLoginUserAlreadyExist.
func IsErrOpenIDConnectInitialize(err error) bool {
_, ok := err.(ErrOpenIDConnectInitialize)
return ok
}

func (err ErrOpenIDConnectInitialize) Error() string {
return fmt.Sprintf("Failed to initialize OpenID Connect Provider with name '%s' with url '%s': %v", err.ProviderName, err.OpenIDConnectAutoDiscoveryURL, err.Cause)
}
116 changes: 31 additions & 85 deletions models/login_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,11 @@ func (cfg *PAMConfig) ToDB() ([]byte, error) {

// OAuth2Config holds configuration for the OAuth2 login source.
type OAuth2Config struct {
Provider string
ClientID string
ClientSecret string
Provider string
ClientID string
ClientSecret string
OpenIDConnectAutoDiscoveryURL string
CustomURLMapping *oauth2.CustomURLMapping
}

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

_, err = x.Insert(source)
if err == nil && source.IsOAuth2() {
if err == nil && source.IsOAuth2() && source.IsActived {
oAuth2Config := source.OAuth2()
oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret)
err = oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret, oAuth2Config.OpenIDConnectAutoDiscoveryURL, oAuth2Config.CustomURLMapping)
err = wrapOpenIDConnectInitializeError(err, source.Name, oAuth2Config)

if err != nil {
// remove the LoginSource in case of errors while registering OAuth2 providers
x.Delete(source)
}
}
return err
}
Expand All @@ -321,11 +329,25 @@ func GetLoginSourceByID(id int64) (*LoginSource, error) {

// UpdateSource updates a LoginSource record in DB.
func UpdateSource(source *LoginSource) error {
var originalLoginSource *LoginSource
if source.IsOAuth2() {
// keep track of the original values so we can restore in case of errors while registering OAuth2 providers
var err error
if originalLoginSource, err = GetLoginSourceByID(source.ID); err != nil {
return err
}
}

_, err := x.Id(source.ID).AllCols().Update(source)
if err == nil && source.IsOAuth2() {
if err == nil && source.IsOAuth2() && source.IsActived {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling: source.IsActive , this needs to be renamed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is reuse of existing code, so I think out of scope for this PR

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not only that, it's also in a DB model so changing that field would require a migration :/

oAuth2Config := source.OAuth2()
oauth2.RemoveProvider(source.Name)
oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret)
err = oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret, oAuth2Config.OpenIDConnectAutoDiscoveryURL, oAuth2Config.CustomURLMapping)
err = wrapOpenIDConnectInitializeError(err, source.Name, oAuth2Config)

if err != nil {
// restore original values since we cannot update the provider it self
x.Id(source.ID).AllCols().Update(originalLoginSource)
}
}
return err
}
Expand Down Expand Up @@ -580,27 +602,6 @@ func LoginViaPAM(user *User, login, password string, sourceID int64, cfg *PAMCon
return user, CreateUser(user)
}

// ________ _____ __ .__ ________
// \_____ \ / _ \ __ ___/ |_| |__ \_____ \
// / | \ / /_\ \| | \ __\ | \ / ____/
// / | \/ | \ | /| | | Y \/ \
// \_______ /\____|__ /____/ |__| |___| /\_______ \
// \/ \/ \/ \/

// OAuth2Provider describes the display values of a single OAuth2 provider
type OAuth2Provider struct {
Name string
DisplayName string
Image string
}

// OAuth2Providers 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 OAuth2Providers = map[string]OAuth2Provider{
"github": {Name: "github", DisplayName: "GitHub", Image: "/img/github.png"},
}

// ExternalUserLogin attempts a login using external source types.
func ExternalUserLogin(user *User, login, password string, source *LoginSource, autoRegister bool) (*User, error) {
if !source.IsActived {
Expand Down Expand Up @@ -684,59 +685,4 @@ func UserSignIn(username, password string) (*User, error) {
}

return nil, ErrUserNotExist{user.ID, user.Name, 0}
}

// GetActiveOAuth2ProviderLoginSources returns all actived LoginOAuth2 sources
func GetActiveOAuth2ProviderLoginSources() ([]*LoginSource, error) {
sources := make([]*LoginSource, 0, 1)
if err := x.UseBool().Find(&sources, &LoginSource{IsActived: true, Type: LoginOAuth2}); err != nil {
return nil, err
}
return sources, nil
}

// GetActiveOAuth2LoginSourceByName returns a OAuth2 LoginSource based on the given name
func GetActiveOAuth2LoginSourceByName(name string) (*LoginSource, error) {
loginSource := &LoginSource{
Name: name,
Type: LoginOAuth2,
IsActived: true,
}

has, err := x.UseBool().Get(loginSource)
if !has || err != nil {
return nil, err
}

return loginSource, nil
}

// GetActiveOAuth2Providers returns the map of configured active OAuth2 providers
// key is used as technical name (like in the callbackURL)
// values to display
func GetActiveOAuth2Providers() (map[string]OAuth2Provider, error) {
// Maybe also separate used and unused providers so we can force the registration of only 1 active provider for each type

loginSources, err := GetActiveOAuth2ProviderLoginSources()
if err != nil {
return nil, err
}

providers := make(map[string]OAuth2Provider)
for _, source := range loginSources {
providers[source.Name] = OAuth2Providers[source.OAuth2().Provider]
}

return providers, nil
}

// InitOAuth2 initialize the OAuth2 lib and register all active OAuth2 providers in the library
func InitOAuth2() {
oauth2.Init()
loginSources, _ := GetActiveOAuth2ProviderLoginSources()

for _, source := range loginSources {
oAuth2Config := source.OAuth2()
oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret)
}
}
}
122 changes: 122 additions & 0 deletions models/oauth2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Copyright 2017 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 models

import (
"sort"
"code.gitea.io/gitea/modules/auth/oauth2"
)

// OAuth2Provider describes the display values of a single OAuth2 provider
type OAuth2Provider struct {
Name string
DisplayName string
Image string
CustomURLMapping *oauth2.CustomURLMapping
}

// OAuth2Providers 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 OAuth2Providers = map[string]OAuth2Provider{
"bitbucket": {Name: "bitbucket", DisplayName: "Bitbucket", Image: "/img/auth/bitbucket.png"},
"dropbox": {Name: "dropbox", DisplayName: "Dropbox", Image: "/img/auth/dropbox.png"},
"facebook": {Name: "facebook", DisplayName: "Facebook", Image: "/img/auth/facebook.png"},
"github": {Name: "github", DisplayName: "GitHub", Image: "/img/auth/github.png",
CustomURLMapping: &oauth2.CustomURLMapping{
TokenURL: oauth2.GetDefaultTokenURL("github"),
AuthURL: oauth2.GetDefaultAuthURL("github"),
ProfileURL: oauth2.GetDefaultProfileURL("github"),
EmailURL: oauth2.GetDefaultEmailURL("github"),
},
},
"gitlab": {Name: "gitlab", DisplayName: "GitLab", Image: "/img/auth/gitlab.png",
CustomURLMapping: &oauth2.CustomURLMapping{
TokenURL: oauth2.GetDefaultTokenURL("gitlab"),
AuthURL: oauth2.GetDefaultAuthURL("gitlab"),
ProfileURL: oauth2.GetDefaultProfileURL("gitlab"),
},
},
"gplus": {Name: "gplus", DisplayName: "Google+", Image: "/img/auth/google_plus.png"},
"openidConnect": {Name: "openidConnect", DisplayName: "OpenID Connect", Image: "/img/auth/openid_connect.png"},
"twitter": {Name: "twitter", DisplayName: "Twitter", Image: "/img/auth/twitter.png"},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add stack exchange as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think this is not yet available in https://github.com/markbates/goth/#supported-providers or is it named differently?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for the record; stackexchange (also?) supports OpenID-2.0, and you can use openid.stackexchange.com to login (will be converted to actual URL upon logging in)

}

// OAuth2DefaultCustomURLMappings 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 OAuth2DefaultCustomURLMappings = map[string]*oauth2.CustomURLMapping {
"github": OAuth2Providers["github"].CustomURLMapping,
"gitlab": OAuth2Providers["gitlab"].CustomURLMapping,
}

// GetActiveOAuth2ProviderLoginSources returns all actived LoginOAuth2 sources
func GetActiveOAuth2ProviderLoginSources() ([]*LoginSource, error) {
sources := make([]*LoginSource, 0, 1)
if err := x.UseBool().Find(&sources, &LoginSource{IsActived: true, Type: LoginOAuth2}); err != nil {
return nil, err
}
return sources, nil
}

// GetActiveOAuth2LoginSourceByName returns a OAuth2 LoginSource based on the given name
func GetActiveOAuth2LoginSourceByName(name string) (*LoginSource, error) {
loginSource := &LoginSource{
Name: name,
Type: LoginOAuth2,
IsActived: true,
}

has, err := x.UseBool().Get(loginSource)
if !has || err != nil {
return nil, err
}

return loginSource, nil
}

// GetActiveOAuth2Providers returns the map of configured active OAuth2 providers
// key is used as technical name (like in the callbackURL)
// values to display
func GetActiveOAuth2Providers() ([]string, map[string]OAuth2Provider, error) {
// Maybe also separate used and unused providers so we can force the registration of only 1 active provider for each type

loginSources, err := GetActiveOAuth2ProviderLoginSources()
if err != nil {
return nil, nil, err
}

var orderedKeys []string
providers := make(map[string]OAuth2Provider)
for _, source := range loginSources {
providers[source.Name] = OAuth2Providers[source.OAuth2().Provider]
orderedKeys = append(orderedKeys, source.Name)
}

sort.Strings(orderedKeys)

return orderedKeys, providers, nil
}

// InitOAuth2 initialize the OAuth2 lib and register all active OAuth2 providers in the library
func InitOAuth2() {
oauth2.Init()
loginSources, _ := GetActiveOAuth2ProviderLoginSources()

for _, source := range loginSources {
oAuth2Config := source.OAuth2()
oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret, oAuth2Config.OpenIDConnectAutoDiscoveryURL, oAuth2Config.CustomURLMapping)
}
}

// wrapOpenIDConnectInitializeError is used to wrap the error but this cannot be done in modules/auth/oauth2
// inside oauth2: import cycle not allowed models -> modules/auth/oauth2 -> models
func wrapOpenIDConnectInitializeError(err error, providerName string, oAuth2Config *OAuth2Config) error {
if err != nil && "openidConnect" == oAuth2Config.Provider {
err = ErrOpenIDConnectInitialize{ProviderName: providerName, OpenIDConnectAutoDiscoveryURL: oAuth2Config.OpenIDConnectAutoDiscoveryURL, Cause: err}
}
return err
}

62 changes: 34 additions & 28 deletions modules/auth/auth_form.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,40 @@ import (

// AuthenticationForm form for authentication
type AuthenticationForm struct {
ID int64
Type int `binding:"Range(2,6)"`
Name string `binding:"Required;MaxSize(30)"`
Host string
Port int
BindDN string
BindPassword string
UserBase string
UserDN string
AttributeUsername string
AttributeName string
AttributeSurname string
AttributeMail string
AttributesInBind bool
Filter string
AdminFilter string
IsActive bool
SMTPAuth string
SMTPHost string
SMTPPort int
AllowedDomains string
SecurityProtocol int `binding:"Range(0,2)"`
TLS bool
SkipVerify bool
PAMServiceName string
Oauth2Provider string
Oauth2Key string
Oauth2Secret string
ID int64
Type int `binding:"Range(2,6)"`
Name string `binding:"Required;MaxSize(30)"`
Host string
Port int
BindDN string
BindPassword string
UserBase string
UserDN string
AttributeUsername string
AttributeName string
AttributeSurname string
AttributeMail string
AttributesInBind bool
Filter string
AdminFilter string
IsActive bool
SMTPAuth string
SMTPHost string
SMTPPort int
AllowedDomains string
SecurityProtocol int `binding:"Range(0,2)"`
TLS bool
SkipVerify bool
PAMServiceName string
Oauth2Provider string
Oauth2Key string
Oauth2Secret string
OpenIDConnectAutoDiscoveryURL string
Oauth2UseCustomURL bool
Oauth2TokenURL string
Oauth2AuthURL string
Oauth2ProfileURL string
Oauth2EmailURL string
}

// Validate validates fields
Expand Down
Loading