-
-
Notifications
You must be signed in to change notification settings - Fork 6.2k
Login via OpenID-2.0 #618
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
Login via OpenID-2.0 #618
Changes from all commits
3f28e16
f2f30fe
a40bc35
7bf7213
0c8a0ef
34e054f
fb26d58
0af2fdc
2469417
9fb42f7
0c036e6
327ad46
9d7dc21
93adb13
8fedc20
8c2363d
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 |
|---|---|---|
|
|
@@ -182,6 +182,38 @@ MIN_PASSWORD_LENGTH = 6 | |
| ; True when users are allowed to import local server paths | ||
| IMPORT_LOCAL_PATHS = false | ||
|
|
||
| [openid] | ||
| ; | ||
| ; OpenID is an open standard and decentralized authentication protocol. | ||
| ; Your identity is the address of a webpage you provide, which describes | ||
| ; how to prove you are in control of that page. | ||
| ; | ||
| ; For more info: https://en.wikipedia.org/wiki/OpenID | ||
| ; | ||
| ; Current implementation supports OpenID-2.0 | ||
| ; | ||
| ; Tested to work providers at the time of writing: | ||
| ; - Any GNUSocial node (your.hostname.tld/username) | ||
| ; - Any SimpleID provider (http://simpleid.koinic.net) | ||
| ; - http://openid.org.cn/ | ||
| ; - openid.stackexchange.com | ||
| ; - login.launchpad.net | ||
| ; | ||
| ; Whether to allow signin in via OpenID | ||
| ENABLE_OPENID_SIGNIN = true | ||
| ; Whether to allow registering via OpenID | ||
| ENABLE_OPENID_SIGNUP = true | ||
| ; Allowed URI patterns (POSIX regexp). | ||
| ; Space separated. | ||
|
||
| ; Only these would be allowed if non-blank. | ||
|
||
| ; Example value: trusted.domain.org trusted.domain.net | ||
| WHITELISTED_URIS = | ||
| ; Forbidden URI patterns (POSIX regexp). | ||
| ; Space sepaated. | ||
| ; Only used if WHITELISTED_URIS is blank. | ||
| ; Example value: loadaverage.org/badguy stackexchange.com/.*spammer | ||
| BLACKLISTED_URIS = | ||
|
|
||
| [service] | ||
| ACTIVE_CODE_LIVE_MINUTES = 180 | ||
| RESET_PASSWD_CODE_LIVE_MINUTES = 180 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| // Copyright 2017 Gitea. All rights reserved. | ||
| // Use of this source code is governed by a MIT-style | ||
| // license that can be found in the LICENSE file. | ||
|
|
||
| package migrations | ||
|
|
||
| import ( | ||
| "fmt" | ||
|
|
||
| "github.com/go-xorm/xorm" | ||
| ) | ||
|
|
||
| // UserOpenID is the list of all OpenID identities of a user. | ||
| type UserOpenID struct { | ||
| ID int64 `xorm:"pk autoincr"` | ||
| UID int64 `xorm:"INDEX NOT NULL"` | ||
| URI string `xorm:"UNIQUE NOT NULL"` | ||
| } | ||
|
|
||
|
|
||
| func addUserOpenID(x *xorm.Engine) error { | ||
| if err := x.Sync2(new(UserOpenID)); err != nil { | ||
| return fmt.Errorf("Sync2: %v", err) | ||
| } | ||
| return nil | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| // 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 ( | ||
| "errors" | ||
|
|
||
| "code.gitea.io/gitea/modules/auth/openid" | ||
| "code.gitea.io/gitea/modules/log" | ||
| ) | ||
|
|
||
| var ( | ||
| // ErrOpenIDNotExist openid is not known | ||
| ErrOpenIDNotExist = errors.New("OpenID is unknown") | ||
| ) | ||
|
|
||
| // UserOpenID is the list of all OpenID identities of a user. | ||
| type UserOpenID struct { | ||
| ID int64 `xorm:"pk autoincr"` | ||
| UID int64 `xorm:"INDEX NOT NULL"` | ||
| URI string `xorm:"UNIQUE NOT NULL"` | ||
| } | ||
|
|
||
| // GetUserOpenIDs returns all openid addresses that belongs to given user. | ||
| func GetUserOpenIDs(uid int64) ([]*UserOpenID, error) { | ||
| openids := make([]*UserOpenID, 0, 5) | ||
| if err := x. | ||
| Where("uid=?", uid). | ||
| Find(&openids); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return openids, nil | ||
| } | ||
|
|
||
| func isOpenIDUsed(e Engine, uri string) (bool, error) { | ||
| if len(uri) == 0 { | ||
| return true, nil | ||
| } | ||
|
|
||
| return e.Get(&UserOpenID{URI: uri}) | ||
| } | ||
|
|
||
| // IsOpenIDUsed returns true if the openid has been used. | ||
| func IsOpenIDUsed(openid string) (bool, error) { | ||
| return isOpenIDUsed(x, openid) | ||
| } | ||
|
|
||
| // NOTE: make sure openid.URI is normalized already | ||
| func addUserOpenID(e Engine, openid *UserOpenID) error { | ||
| used, err := isOpenIDUsed(e, openid.URI) | ||
| if err != nil { | ||
| return err | ||
| } else if used { | ||
| return ErrOpenIDAlreadyUsed{openid.URI} | ||
| } | ||
|
|
||
| _, err = e.Insert(openid) | ||
| return err | ||
| } | ||
|
|
||
| // AddUserOpenID adds an pre-verified/normalized OpenID URI to given user. | ||
| func AddUserOpenID(openid *UserOpenID) error { | ||
| return addUserOpenID(x, openid) | ||
| } | ||
|
|
||
| // DeleteUserOpenID deletes an openid address of given user. | ||
| func DeleteUserOpenID(openid *UserOpenID) (err error) { | ||
| var deleted int64 | ||
| // ask to check UID | ||
| var address = UserOpenID{ | ||
| UID: openid.UID, | ||
| } | ||
| if openid.ID > 0 { | ||
| deleted, err = x.Id(openid.ID).Delete(&address) | ||
| } else { | ||
| deleted, err = x. | ||
| Where("openid=?", openid.URI). | ||
| Delete(&address) | ||
| } | ||
|
|
||
| if err != nil { | ||
| return err | ||
| } else if deleted != 1 { | ||
| return ErrOpenIDNotExist | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // GetUserByOpenID returns the user object by given OpenID if exists. | ||
| func GetUserByOpenID(uri string) (*User, error) { | ||
| if len(uri) == 0 { | ||
| return nil, ErrUserNotExist{0, uri, 0} | ||
| } | ||
|
|
||
| uri, err := openid.Normalize(uri) | ||
|
||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| log.Trace("Normalized OpenID URI: " + uri) | ||
|
|
||
| // Otherwise, check in openid table | ||
| oid := &UserOpenID{URI: uri} | ||
| has, err := x.Get(oid) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| if has { | ||
| return GetUserByID(oid.UID) | ||
| } | ||
|
|
||
| return nil, ErrUserNotExist{0, uri, 0} | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| // 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 openid | ||
|
|
||
| import ( | ||
| "sync" | ||
| "time" | ||
|
|
||
| "github.com/yohcop/openid-go" | ||
| ) | ||
|
|
||
| type timedDiscoveredInfo struct { | ||
| info openid.DiscoveredInfo | ||
| time time.Time | ||
| } | ||
|
|
||
| type timedDiscoveryCache struct { | ||
| cache map[string]timedDiscoveredInfo | ||
| ttl time.Duration | ||
| mutex *sync.Mutex | ||
| } | ||
|
|
||
| func newTimedDiscoveryCache(ttl time.Duration) *timedDiscoveryCache { | ||
| return &timedDiscoveryCache{cache: map[string]timedDiscoveredInfo{}, ttl: ttl, mutex: &sync.Mutex{}} | ||
| } | ||
|
|
||
| func (s *timedDiscoveryCache) Put(id string, info openid.DiscoveredInfo) { | ||
| s.mutex.Lock() | ||
| defer s.mutex.Unlock() | ||
|
|
||
| s.cache[id] = timedDiscoveredInfo{info: info, time: time.Now()} | ||
| } | ||
|
|
||
| // Delete timed-out cache entries | ||
| func (s *timedDiscoveryCache) cleanTimedOut() { | ||
| now := time.Now() | ||
| for k, e := range s.cache { | ||
| diff := now.Sub(e.time) | ||
| if diff > s.ttl { | ||
| delete(s.cache, k) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func (s *timedDiscoveryCache) Get(id string) openid.DiscoveredInfo { | ||
| s.mutex.Lock() | ||
| defer s.mutex.Unlock() | ||
|
|
||
| // Delete old cached while we are at it. | ||
| s.cleanTimedOut() | ||
|
|
||
| if info, has := s.cache[id]; has { | ||
| return info.info | ||
| } | ||
| return nil | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| // 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 openid | ||
|
|
||
| import ( | ||
| "testing" | ||
| "time" | ||
| ) | ||
|
|
||
| type testDiscoveredInfo struct {} | ||
| func (s *testDiscoveredInfo) ClaimedID() string { | ||
| return "claimedID" | ||
| } | ||
| func (s *testDiscoveredInfo) OpEndpoint() string { | ||
| return "opEndpoint" | ||
| } | ||
| func (s *testDiscoveredInfo) OpLocalID() string { | ||
| return "opLocalID" | ||
| } | ||
|
|
||
| func TestTimedDiscoveryCache(t *testing.T) { | ||
| dc := newTimedDiscoveryCache(1*time.Second) | ||
|
|
||
| // Put some initial values | ||
| dc.Put("foo", &testDiscoveredInfo{}) //openid.opEndpoint: "a", openid.opLocalID: "b", openid.claimedID: "c"}) | ||
|
|
||
| // Make sure we can retrieve them | ||
| if di := dc.Get("foo"); di == nil { | ||
| t.Errorf("Expected a result, got nil") | ||
| } else if di.OpEndpoint() != "opEndpoint" || di.OpLocalID() != "opLocalID" || di.ClaimedID() != "claimedID" { | ||
| t.Errorf("Expected opEndpoint opLocalID claimedID, got %v %v %v", di.OpEndpoint(), di.OpLocalID(), di.ClaimedID()) | ||
| } | ||
|
|
||
| // Attempt to get a non-existent value | ||
| if di := dc.Get("bar"); di != nil { | ||
| t.Errorf("Expected nil, got %v", di) | ||
| } | ||
|
|
||
| // Sleep one second and try retrive again | ||
| time.Sleep(1 * time.Second) | ||
|
|
||
| if di := dc.Get("foo"); di != nil { | ||
| t.Errorf("Expected a nil, got a result") | ||
| } | ||
| } |
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.
After this line, add:
And remove the Group below.
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.
Those are under
/user/settings, not just/user