-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
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
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. | ||
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. Why separated on space? 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. Spaces are guaranteed not to be valid characters in URIs, while commas could be, making it harder to encode in the patterns |
||
; Only these would be allowed if non-blank. | ||
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. Maybe give an example of blocking a domain? 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. Done with b7237b4 (more specific than just a domain) |
||
; 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) | ||
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 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. AddUserOpenIDs has gone, check addUserOpenID, which does the same normalization (good question though why I don't use Normalize there...) 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. I'm debugging what seems to be an error due to this, stay tuned (registering a user by logging in as "https://launchpad.net/~strk" and then logging in via "login.launchpad.net" resulting in a mismatch, despite the verification step gives the same resulting ID uri) 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. Confirmed, I'm registering lowercased "https://login.launchpad.net/+id/lcm3qwk" while Normalize keps case and thus gets me "https://login.launchpad.net/+id/lcm3qwk". The OpenID specs don't say anything about case so maybe we should keep it. 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. Fixed with 2ba2d6a7303f1e1cbc26dbc2f5aad938f4afa37e (the addUser function assumes incoming item is pre-normalized as it returns from a verification step) |
||
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