auth
38:1f7b44b130a0 Browse Files
Update profiles and add tests. Add tests for profile storage. Make Memstore implement the profilestore interface. Get the groundwork of profiles laid.
memstore.go profile.go profile_test.go
1.1 --- a/memstore.go Sun Sep 07 19:07:48 2014 -0400 1.2 +++ b/memstore.go Sun Sep 07 19:09:01 2014 -0400 1.3 @@ -18,6 +18,9 @@ 1.4 clients map[string]Client 1.5 profileClientLookup map[string][]uuid.ID 1.6 clientLock sync.RWMutex 1.7 + 1.8 + profiles map[string]Profile 1.9 + profileLock sync.RWMutex 1.10 } 1.11 1.12 func NewMemstore() *Memstore { 1.13 @@ -28,6 +31,7 @@ 1.14 grants: map[string]Grant{}, 1.15 clients: map[string]Client{}, 1.16 profileClientLookup: map[string][]uuid.ID{}, 1.17 + profiles: map[string]Profile{}, 1.18 } 1.19 } 1.20
2.1 --- a/profile.go Sun Sep 07 19:07:48 2014 -0400 2.2 +++ b/profile.go Sun Sep 07 19:09:01 2014 -0400 2.3 @@ -1,18 +1,80 @@ 2.4 package auth 2.5 2.6 import ( 2.7 + "errors" 2.8 "time" 2.9 2.10 "secondbit.org/uuid" 2.11 ) 2.12 2.13 +var ( 2.14 + ErrProfileAlreadyExists = errors.New("profile already exists in ProfileStore") 2.15 + ErrProfileNotFound = errors.New("profile not found in ProfileStore") 2.16 +) 2.17 + 2.18 type Profile struct { 2.19 - ID uuid.ID 2.20 - Name string 2.21 - Passphrase string 2.22 - Email string 2.23 - Created time.Time 2.24 - LastSeen time.Time 2.25 + ID uuid.ID 2.26 + Name string 2.27 + Passphrase string 2.28 + Iterations int64 2.29 + Salt string 2.30 + PassphraseScheme int 2.31 + Compromised bool 2.32 + LockedUntil time.Time 2.33 + PassphraseReset string 2.34 + PassphraseResetCreated time.Time 2.35 + Created time.Time 2.36 + LastSeen time.Time 2.37 +} 2.38 + 2.39 +func (p *Profile) ApplyChange(change ProfileChange) { 2.40 + if change.Name != nil { 2.41 + p.Name = *change.Name 2.42 + } 2.43 + if change.Passphrase != nil { 2.44 + p.Passphrase = *change.Passphrase 2.45 + } 2.46 + if change.Iterations != nil { 2.47 + p.Iterations = *change.Iterations 2.48 + } 2.49 + if change.Salt != nil { 2.50 + p.Salt = *change.Salt 2.51 + } 2.52 + if change.PassphraseScheme != nil { 2.53 + p.PassphraseScheme = *change.PassphraseScheme 2.54 + } 2.55 + if change.Compromised != nil { 2.56 + p.Compromised = *change.Compromised 2.57 + } 2.58 + if change.LockedUntil != nil { 2.59 + p.LockedUntil = *change.LockedUntil 2.60 + } 2.61 + if change.PassphraseReset != nil { 2.62 + p.PassphraseReset = *change.PassphraseReset 2.63 + } 2.64 + if change.PassphraseResetCreated != nil { 2.65 + p.PassphraseResetCreated = *change.PassphraseResetCreated 2.66 + } 2.67 + if change.LastSeen != nil { 2.68 + p.LastSeen = *change.LastSeen 2.69 + } 2.70 +} 2.71 + 2.72 +type ProfileChange struct { 2.73 + Name *string 2.74 + Passphrase *string 2.75 + Iterations *int64 2.76 + Salt *string 2.77 + PassphraseScheme *int 2.78 + Compromised *bool 2.79 + LockedUntil *time.Time 2.80 + PassphraseReset *string 2.81 + PassphraseResetCreated *time.Time 2.82 + LastSeen *time.Time 2.83 +} 2.84 + 2.85 +func (c ProfileChange) Validate() error { 2.86 + return nil 2.87 } 2.88 2.89 type Login struct { 2.90 @@ -25,12 +87,56 @@ 2.91 2.92 type ProfileStore interface { 2.93 GetProfileByID(id uuid.ID) (Profile, error) 2.94 - GetProfileByLogin(loginType, value, passphrase string) (Profile, error) 2.95 - SaveProfile(user Profile) error 2.96 - UpdateProfile(id uuid.ID, name, passphrase, email *string) error 2.97 + GetProfileByLogin(login Login) (Profile, error) 2.98 + SaveProfile(profile Profile) error 2.99 + UpdateProfile(id uuid.ID, change ProfileChange) error 2.100 DeleteProfile(id uuid.ID) error 2.101 +} 2.102 2.103 - SaveLogin(login Login) error 2.104 - DeleteLogin(login Login) error 2.105 - UpdateLogin(id uuid.ID, lastUsed time.Time) error 2.106 +func (m *Memstore) GetProfileByID(id uuid.ID) (Profile, error) { 2.107 + m.profileLock.RLock() 2.108 + defer m.profileLock.RUnlock() 2.109 + p, ok := m.profiles[id.String()] 2.110 + if !ok { 2.111 + return Profile{}, ErrProfileNotFound 2.112 + } 2.113 + return p, nil 2.114 } 2.115 + 2.116 +func (m *Memstore) GetProfileByLogin(login Login) (Profile, error) { 2.117 + return Profile{}, nil 2.118 +} 2.119 + 2.120 +func (m *Memstore) SaveProfile(profile Profile) error { 2.121 + m.profileLock.Lock() 2.122 + defer m.profileLock.Unlock() 2.123 + _, ok := m.profiles[profile.ID.String()] 2.124 + if ok { 2.125 + return ErrProfileAlreadyExists 2.126 + } 2.127 + m.profiles[profile.ID.String()] = profile 2.128 + return nil 2.129 +} 2.130 + 2.131 +func (m *Memstore) UpdateProfile(id uuid.ID, change ProfileChange) error { 2.132 + m.profileLock.Lock() 2.133 + defer m.profileLock.Unlock() 2.134 + p, ok := m.profiles[id.String()] 2.135 + if !ok { 2.136 + return ErrProfileNotFound 2.137 + } 2.138 + p.ApplyChange(change) 2.139 + m.profiles[id.String()] = p 2.140 + return nil 2.141 +} 2.142 + 2.143 +func (m *Memstore) DeleteProfile(id uuid.ID) error { 2.144 + m.profileLock.Lock() 2.145 + defer m.profileLock.Unlock() 2.146 + _, ok := m.profiles[id.String()] 2.147 + if !ok { 2.148 + return ErrProfileNotFound 2.149 + } 2.150 + delete(m.profiles, id.String()) 2.151 + return nil 2.152 +}
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 3.2 +++ b/profile_test.go Sun Sep 07 19:09:01 2014 -0400 3.3 @@ -0,0 +1,223 @@ 3.4 +package auth 3.5 + 3.6 +import ( 3.7 + "fmt" 3.8 + "testing" 3.9 + "time" 3.10 + 3.11 + "secondbit.org/uuid" 3.12 +) 3.13 + 3.14 +const ( 3.15 + profileChangeName = 1 << iota 3.16 + profileChangePassphrase 3.17 + profileChangeIterations 3.18 + profileChangeSalt 3.19 + profileChangePassphraseScheme 3.20 + profileChangeCompromised 3.21 + profileChangeLockedUntil 3.22 + profileChangePassphraseReset 3.23 + profileChangePassphraseResetCreated 3.24 + profileChangeLastSeen 3.25 +) 3.26 + 3.27 +var profileStores = []ProfileStore{NewMemstore()} 3.28 + 3.29 +func compareProfiles(profile1, profile2 Profile) (success bool, field string, val1, val2 interface{}) { 3.30 + if !profile1.ID.Equal(profile2.ID) { 3.31 + return false, "ID", profile1.ID, profile2.ID 3.32 + } 3.33 + if profile1.Name != profile2.Name { 3.34 + return false, "name", profile1.Name, profile2.Name 3.35 + } 3.36 + if profile1.Passphrase != profile2.Passphrase { 3.37 + return false, "passphrase", profile1.Passphrase, profile2.Passphrase 3.38 + } 3.39 + if profile1.Iterations != profile2.Iterations { 3.40 + return false, "iterations", profile1.Iterations, profile2.Iterations 3.41 + } 3.42 + if profile1.Salt != profile2.Salt { 3.43 + return false, "salt", profile1.Salt, profile2.Salt 3.44 + } 3.45 + if profile1.PassphraseScheme != profile2.PassphraseScheme { 3.46 + return false, "passphrase scheme", profile1.PassphraseScheme, profile2.PassphraseScheme 3.47 + } 3.48 + if profile1.Compromised != profile2.Compromised { 3.49 + return false, "compromised", profile1.Compromised, profile2.Compromised 3.50 + } 3.51 + if !profile1.LockedUntil.Equal(profile2.LockedUntil) { 3.52 + return false, "locked until", profile1.LockedUntil, profile2.LockedUntil 3.53 + } 3.54 + if profile1.PassphraseReset != profile2.PassphraseReset { 3.55 + return false, "passphrase reset", profile1.PassphraseReset, profile2.PassphraseReset 3.56 + } 3.57 + if !profile1.PassphraseResetCreated.Equal(profile2.PassphraseResetCreated) { 3.58 + return false, "passphrase reset created", profile1.PassphraseResetCreated, profile2.PassphraseResetCreated 3.59 + } 3.60 + if !profile1.Created.Equal(profile2.Created) { 3.61 + return false, "created", profile1.Created, profile2.Created 3.62 + } 3.63 + if !profile1.LastSeen.Equal(profile2.LastSeen) { 3.64 + return false, "last seen", profile1.LastSeen, profile2.LastSeen 3.65 + } 3.66 + return true, "", nil, nil 3.67 +} 3.68 + 3.69 +func TestProfileStoreSuccess(t *testing.T) { 3.70 + t.Parallel() 3.71 + profile := Profile{ 3.72 + ID: uuid.NewID(), 3.73 + Name: "name", 3.74 + Passphrase: "passphrase", 3.75 + Iterations: 10000, 3.76 + Salt: "salt", 3.77 + PassphraseScheme: 1, 3.78 + Compromised: false, 3.79 + LockedUntil: time.Now().Add(time.Hour), 3.80 + PassphraseReset: "passphrase reset", 3.81 + PassphraseResetCreated: time.Now(), 3.82 + Created: time.Now(), 3.83 + LastSeen: time.Now(), 3.84 + } 3.85 + for _, store := range profileStores { 3.86 + err := store.SaveProfile(profile) 3.87 + if err != nil { 3.88 + t.Errorf("Error saving profile to %T: %s", store, err) 3.89 + } 3.90 + err = store.SaveProfile(profile) 3.91 + if err != ErrProfileAlreadyExists { 3.92 + t.Errorf("Expected ErrProfileAlreadyExists from %T, got %+v", store, err) 3.93 + } 3.94 + retrieved, err := store.GetProfileByID(profile.ID) 3.95 + if err != nil { 3.96 + t.Errorf("Error retrieving profile from %T: %s", store, err) 3.97 + } 3.98 + match, field, expectation, result := compareProfiles(profile, retrieved) 3.99 + if !match { 3.100 + t.Errorf("Expected `%v` in the `%s` field of profile retrieved from %T, got `%v`", expectation, field, store, result) 3.101 + } 3.102 + err = store.DeleteProfile(profile.ID) 3.103 + if err != nil { 3.104 + t.Errorf("Error removing profile from %T: %s", store, err) 3.105 + } 3.106 + retrieved, err = store.GetProfileByID(profile.ID) 3.107 + if err != ErrProfileNotFound { 3.108 + t.Errorf("Expected ErrProfileNotFound from %T, got %+v and %+v", store, retrieved, err) 3.109 + } 3.110 + err = store.DeleteProfile(profile.ID) 3.111 + if err != ErrProfileNotFound { 3.112 + t.Errorf("Expected ErrProfileNotFound from %T, got %+v", store, err) 3.113 + } 3.114 + } 3.115 +} 3.116 + 3.117 +func TestProfileUpdates(t *testing.T) { 3.118 + t.Parallel() 3.119 + variations := 1 << 10 3.120 + profile := Profile{ 3.121 + ID: uuid.NewID(), 3.122 + Name: "name", 3.123 + Passphrase: "passphrase", 3.124 + Iterations: 10000, 3.125 + Salt: "salt", 3.126 + PassphraseScheme: 1, 3.127 + Compromised: false, 3.128 + LockedUntil: time.Now().Add(time.Hour), 3.129 + PassphraseReset: "passphrase reset", 3.130 + PassphraseResetCreated: time.Now(), 3.131 + Created: time.Now(), 3.132 + LastSeen: time.Now(), 3.133 + } 3.134 + for i := 0; i < variations; i++ { 3.135 + var name, passphrase, salt, passphraseReset string 3.136 + var iterations int64 3.137 + var lockedUntil, passphraseResetCreated, lastSeen time.Time 3.138 + var passphraseScheme int 3.139 + var compromised bool 3.140 + 3.141 + change := ProfileChange{} 3.142 + expectation := profile 3.143 + result := profile 3.144 + if i&profileChangeName != 0 { 3.145 + name = fmt.Sprintf("name-%d", i) 3.146 + change.Name = &name 3.147 + expectation.Name = name 3.148 + } 3.149 + if i&profileChangePassphrase != 0 { 3.150 + passphrase = fmt.Sprintf("passphrase-%d", i) 3.151 + change.Passphrase = &passphrase 3.152 + expectation.Passphrase = passphrase 3.153 + } 3.154 + if i&profileChangeIterations != 0 { 3.155 + iterations = int64(i) 3.156 + change.Iterations = &iterations 3.157 + expectation.Iterations = iterations 3.158 + } 3.159 + if i&profileChangeSalt != 0 { 3.160 + salt = fmt.Sprintf("salt-%d", i) 3.161 + change.Salt = &salt 3.162 + expectation.Salt = salt 3.163 + } 3.164 + if i&profileChangePassphraseScheme != 0 { 3.165 + passphraseScheme = i 3.166 + change.PassphraseScheme = &passphraseScheme 3.167 + expectation.PassphraseScheme = passphraseScheme 3.168 + } 3.169 + if i&profileChangeCompromised != 0 { 3.170 + compromised = i%2 != 0 3.171 + change.Compromised = &compromised 3.172 + expectation.Compromised = compromised 3.173 + } 3.174 + if i&profileChangeLockedUntil != 0 { 3.175 + lockedUntil = time.Now() 3.176 + change.LockedUntil = &lockedUntil 3.177 + expectation.LockedUntil = lockedUntil 3.178 + } 3.179 + if i&profileChangePassphraseReset != 0 { 3.180 + passphraseReset = fmt.Sprintf("passphraseReset-%d", i) 3.181 + change.PassphraseReset = &passphraseReset 3.182 + expectation.PassphraseReset = passphraseReset 3.183 + } 3.184 + if i&profileChangePassphraseResetCreated != 0 { 3.185 + passphraseResetCreated = time.Now() 3.186 + change.PassphraseResetCreated = &passphraseResetCreated 3.187 + expectation.PassphraseResetCreated = passphraseResetCreated 3.188 + } 3.189 + if i&profileChangeLastSeen != 0 { 3.190 + lastSeen = time.Now() 3.191 + change.LastSeen = &lastSeen 3.192 + expectation.LastSeen = lastSeen 3.193 + } 3.194 + result.ApplyChange(change) 3.195 + match, field, expected, got := compareProfiles(expectation, result) 3.196 + if !match { 3.197 + t.Errorf("Expected field `%s` to be `%v`, got `%v`", field, expected, got) 3.198 + } 3.199 + for _, store := range profileStores { 3.200 + err := store.SaveProfile(profile) 3.201 + if err != nil { 3.202 + t.Errorf("Error saving profile in %T: %s", store, err) 3.203 + } 3.204 + err = store.UpdateProfile(profile.ID, change) 3.205 + if err != nil { 3.206 + t.Errorf("Error updating profile in %T: %s", store, err) 3.207 + } 3.208 + retrieved, err := store.GetProfileByID(profile.ID) 3.209 + if err != nil { 3.210 + t.Errorf("Error getting profile from %T: %s", store, err) 3.211 + } 3.212 + match, field, expected, got = compareProfiles(expectation, retrieved) 3.213 + if !match { 3.214 + t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store) 3.215 + } 3.216 + err = store.DeleteProfile(profile.ID) 3.217 + if err != nil { 3.218 + t.Errorf("Error deleting profile from %T: %s", store, err) 3.219 + } 3.220 + err = store.UpdateProfile(profile.ID, change) 3.221 + if err != ErrProfileNotFound { 3.222 + t.Errorf("Expected ErrProfileNotFound, got %v from %T", err, store) 3.223 + } 3.224 + } 3.225 + } 3.226 +}