Skip to content

Commit

Permalink
Additional OAuth2 providers (#1010)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
willemvd authored and lunny committed May 1, 2017
1 parent 2368bbb commit 950f2e2
Show file tree
Hide file tree
Showing 44 changed files with 4,165 additions and 160 deletions.
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 {
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"},
}

// 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

0 comments on commit 950f2e2

Please sign in to comment.