-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Oauth2 consumer #679
Oauth2 consumer #679
Changes from 3 commits
ded3513
1f94c85
642f36e
f93aad8
280913b
b597a86
8c2be7a
f392ce9
2ba7833
8e1ea96
6c98fa7
caeb911
f3f3866
047d50b
ab31c24
fe88e87
827c512
83c238b
c65a216
914f56a
b4eb93c
7a6757f
aae5f80
9151dc8
96d1af5
c3f5d36
2bf6b34
7ccbc44
084c45f
6594d76
19ddb15
57dbb74
32a4c58
770ba31
527d6e1
a7381b5
b64ee7d
5c5214a
769c747
6b16f42
0fa2e40
f54f07a
873e5b7
779e84b
61fe261
66e28df
2c11c44
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,7 +21,10 @@ import ( | |
|
||
"code.gitea.io/gitea/modules/auth/ldap" | ||
"code.gitea.io/gitea/modules/auth/pam" | ||
"code.gitea.io/gitea/modules/auth/oauth2" | ||
"code.gitea.io/gitea/modules/log" | ||
"gopkg.in/macaron.v1" | ||
"github.com/go-macaron/session" | ||
) | ||
|
||
// LoginType represents an login type. | ||
|
@@ -30,19 +33,21 @@ type LoginType int | |
// Note: new type must append to the end of list to maintain compatibility. | ||
const ( | ||
LoginNoType LoginType = iota | ||
LoginPlain // 1 | ||
LoginLDAP // 2 | ||
LoginSMTP // 3 | ||
LoginPAM // 4 | ||
LoginDLDAP // 5 | ||
LoginPlain // 1 | ||
LoginLDAP // 2 | ||
LoginSMTP // 3 | ||
LoginPAM // 4 | ||
LoginDLDAP // 5 | ||
LoginOAuth2 // 6 | ||
) | ||
|
||
// LoginNames contains the name of LoginType values. | ||
var LoginNames = map[LoginType]string{ | ||
LoginLDAP: "LDAP (via BindDN)", | ||
LoginDLDAP: "LDAP (simple auth)", // Via direct bind | ||
LoginSMTP: "SMTP", | ||
LoginPAM: "PAM", | ||
LoginLDAP: "LDAP (via BindDN)", | ||
LoginDLDAP: "LDAP (simple auth)", // Via direct bind | ||
LoginSMTP: "SMTP", | ||
LoginPAM: "PAM", | ||
LoginOAuth2: "OAuth2", | ||
} | ||
|
||
// SecurityProtocolNames contains the name of SecurityProtocol values. | ||
|
@@ -57,6 +62,7 @@ var ( | |
_ core.Conversion = &LDAPConfig{} | ||
_ core.Conversion = &SMTPConfig{} | ||
_ core.Conversion = &PAMConfig{} | ||
_ core.Conversion = &OAuth2Config{} | ||
) | ||
|
||
// LDAPConfig holds configuration for LDAP login source. | ||
|
@@ -115,6 +121,23 @@ func (cfg *PAMConfig) ToDB() ([]byte, error) { | |
return json.Marshal(cfg) | ||
} | ||
|
||
// OAuth2Config holds configuration for the OAuth2 login source. | ||
type OAuth2Config struct { | ||
Provider string | ||
ClientID string | ||
ClientSecret string | ||
} | ||
|
||
// FromDB fills up an OAuth2Config from serialized format. | ||
func (cfg *OAuth2Config) FromDB(bs []byte) error { | ||
return json.Unmarshal(bs, cfg) | ||
} | ||
|
||
// ToDB exports an SMTPConfig to a serialized format. | ||
func (cfg *OAuth2Config) ToDB() ([]byte, error) { | ||
return json.Marshal(cfg) | ||
} | ||
|
||
// LoginSource represents an external way for authorizing users. | ||
type LoginSource struct { | ||
ID int64 `xorm:"pk autoincr"` | ||
|
@@ -162,6 +185,8 @@ func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) { | |
source.Cfg = new(SMTPConfig) | ||
case LoginPAM: | ||
source.Cfg = new(PAMConfig) | ||
case LoginOAuth2: | ||
source.Cfg = new(OAuth2Config) | ||
default: | ||
panic("unrecognized login source type: " + com.ToStr(*val)) | ||
} | ||
|
@@ -203,6 +228,11 @@ func (source *LoginSource) IsPAM() bool { | |
return source.Type == LoginPAM | ||
} | ||
|
||
// IsOAuth2 returns true of this source is of the OAuth2 type. | ||
func (source *LoginSource) IsOAuth2() bool { | ||
return source.Type == LoginOAuth2 | ||
} | ||
|
||
// HasTLS returns true of this source supports TLS. | ||
func (source *LoginSource) HasTLS() bool { | ||
return ((source.IsLDAP() || source.IsDLDAP()) && | ||
|
@@ -217,6 +247,8 @@ func (source *LoginSource) UseTLS() bool { | |
return source.LDAP().SecurityProtocol != ldap.SecurityProtocolUnencrypted | ||
case LoginSMTP: | ||
return source.SMTP().TLS | ||
case LoginOAuth2: | ||
return true | ||
} | ||
|
||
return false | ||
|
@@ -250,6 +282,11 @@ func (source *LoginSource) PAM() *PAMConfig { | |
return source.Cfg.(*PAMConfig) | ||
} | ||
|
||
// OAuth2 returns OAuth2Config for this source, if of OAuth2 type. | ||
func (source *LoginSource) OAuth2() *OAuth2Config { | ||
return source.Cfg.(*OAuth2Config) | ||
} | ||
|
||
// CreateLoginSource inserts a LoginSource in the DB if not already | ||
// existing with the given name. | ||
func CreateLoginSource(source *LoginSource) error { | ||
|
@@ -266,7 +303,7 @@ func CreateLoginSource(source *LoginSource) error { | |
|
||
// LoginSources returns a slice of all login sources found in DB. | ||
func LoginSources() ([]*LoginSource, error) { | ||
auths := make([]*LoginSource, 0, 5) | ||
auths := make([]*LoginSource, 0, 6) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we maybe use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this can be any value, since it will look for all the configured login sources (and so depends on the environment how many this will be) |
||
return auths, x.Find(&auths) | ||
} | ||
|
||
|
@@ -444,7 +481,7 @@ func LoginViaSMTP(user *User, login, password string, sourceID int64, cfg *SMTPC | |
idx := strings.Index(login, "@") | ||
if idx == -1 { | ||
return nil, ErrUserNotExist{0, login, 0} | ||
} else if !com.IsSliceContainsStr(strings.Split(cfg.AllowedDomains, ","), login[idx+1:]) { | ||
} else if !com.IsSliceContainsStr(strings.Split(cfg.AllowedDomains, ","), login[idx + 1:]) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this done by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. gofmt did this |
||
return nil, ErrUserNotExist{0, login, 0} | ||
} | ||
} | ||
|
@@ -526,8 +563,21 @@ func LoginViaPAM(user *User, login, password string, sourceID int64, cfg *PAMCon | |
return user, CreateUser(user) | ||
} | ||
|
||
// ________ _____ __ .__ ________ | ||
// \_____ \ / _ \ __ ___/ |_| |__ \_____ \ | ||
// / | \ / /_\ \| | \ __\ | \ / ____/ | ||
// / | \/ | \ | /| | | Y \/ \ | ||
// \_______ /\____|__ /____/ |__| |___| /\_______ \ | ||
// \/ \/ \/ \/ | ||
|
||
// LoginViaOAuth2 queries if login/password is valid against the OAuth2.0 provider, | ||
// and create a local user if success when enabled. | ||
func LoginViaOAuth2(cfg *OAuth2Config, ctx *macaron.Context, sess session.Store) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
oauth2.Auth(cfg.Provider, cfg.ClientID, cfg.ClientSecret, ctx, sess) | ||
} | ||
|
||
// ExternalUserLogin attempts a login using external source types. | ||
func ExternalUserLogin(user *User, login, password string, source *LoginSource, autoRegister bool) (*User, error) { | ||
func ExternalUserLogin(user *User, login, password string, source *LoginSource, autoRegister bool, ctx *macaron.Context, sess session.Store) (*User, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as above... |
||
if !source.IsActived { | ||
return nil, ErrLoginSourceNotActived | ||
} | ||
|
@@ -539,13 +589,16 @@ func ExternalUserLogin(user *User, login, password string, source *LoginSource, | |
return LoginViaSMTP(user, login, password, source.ID, source.Cfg.(*SMTPConfig), autoRegister) | ||
case LoginPAM: | ||
return LoginViaPAM(user, login, password, source.ID, source.Cfg.(*PAMConfig), autoRegister) | ||
case LoginOAuth2: | ||
LoginViaOAuth2(source.Cfg.(*OAuth2Config), ctx, sess) | ||
return nil, nil | ||
} | ||
|
||
return nil, ErrUnsupportedLoginType | ||
} | ||
|
||
// UserSignIn validates user name and password. | ||
func UserSignIn(username, password string) (*User, error) { | ||
func UserSignIn(username, password string, ctx *macaron.Context, sess session.Store) (*User, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above |
||
var user *User | ||
if strings.Contains(username, "@") { | ||
user = &User{Email: strings.ToLower(strings.TrimSpace(username))} | ||
|
@@ -576,17 +629,17 @@ func UserSignIn(username, password string) (*User, error) { | |
return nil, ErrLoginSourceNotExist{user.LoginSource} | ||
} | ||
|
||
return ExternalUserLogin(user, user.LoginName, password, &source, false) | ||
return ExternalUserLogin(user, user.LoginName, password, &source, false, ctx, sess) | ||
} | ||
} | ||
|
||
sources := make([]*LoginSource, 0, 3) | ||
sources := make([]*LoginSource, 0, 5) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will lookup all activated loginSources, so this cannot be determined up front There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You added a single LoginSource but you incremented this number by 2, either there's a bug in current code (short count) or in your patch (long count) ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see comment above, this can be any value because it is fetched from the database and so unpredictable, so what ever you want 😄 |
||
if err = x.UseBool().Find(&sources, &LoginSource{IsActived: true}); err != nil { | ||
return nil, err | ||
} | ||
|
||
for _, source := range sources { | ||
authUser, err := ExternalUserLogin(nil, username, password, source, true) | ||
authUser, err := ExternalUserLogin(nil, username, password, source, true, ctx, sess) | ||
if err == nil { | ||
return authUser, nil | ||
} | ||
|
@@ -596,3 +649,85 @@ func UserSignIn(username, password string) (*User, error) { | |
|
||
return nil, ErrUserNotExist{user.ID, user.Name, 0} | ||
} | ||
|
||
|
||
// OAuth2UserLogin attempts a login using a OAuth2 source type | ||
func OAuth2UserLogin(provider string, ctx *macaron.Context, sess session.Store) (*User, error) { | ||
sources := make([]*LoginSource, 0, 1) | ||
if err := x.UseBool().Find(&sources, &LoginSource{IsActived: true, Type: LoginOAuth2}); err != nil { | ||
return nil, err | ||
} | ||
|
||
for _, source := range sources { | ||
// TODO how to put this in the xorm Find ? | ||
if source.Cfg.(*OAuth2Config).Provider == provider { | ||
LoginViaOAuth2(source.Cfg.(*OAuth2Config), ctx, sess) | ||
return nil, nil | ||
} | ||
} | ||
|
||
return nil, errors.New("No valid provider found") | ||
} | ||
|
||
// OAuth2UserLoginCallback attempts to handle the callback from the OAuth2 provider and if successful | ||
// login the user | ||
func OAuth2UserLoginCallback(ctx *macaron.Context, sess session.Store) (*User, string, error) { | ||
provider := ctx.Params(":provider") | ||
|
||
gothUser, redirectURL, error := oauth2.ProviderCallback(provider, ctx, sess) | ||
|
||
if error != nil { | ||
return nil, redirectURL, error | ||
} | ||
|
||
sources := make([]*LoginSource, 0, 1) | ||
if err := x.UseBool().Find(&sources, &LoginSource{IsActived: true, Type: LoginOAuth2}); err != nil { | ||
return nil, "", err | ||
} | ||
|
||
for _, source := range sources { | ||
// TODO how to put this in the xorm Find ? | ||
if source.Cfg.(*OAuth2Config).Provider == provider { | ||
user := &User{ | ||
LoginName: gothUser.UserID, | ||
LoginType: LoginOAuth2, | ||
LoginSource: sources[0].ID, | ||
} | ||
|
||
hasUser, err := x.Get(user) | ||
if err != nil { | ||
return nil, "", err | ||
} | ||
|
||
if !hasUser { | ||
user = &User{ | ||
LowerName: strings.ToLower(gothUser.NickName), | ||
Name: gothUser.NickName, | ||
Email: gothUser.Email, | ||
LoginType: LoginOAuth2, | ||
LoginSource: sources[0].ID, | ||
LoginName: gothUser.NickName, | ||
IsActive: true, | ||
// TODO should OAuth2 imported emails be private? | ||
KeepEmailPrivate: true, | ||
} | ||
return user, redirectURL, CreateUser(user) | ||
} | ||
|
||
return user, redirectURL, nil | ||
} | ||
} | ||
|
||
return nil, "", errors.New("No valid provider found") | ||
} | ||
|
||
// GetOAuth2Providers returns the map of registered OAuth2 providers | ||
// key is used as technical name (like in the callbackURL) | ||
// value is used to display | ||
func GetOAuth2Providers() map[string]string { | ||
// TODO get this from database or somewhere else? | ||
// Maybe also seperate used and unused providers so we can force the registration of only 1 active provider for each type | ||
return map[string]string{ | ||
"github": "GitHub", | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to verify this...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure how to use this property correct if we are doing a call to the OAuth2 provider, suggestions?