auth

Paddy 2014-09-27 Parent:3a6a65ed380c Child:d78418fb9f56

46:c6ace3d27c6f Go to Latest

auth/profile.go

Add simple tests for logins. Test for login adding, removal, listing, and updating, to ensure they work as intended across all our ProfileStores.

History
paddy@27 1 package auth
paddy@27 2
paddy@27 3 import (
paddy@38 4 "errors"
paddy@27 5 "time"
paddy@27 6
paddy@45 7 "code.secondbit.org/uuid"
paddy@27 8 )
paddy@27 9
paddy@38 10 var (
paddy@38 11 ErrProfileAlreadyExists = errors.New("profile already exists in ProfileStore")
paddy@38 12 ErrProfileNotFound = errors.New("profile not found in ProfileStore")
paddy@44 13 ErrLoginAlreadyExists = errors.New("login already exists in ProfileStore")
paddy@44 14 ErrLoginNotFound = errors.New("login not found in ProfileStore")
paddy@38 15 )
paddy@38 16
paddy@27 17 type Profile struct {
paddy@38 18 ID uuid.ID
paddy@38 19 Name string
paddy@38 20 Passphrase string
paddy@38 21 Iterations int64
paddy@38 22 Salt string
paddy@38 23 PassphraseScheme int
paddy@38 24 Compromised bool
paddy@38 25 LockedUntil time.Time
paddy@38 26 PassphraseReset string
paddy@38 27 PassphraseResetCreated time.Time
paddy@38 28 Created time.Time
paddy@38 29 LastSeen time.Time
paddy@38 30 }
paddy@38 31
paddy@38 32 func (p *Profile) ApplyChange(change ProfileChange) {
paddy@38 33 if change.Name != nil {
paddy@38 34 p.Name = *change.Name
paddy@38 35 }
paddy@38 36 if change.Passphrase != nil {
paddy@38 37 p.Passphrase = *change.Passphrase
paddy@38 38 }
paddy@38 39 if change.Iterations != nil {
paddy@38 40 p.Iterations = *change.Iterations
paddy@38 41 }
paddy@38 42 if change.Salt != nil {
paddy@38 43 p.Salt = *change.Salt
paddy@38 44 }
paddy@38 45 if change.PassphraseScheme != nil {
paddy@38 46 p.PassphraseScheme = *change.PassphraseScheme
paddy@38 47 }
paddy@38 48 if change.Compromised != nil {
paddy@38 49 p.Compromised = *change.Compromised
paddy@38 50 }
paddy@38 51 if change.LockedUntil != nil {
paddy@38 52 p.LockedUntil = *change.LockedUntil
paddy@38 53 }
paddy@38 54 if change.PassphraseReset != nil {
paddy@38 55 p.PassphraseReset = *change.PassphraseReset
paddy@38 56 }
paddy@38 57 if change.PassphraseResetCreated != nil {
paddy@38 58 p.PassphraseResetCreated = *change.PassphraseResetCreated
paddy@38 59 }
paddy@38 60 if change.LastSeen != nil {
paddy@38 61 p.LastSeen = *change.LastSeen
paddy@38 62 }
paddy@38 63 }
paddy@38 64
paddy@44 65 func (p *Profile) ApplyBulkChange(change BulkProfileChange) {
paddy@44 66 if change.Compromised != nil {
paddy@44 67 p.Compromised = *change.Compromised
paddy@44 68 }
paddy@44 69 }
paddy@44 70
paddy@38 71 type ProfileChange struct {
paddy@38 72 Name *string
paddy@38 73 Passphrase *string
paddy@38 74 Iterations *int64
paddy@38 75 Salt *string
paddy@38 76 PassphraseScheme *int
paddy@38 77 Compromised *bool
paddy@38 78 LockedUntil *time.Time
paddy@38 79 PassphraseReset *string
paddy@38 80 PassphraseResetCreated *time.Time
paddy@38 81 LastSeen *time.Time
paddy@38 82 }
paddy@38 83
paddy@38 84 func (c ProfileChange) Validate() error {
paddy@40 85 // TODO: validate profile changes
paddy@38 86 return nil
paddy@27 87 }
paddy@27 88
paddy@44 89 type BulkProfileChange struct {
paddy@44 90 Compromised *bool
paddy@44 91 }
paddy@44 92
paddy@44 93 func (b BulkProfileChange) Validate() error {
paddy@44 94 // TODO: validate bulk profile changs
paddy@44 95 return nil
paddy@44 96 }
paddy@44 97
paddy@27 98 type Login struct {
paddy@27 99 Type string
paddy@27 100 Value string
paddy@27 101 ProfileID uuid.ID
paddy@27 102 Created time.Time
paddy@27 103 LastUsed time.Time
paddy@27 104 }
paddy@27 105
paddy@27 106 type ProfileStore interface {
paddy@27 107 GetProfileByID(id uuid.ID) (Profile, error)
paddy@44 108 GetProfileByLogin(loginType, value string) (Profile, error)
paddy@38 109 SaveProfile(profile Profile) error
paddy@38 110 UpdateProfile(id uuid.ID, change ProfileChange) error
paddy@44 111 UpdateProfiles(ids []uuid.ID, change BulkProfileChange) error
paddy@27 112 DeleteProfile(id uuid.ID) error
paddy@44 113
paddy@44 114 AddLogin(login Login) error
paddy@44 115 RemoveLogin(loginType, value string, profile uuid.ID) error
paddy@44 116 RecordLoginUse(loginType, value string, when time.Time) error
paddy@44 117 ListLogins(profile uuid.ID, num, offset int) ([]Login, error)
paddy@38 118 }
paddy@27 119
paddy@38 120 func (m *Memstore) GetProfileByID(id uuid.ID) (Profile, error) {
paddy@38 121 m.profileLock.RLock()
paddy@38 122 defer m.profileLock.RUnlock()
paddy@38 123 p, ok := m.profiles[id.String()]
paddy@38 124 if !ok {
paddy@38 125 return Profile{}, ErrProfileNotFound
paddy@38 126 }
paddy@38 127 return p, nil
paddy@27 128 }
paddy@38 129
paddy@44 130 func (m *Memstore) GetProfileByLogin(loginType, value string) (Profile, error) {
paddy@44 131 m.loginLock.RLock()
paddy@44 132 defer m.loginLock.RUnlock()
paddy@44 133 login, ok := m.logins[loginType+":"+value]
paddy@44 134 if !ok {
paddy@44 135 return Profile{}, ErrLoginNotFound
paddy@44 136 }
paddy@44 137 m.profileLock.RLock()
paddy@44 138 defer m.profileLock.RUnlock()
paddy@44 139 profile, ok := m.profiles[login.ProfileID.String()]
paddy@44 140 if !ok {
paddy@44 141 return Profile{}, ErrProfileNotFound
paddy@44 142 }
paddy@44 143 return profile, nil
paddy@38 144 }
paddy@38 145
paddy@38 146 func (m *Memstore) SaveProfile(profile Profile) error {
paddy@38 147 m.profileLock.Lock()
paddy@38 148 defer m.profileLock.Unlock()
paddy@38 149 _, ok := m.profiles[profile.ID.String()]
paddy@38 150 if ok {
paddy@38 151 return ErrProfileAlreadyExists
paddy@38 152 }
paddy@38 153 m.profiles[profile.ID.String()] = profile
paddy@38 154 return nil
paddy@38 155 }
paddy@38 156
paddy@38 157 func (m *Memstore) UpdateProfile(id uuid.ID, change ProfileChange) error {
paddy@38 158 m.profileLock.Lock()
paddy@38 159 defer m.profileLock.Unlock()
paddy@38 160 p, ok := m.profiles[id.String()]
paddy@38 161 if !ok {
paddy@38 162 return ErrProfileNotFound
paddy@38 163 }
paddy@38 164 p.ApplyChange(change)
paddy@38 165 m.profiles[id.String()] = p
paddy@38 166 return nil
paddy@38 167 }
paddy@38 168
paddy@44 169 func (m *Memstore) UpdateProfiles(ids []uuid.ID, change BulkProfileChange) error {
paddy@44 170 m.profileLock.Lock()
paddy@44 171 defer m.profileLock.Unlock()
paddy@44 172 for id, profile := range m.profiles {
paddy@44 173 for _, i := range ids {
paddy@44 174 if id == i.String() {
paddy@44 175 profile.ApplyBulkChange(change)
paddy@44 176 m.profiles[id] = profile
paddy@44 177 break
paddy@44 178 }
paddy@44 179 }
paddy@44 180 }
paddy@44 181 return nil
paddy@44 182 }
paddy@44 183
paddy@38 184 func (m *Memstore) DeleteProfile(id uuid.ID) error {
paddy@38 185 m.profileLock.Lock()
paddy@38 186 defer m.profileLock.Unlock()
paddy@38 187 _, ok := m.profiles[id.String()]
paddy@38 188 if !ok {
paddy@38 189 return ErrProfileNotFound
paddy@38 190 }
paddy@38 191 delete(m.profiles, id.String())
paddy@38 192 return nil
paddy@38 193 }
paddy@40 194
paddy@44 195 func (m *Memstore) AddLogin(login Login) error {
paddy@44 196 m.loginLock.Lock()
paddy@44 197 defer m.loginLock.Unlock()
paddy@44 198 _, ok := m.logins[login.Type+":"+login.Value]
paddy@44 199 if ok {
paddy@44 200 return ErrLoginAlreadyExists
paddy@44 201 }
paddy@44 202 m.logins[login.Type+":"+login.Value] = login
paddy@44 203 m.profileLoginLookup[login.ProfileID.String()] = append(m.profileLoginLookup[login.ProfileID.String()], login.Type+":"+login.Value)
paddy@44 204 return nil
paddy@44 205 }
paddy@44 206
paddy@44 207 func (m *Memstore) RemoveLogin(loginType, value string, profile uuid.ID) error {
paddy@44 208 m.loginLock.Lock()
paddy@44 209 defer m.loginLock.Unlock()
paddy@44 210 l, ok := m.logins[loginType+":"+value]
paddy@44 211 if !ok {
paddy@44 212 return ErrLoginNotFound
paddy@44 213 }
paddy@44 214 if !l.ProfileID.Equal(profile) {
paddy@44 215 return ErrLoginNotFound
paddy@44 216 }
paddy@44 217 delete(m.logins, loginType+":"+value)
paddy@44 218 pos := -1
paddy@44 219 for p, id := range m.profileLoginLookup[profile.String()] {
paddy@44 220 if id == loginType+":"+value {
paddy@44 221 pos = p
paddy@44 222 break
paddy@44 223 }
paddy@44 224 }
paddy@44 225 if pos >= 0 {
paddy@44 226 m.profileLoginLookup[profile.String()] = append(m.profileLoginLookup[profile.String()][:pos], m.profileLoginLookup[profile.String()][pos+1:]...)
paddy@44 227 }
paddy@44 228 return nil
paddy@44 229 }
paddy@44 230
paddy@44 231 func (m *Memstore) RecordLoginUse(loginType, value string, when time.Time) error {
paddy@44 232 m.loginLock.Lock()
paddy@44 233 defer m.loginLock.Unlock()
paddy@44 234 l, ok := m.logins[loginType+":"+value]
paddy@44 235 if !ok {
paddy@44 236 return ErrLoginNotFound
paddy@44 237 }
paddy@44 238 l.LastUsed = when
paddy@44 239 m.logins[loginType+":"+value] = l
paddy@44 240 return nil
paddy@44 241 }
paddy@44 242
paddy@44 243 func (m *Memstore) ListLogins(profile uuid.ID, num, offset int) ([]Login, error) {
paddy@44 244 m.loginLock.RLock()
paddy@44 245 defer m.loginLock.RUnlock()
paddy@44 246 ids, ok := m.profileLoginLookup[profile.String()]
paddy@44 247 if !ok {
paddy@44 248 return []Login{}, nil
paddy@44 249 }
paddy@44 250 if len(ids) > num+offset {
paddy@44 251 ids = ids[offset : num+offset]
paddy@44 252 } else if len(ids) > offset {
paddy@44 253 ids = ids[offset:]
paddy@44 254 } else {
paddy@44 255 return []Login{}, nil
paddy@44 256 }
paddy@44 257 logins := []Login{}
paddy@44 258 for _, id := range ids {
paddy@44 259 login, ok := m.logins[id]
paddy@44 260 if !ok {
paddy@44 261 continue
paddy@44 262 }
paddy@44 263 logins = append(logins, login)
paddy@44 264 }
paddy@44 265 return logins, nil
paddy@44 266 }