From cb2ce01e16bdff38641c31e327a7f671a23ea7f0 Mon Sep 17 00:00:00 2001 From: Sandro Santilli Date: Sun, 3 Jul 2016 14:18:33 +0200 Subject: [PATCH] Stub openid support Handle OpenID redirection Implement the verfication routes ... still all to be architectured --- cmd/web.go | 1 + models/error.go | 16 +++++++ models/login_source.go | 88 +++++++++++++++++++++++++++++++++-- models/user.go | 1 + modules/auth/auth_form.go | 2 +- modules/auth/openid/openid.go | 48 +++++++++++++++++++ routers/admin/auths.go | 5 ++ routers/user/auth.go | 15 ++++++ templates/admin/auth/new.tmpl | 4 ++ 9 files changed, 174 insertions(+), 6 deletions(-) create mode 100644 modules/auth/openid/openid.go diff --git a/cmd/web.go b/cmd/web.go index 9776fdc11a1dc..12c60424d06ef 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -186,6 +186,7 @@ func runWeb(ctx *cli.Context) error { m.Group("/user", func() { m.Get("/login", user.SignIn) m.Post("/login", bindIgnErr(auth.SignInForm{}), user.SignInPost) + m.Get("/login/openid/verify", user.OpenIDVerify) m.Get("/sign_up", user.SignUp) m.Post("/sign_up", bindIgnErr(auth.RegisterForm{}), user.SignUpPost) m.Get("/reset_password", user.ResetPasswd) diff --git a/models/error.go b/models/error.go index d11a9eeb1feb3..c7f286d3b196a 100644 --- a/models/error.go +++ b/models/error.go @@ -78,6 +78,22 @@ func (err ErrUserNotExist) Error() string { return fmt.Sprintf("user does not exist [uid: %d, name: %s, keyid: %d]", err.UID, err.Name, err.KeyID) } +// ErrDelegatedAuth is not a real error but notifies code that +// authentication is delegated to a second request. +type ErrDelegatedAuth struct { + OP string // OpenID Provider +} + +// IsErrDelegatedAuth checks if an error is a ErrDelegatedAuth. +func IsErrDelegatedAuth(err error) bool { + _, ok := err.(ErrDelegatedAuth) + return ok +} + +func (err ErrDelegatedAuth) Error() string { + return err.OP +} + // ErrEmailAlreadyUsed represents a "EmailAlreadyUsed" kind of error. type ErrEmailAlreadyUsed struct { Email string diff --git a/models/login_source.go b/models/login_source.go index 58e0e88b3ea01..c2430a235b696 100644 --- a/models/login_source.go +++ b/models/login_source.go @@ -18,10 +18,13 @@ import ( "github.com/go-macaron/binding" "github.com/go-xorm/core" "github.com/go-xorm/xorm" + "github.com/yohcop/openid-go" + //"github.com/akavel/go-openid" "code.gitea.io/gitea/modules/auth/ldap" "code.gitea.io/gitea/modules/auth/pam" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" ) // LoginType represents an login type. @@ -35,14 +38,16 @@ const ( LoginSMTP // 3 LoginPAM // 4 LoginDLDAP // 5 + LoginOpenID // 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", + LoginOpenID: "OpenID", } // SecurityProtocolNames contains the name of SecurityProtocol values. @@ -57,6 +62,7 @@ var ( _ core.Conversion = &LDAPConfig{} _ core.Conversion = &SMTPConfig{} _ core.Conversion = &PAMConfig{} + _ core.Conversion = &OpenIDConfig{} ) // LDAPConfig holds configuration for LDAP login source. @@ -80,6 +86,21 @@ func (cfg *LDAPConfig) SecurityProtocolName() string { return SecurityProtocolNames[cfg.SecurityProtocol] } +// OpenIDConfig holds an OpenID login source configuration. +type OpenIDConfig struct { + //*openid.Source +} + +// FromDB fills up an OpenIDConfig from serialized format. +func (cfg *OpenIDConfig) FromDB(bs []byte) error { + return json.Unmarshal(bs, &cfg) +} + +// ToDB exports an OpenIDConfig to a serialized format. +func (cfg *OpenIDConfig) ToDB() ([]byte, error) { + return json.Marshal(cfg) +} + // SMTPConfig holds configuration for the SMTP login source. type SMTPConfig struct { Auth string @@ -162,6 +183,8 @@ func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) { source.Cfg = new(SMTPConfig) case LoginPAM: source.Cfg = new(PAMConfig) + case LoginOpenID: + source.Cfg = new(OpenIDConfig) default: panic("unrecognized login source type: " + com.ToStr(*val)) } @@ -526,6 +549,54 @@ func LoginViaPAM(user *User, login, password string, sourceID int64, cfg *PAMCon return user, CreateUser(user) } +// ________ .___________ +// \_____ \ ______ ____ ____ | \______ \ +// / | \\____ \_/ __ \ / \| || | \ +// / | \ |_> > ___/| | \ || ` \ +// \_______ / __/ \___ >___| /___/_______ / +// \/|__| \/ \/ \/ + +// LoginViaOpenID authorizes against "id" (openid URL) +// and create a local user if success when enabled. +func LoginViaOpenID(user *User, id string, source *LoginSource, autoRegister bool) (*User, error) { + + url, err := openid.RedirectURL(id, setting.AppURL+"user/login/openid/verify", setting.AppURL) + if err != nil { + return nil, err + } + return nil, ErrDelegatedAuth{OP: url} +} + +var nonceStore = openid.NewSimpleNonceStore() +var discoveryCache = openid.NewSimpleDiscoveryCache() + +// LoginViaOpenIDVerification verifies a given OpenID url. +func LoginViaOpenIDVerification(url string, autoRegister bool) (*User, error) { + + var id, err = openid.Verify(url, discoveryCache, nonceStore) + if err != nil { + log.Fatal(1, "Error verifying: %v", err) + } + log.Trace("Verified ID: " + id) + + /* + login := id + + user = &User{ + LowerName: strings.ToLower(login), + Name: login, + Email: login, + Passwd: nil, + LoginType: LoginOpenID, + LoginSource: sourceID, + LoginName: login, + IsActive: true, + } + return user, CreateUser(user) + */ + return nil, nil +} + // ExternalUserLogin attempts a login using external source types. func ExternalUserLogin(user *User, login, password string, source *LoginSource, autoRegister bool) (*User, error) { if !source.IsActived { @@ -539,6 +610,8 @@ 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 LoginOpenID: + return LoginViaOpenID(user, login, source, autoRegister) } return nil, ErrUnsupportedLoginType @@ -549,6 +622,8 @@ func UserSignIn(username, password string) (*User, error) { var user *User if strings.Contains(username, "@") { user = &User{Email: strings.ToLower(strings.TrimSpace(username))} + } else if strings.Contains(username, "://") { + user = &User{OpenID: strings.ToLower(username)} } else { user = &User{LowerName: strings.ToLower(strings.TrimSpace(username))} } @@ -580,7 +655,7 @@ func UserSignIn(username, password string) (*User, error) { } } - sources := make([]*LoginSource, 0, 3) + sources := make([]*LoginSource, 0, 4) if err = x.UseBool().Find(&sources, &LoginSource{IsActived: true}); err != nil { return nil, err } @@ -590,6 +665,9 @@ func UserSignIn(username, password string) (*User, error) { if err == nil { return authUser, nil } + if IsErrDelegatedAuth(err) { + return nil, err + } log.Warn("Failed to login '%s' via '%s': %v", username, source.Name, err) } diff --git a/models/user.go b/models/user.go index 803cb3b03e2d1..97c18f5ec4564 100644 --- a/models/user.go +++ b/models/user.go @@ -86,6 +86,7 @@ type User struct { Repos []*Repository `xorm:"-"` Location string Website string + OpenID string Rands string `xorm:"VARCHAR(10)"` Salt string `xorm:"VARCHAR(10)"` diff --git a/modules/auth/auth_form.go b/modules/auth/auth_form.go index 78ba2b899788d..5b7c0b9f4b7f6 100644 --- a/modules/auth/auth_form.go +++ b/modules/auth/auth_form.go @@ -12,7 +12,7 @@ import ( // AuthenticationForm form for authentication type AuthenticationForm struct { ID int64 - Type int `binding:"Range(2,5)"` + Type int `binding:"Range(2,6)"` Name string `binding:"Required;MaxSize(30)"` Host string Port int diff --git a/modules/auth/openid/openid.go b/modules/auth/openid/openid.go new file mode 100644 index 0000000000000..3105c05639b11 --- /dev/null +++ b/modules/auth/openid/openid.go @@ -0,0 +1,48 @@ +// Copyright 2016 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 openid provide functions & structure to authenticate users +// via OpenID +package openid + +import ( + "github.com/yohcop/openid-go" +) + +// For the demo, we use in-memory infinite storage nonce and discovery +// cache. In your app, do not use this as it will eat up memory and +// never +// free it. Use your own implementation, on a better database system. +// If you have multiple servers for example, you may need to share at +// least +// the nonceStore between them. +var nonceStore = openid.NewSimpleNonceStore() +var discoveryCache = openid.NewSimpleDiscoveryCache() + +// LoginViaOpenIDVerification verifies an OpenID URL claim +func LoginViaOpenIDVerification(url string, autoRegister bool) (*User, error) { + + var id, err = openid.Verify(url, discoveryCache, nonceStore) + if err != nil { + log.Fatal(1, "Error verifying: %v", err) + } + log.Trace("Verified ID: " + id) + + /* + login := id + + user = &User{ + LowerName: strings.ToLower(login), + Name: login, + Email: login, + Passwd: nil, + LoginType: LoginOpenID, + LoginSource: sourceID, + LoginName: login, + IsActive: true, + } + return user, CreateUser(user) + */ + return nil, nil +} diff --git a/routers/admin/auths.go b/routers/admin/auths.go index d437dc4b80e6e..624b2312dc5f3 100644 --- a/routers/admin/auths.go +++ b/routers/admin/auths.go @@ -53,6 +53,7 @@ var ( {models.LoginNames[models.LoginDLDAP], models.LoginDLDAP}, {models.LoginNames[models.LoginSMTP], models.LoginSMTP}, {models.LoginNames[models.LoginPAM], models.LoginPAM}, + {models.LoginNames[models.LoginOpenID], models.LoginOpenID}, } securityProtocols = []dropdownItem{ {models.SecurityProtocolNames[ldap.SecurityProtocolUnencrypted], ldap.SecurityProtocolUnencrypted}, @@ -138,6 +139,10 @@ func NewAuthSourcePost(ctx *context.Context, form auth.AuthenticationForm) { config = &models.PAMConfig{ ServiceName: form.PAMServiceName, } + case models.LoginOpenID: + config = &models.OpenIDConfig{ + //ServiceName: form.PAMServiceName, + } default: ctx.Error(400) return diff --git a/routers/user/auth.go b/routers/user/auth.go index ef2a04005b64c..09dce3cbb748c 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -100,6 +100,19 @@ func SignIn(ctx *context.Context) { ctx.HTML(200, tplSignIn) } +// OpenIDVerify handles response from OpenID provider +func OpenIDVerify(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("sign_in") + + log.Trace("Incoming call to: " + ctx.Req.Request.URL.String()) + + fullURL := setting.AppURL + ctx.Req.Request.URL.String()[1:] + log.Trace(fullURL) + models.LoginViaOpenIDVerification(fullURL, true) + + ctx.HTML(200, tplSignIn) +} + // SignInPost response for sign in request func SignInPost(ctx *context.Context, form auth.SignInForm) { ctx.Data["Title"] = ctx.Tr("sign_in") @@ -113,6 +126,8 @@ func SignInPost(ctx *context.Context, form auth.SignInForm) { if err != nil { if models.IsErrUserNotExist(err) { ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplSignIn, &form) + } else if models.IsErrDelegatedAuth(err) { + ctx.Redirect(err.Error()) } else { ctx.Handle(500, "UserSignIn", err) } diff --git a/templates/admin/auth/new.tmpl b/templates/admin/auth/new.tmpl index 1edec0cb341cb..c8cd0e7c1c5a4 100644 --- a/templates/admin/auth/new.tmpl +++ b/templates/admin/auth/new.tmpl @@ -133,6 +133,10 @@ + +
+
+