|
|
|
|
@ -4,13 +4,10 @@ import (
|
|
|
|
|
"encoding/json" |
|
|
|
|
"errors" |
|
|
|
|
"fmt" |
|
|
|
|
"io" |
|
|
|
|
"time" |
|
|
|
|
|
|
|
|
|
"net/mail" |
|
|
|
|
"net/url" |
|
|
|
|
"os" |
|
|
|
|
"sort" |
|
|
|
|
|
|
|
|
|
"github.com/jonboulle/clockwork" |
|
|
|
|
"github.com/pborman/uuid" |
|
|
|
|
@ -172,262 +169,11 @@ func ValidPassword(plaintext string) bool {
|
|
|
|
|
return len(plaintext) > 5 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// NewUserRepo returns an in-memory UserRepo useful for development.
|
|
|
|
|
func NewUserRepo() UserRepo { |
|
|
|
|
return &memUserRepo{ |
|
|
|
|
usersByID: make(map[string]User), |
|
|
|
|
userIDsByEmail: make(map[string]string), |
|
|
|
|
userIDsByRemoteID: make(map[RemoteIdentity]string), |
|
|
|
|
remoteIDsByUserID: make(map[string]map[RemoteIdentity]struct{}), |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type memUserRepo struct { |
|
|
|
|
usersByID map[string]User |
|
|
|
|
userIDsByEmail map[string]string |
|
|
|
|
userIDsByRemoteID map[RemoteIdentity]string |
|
|
|
|
remoteIDsByUserID map[string]map[RemoteIdentity]struct{} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (r *memUserRepo) Get(_ repo.Transaction, id string) (User, error) { |
|
|
|
|
user, ok := r.usersByID[id] |
|
|
|
|
if !ok { |
|
|
|
|
return User{}, ErrorNotFound |
|
|
|
|
} |
|
|
|
|
return user, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type usersByEmail []User |
|
|
|
|
|
|
|
|
|
func (s usersByEmail) Len() int { return len(s) } |
|
|
|
|
func (s usersByEmail) Swap(i, j int) { s[i], s[j] = s[j], s[i] } |
|
|
|
|
func (s usersByEmail) Less(i, j int) bool { return s[i].Email < s[j].Email } |
|
|
|
|
|
|
|
|
|
func (r *memUserRepo) List(tx repo.Transaction, filter UserFilter, maxResults int, nextPageToken string) ([]User, string, error) { |
|
|
|
|
var offset int |
|
|
|
|
var err error |
|
|
|
|
if nextPageToken != "" { |
|
|
|
|
filter, maxResults, offset, err = DecodeNextPageToken(nextPageToken) |
|
|
|
|
} |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, "", err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
users := []User{} |
|
|
|
|
for _, usr := range r.usersByID { |
|
|
|
|
users = append(users, usr) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
sort.Sort(usersByEmail(users)) |
|
|
|
|
|
|
|
|
|
high := offset + maxResults |
|
|
|
|
|
|
|
|
|
var tok string |
|
|
|
|
if high >= len(users) { |
|
|
|
|
high = len(users) |
|
|
|
|
} else { |
|
|
|
|
tok, err = EncodeNextPageToken(filter, maxResults, high) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
return nil, "", err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if len(users[offset:high]) == 0 { |
|
|
|
|
return nil, "", ErrorNotFound |
|
|
|
|
} |
|
|
|
|
return users[offset:high], tok, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (r *memUserRepo) GetByEmail(tx repo.Transaction, email string) (User, error) { |
|
|
|
|
userID, ok := r.userIDsByEmail[email] |
|
|
|
|
if !ok { |
|
|
|
|
return User{}, ErrorNotFound |
|
|
|
|
} |
|
|
|
|
return r.Get(tx, userID) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (r *memUserRepo) Create(_ repo.Transaction, user User) error { |
|
|
|
|
if user.ID == "" { |
|
|
|
|
return ErrorInvalidID |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if !ValidEmail(user.Email) { |
|
|
|
|
return ErrorInvalidEmail |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// make sure no one has the same ID; if using UUID the chances of this
|
|
|
|
|
// happening are astronomically small.
|
|
|
|
|
_, ok := r.usersByID[user.ID] |
|
|
|
|
if ok { |
|
|
|
|
return ErrorDuplicateID |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// make sure there's no other user with the same Email
|
|
|
|
|
_, ok = r.userIDsByEmail[user.Email] |
|
|
|
|
if ok { |
|
|
|
|
return ErrorDuplicateEmail |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
r.set(user) |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (r *memUserRepo) Update(_ repo.Transaction, user User) error { |
|
|
|
|
if user.ID == "" { |
|
|
|
|
return ErrorInvalidID |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if !ValidEmail(user.Email) { |
|
|
|
|
return ErrorInvalidEmail |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// make sure this user exists already
|
|
|
|
|
_, ok := r.usersByID[user.ID] |
|
|
|
|
if !ok { |
|
|
|
|
return ErrorNotFound |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// make sure there's no other user with the same Email
|
|
|
|
|
otherID, ok := r.userIDsByEmail[user.Email] |
|
|
|
|
if ok && otherID != user.ID { |
|
|
|
|
return ErrorDuplicateEmail |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
r.set(user) |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (r *memUserRepo) Disable(_ repo.Transaction, id string, disable bool) error { |
|
|
|
|
if id == "" { |
|
|
|
|
return ErrorInvalidID |
|
|
|
|
} |
|
|
|
|
user, ok := r.usersByID[id] |
|
|
|
|
if !ok { |
|
|
|
|
return ErrorNotFound |
|
|
|
|
} |
|
|
|
|
user.Disabled = disable |
|
|
|
|
r.set(user) |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (r *memUserRepo) AddRemoteIdentity(_ repo.Transaction, userID string, ri RemoteIdentity) error { |
|
|
|
|
_, ok := r.usersByID[userID] |
|
|
|
|
if !ok { |
|
|
|
|
return ErrorNotFound |
|
|
|
|
} |
|
|
|
|
_, ok = r.userIDsByRemoteID[ri] |
|
|
|
|
if ok { |
|
|
|
|
return ErrorDuplicateRemoteIdentity |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
r.userIDsByRemoteID[ri] = userID |
|
|
|
|
rIDs, ok := r.remoteIDsByUserID[userID] |
|
|
|
|
if !ok { |
|
|
|
|
rIDs = make(map[RemoteIdentity]struct{}) |
|
|
|
|
r.remoteIDsByUserID[userID] = rIDs |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
rIDs[ri] = struct{}{} |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (r *memUserRepo) RemoveRemoteIdentity(_ repo.Transaction, userID string, ri RemoteIdentity) error { |
|
|
|
|
otherID, ok := r.userIDsByRemoteID[ri] |
|
|
|
|
if !ok { |
|
|
|
|
return ErrorNotFound |
|
|
|
|
} |
|
|
|
|
if otherID != userID { |
|
|
|
|
return ErrorNotFound |
|
|
|
|
} |
|
|
|
|
delete(r.userIDsByRemoteID, ri) |
|
|
|
|
delete(r.remoteIDsByUserID[userID], ri) |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (r *memUserRepo) GetByRemoteIdentity(_ repo.Transaction, ri RemoteIdentity) (User, error) { |
|
|
|
|
userID, ok := r.userIDsByRemoteID[ri] |
|
|
|
|
if !ok { |
|
|
|
|
return User{}, ErrorNotFound |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
user, ok := r.usersByID[userID] |
|
|
|
|
if !ok { |
|
|
|
|
return User{}, ErrorNotFound |
|
|
|
|
} |
|
|
|
|
return user, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (r *memUserRepo) GetRemoteIdentities(_ repo.Transaction, userID string) ([]RemoteIdentity, error) { |
|
|
|
|
ids := []RemoteIdentity{} |
|
|
|
|
for id := range r.remoteIDsByUserID[userID] { |
|
|
|
|
ids = append(ids, id) |
|
|
|
|
} |
|
|
|
|
return ids, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (r *memUserRepo) GetAdminCount(_ repo.Transaction) (int, error) { |
|
|
|
|
var i int |
|
|
|
|
for _, usr := range r.usersByID { |
|
|
|
|
if usr.Admin { |
|
|
|
|
i++ |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return i, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (r *memUserRepo) set(user User) error { |
|
|
|
|
r.usersByID[user.ID] = user |
|
|
|
|
r.userIDsByEmail[user.Email] = user.ID |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type UserWithRemoteIdentities struct { |
|
|
|
|
User User `json:"user"` |
|
|
|
|
RemoteIdentities []RemoteIdentity `json:"remoteIdentities"` |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// NewUserRepoFromFile returns an in-memory UserRepo useful for development given a JSON serialized file of Users.
|
|
|
|
|
func NewUserRepoFromFile(loc string) (UserRepo, error) { |
|
|
|
|
us, err := readUsersFromFile(loc) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
return NewUserRepoFromUsers(us), nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func NewUserRepoFromUsers(us []UserWithRemoteIdentities) UserRepo { |
|
|
|
|
memUserRepo := NewUserRepo().(*memUserRepo) |
|
|
|
|
for _, u := range us { |
|
|
|
|
memUserRepo.set(u.User) |
|
|
|
|
for _, ri := range u.RemoteIdentities { |
|
|
|
|
memUserRepo.AddRemoteIdentity(nil, u.User.ID, ri) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return memUserRepo |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func newUsersFromReader(r io.Reader) ([]UserWithRemoteIdentities, error) { |
|
|
|
|
var us []UserWithRemoteIdentities |
|
|
|
|
err := json.NewDecoder(r).Decode(&us) |
|
|
|
|
return us, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func readUsersFromFile(loc string) ([]UserWithRemoteIdentities, error) { |
|
|
|
|
uf, err := os.Open(loc) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, fmt.Errorf("unable to read users from file %q: %v", loc, err) |
|
|
|
|
} |
|
|
|
|
defer uf.Close() |
|
|
|
|
|
|
|
|
|
us, err := newUsersFromReader(uf) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return us, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (u *User) UnmarshalJSON(data []byte) error { |
|
|
|
|
var dec struct { |
|
|
|
|
ID string `json:"id"` |
|
|
|
|
|