auth
auth/profile.go
Update error strings, add ErrNo*Store errors. Update error strings to consistently begin with a lowercase letter and end without punctuation, as per the Go style guide. See https://code.google.com/p/go-wiki/wiki/CodeReviewComments#Error_Strings For each type of Store, add an ErrNo*Store error (e.g., ErrNoTokenStore) variable, to prepare for our Context type, which will throw these errors when a Store is used without being set.
| 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@48 | 10 const ( |
| paddy@48 | 11 MinPassphraseLength = 6 |
| paddy@48 | 12 MaxPassphraseLength = 64 |
| paddy@48 | 13 ) |
| paddy@48 | 14 |
| paddy@38 | 15 var ( |
| paddy@49 | 16 ErrNoProfileStore = errors.New("no ProfileStore was specified for the Context") |
| paddy@38 | 17 ErrProfileAlreadyExists = errors.New("profile already exists in ProfileStore") |
| paddy@38 | 18 ErrProfileNotFound = errors.New("profile not found in ProfileStore") |
| paddy@44 | 19 ErrLoginAlreadyExists = errors.New("login already exists in ProfileStore") |
| paddy@44 | 20 ErrLoginNotFound = errors.New("login not found in ProfileStore") |
| paddy@48 | 21 |
| paddy@48 | 22 ErrMissingPassphrase = errors.New("missing passphrase") |
| paddy@48 | 23 ErrMissingPassphraseReset = errors.New("missing passphrase reset") |
| paddy@48 | 24 ErrMissingPassphraseResetCreated = errors.New("missing passphrase reset created timestamp") |
| paddy@48 | 25 ErrPassphraseTooShort = errors.New("passphrase too short") |
| paddy@48 | 26 ErrPassphraseTooLong = errors.New("passphrase too long") |
| paddy@38 | 27 ) |
| paddy@38 | 28 |
| paddy@27 | 29 type Profile struct { |
| paddy@38 | 30 ID uuid.ID |
| paddy@38 | 31 Name string |
| paddy@38 | 32 Passphrase string |
| paddy@38 | 33 Iterations int64 |
| paddy@38 | 34 Salt string |
| paddy@38 | 35 PassphraseScheme int |
| paddy@38 | 36 Compromised bool |
| paddy@38 | 37 LockedUntil time.Time |
| paddy@38 | 38 PassphraseReset string |
| paddy@38 | 39 PassphraseResetCreated time.Time |
| paddy@38 | 40 Created time.Time |
| paddy@38 | 41 LastSeen time.Time |
| paddy@38 | 42 } |
| paddy@38 | 43 |
| paddy@38 | 44 func (p *Profile) ApplyChange(change ProfileChange) { |
| paddy@38 | 45 if change.Name != nil { |
| paddy@38 | 46 p.Name = *change.Name |
| paddy@38 | 47 } |
| paddy@38 | 48 if change.Passphrase != nil { |
| paddy@38 | 49 p.Passphrase = *change.Passphrase |
| paddy@38 | 50 } |
| paddy@38 | 51 if change.Iterations != nil { |
| paddy@38 | 52 p.Iterations = *change.Iterations |
| paddy@38 | 53 } |
| paddy@38 | 54 if change.Salt != nil { |
| paddy@38 | 55 p.Salt = *change.Salt |
| paddy@38 | 56 } |
| paddy@38 | 57 if change.PassphraseScheme != nil { |
| paddy@38 | 58 p.PassphraseScheme = *change.PassphraseScheme |
| paddy@38 | 59 } |
| paddy@38 | 60 if change.Compromised != nil { |
| paddy@38 | 61 p.Compromised = *change.Compromised |
| paddy@38 | 62 } |
| paddy@38 | 63 if change.LockedUntil != nil { |
| paddy@38 | 64 p.LockedUntil = *change.LockedUntil |
| paddy@38 | 65 } |
| paddy@38 | 66 if change.PassphraseReset != nil { |
| paddy@38 | 67 p.PassphraseReset = *change.PassphraseReset |
| paddy@38 | 68 } |
| paddy@38 | 69 if change.PassphraseResetCreated != nil { |
| paddy@38 | 70 p.PassphraseResetCreated = *change.PassphraseResetCreated |
| paddy@38 | 71 } |
| paddy@38 | 72 if change.LastSeen != nil { |
| paddy@38 | 73 p.LastSeen = *change.LastSeen |
| paddy@38 | 74 } |
| paddy@38 | 75 } |
| paddy@38 | 76 |
| paddy@44 | 77 func (p *Profile) ApplyBulkChange(change BulkProfileChange) { |
| paddy@44 | 78 if change.Compromised != nil { |
| paddy@44 | 79 p.Compromised = *change.Compromised |
| paddy@44 | 80 } |
| paddy@44 | 81 } |
| paddy@44 | 82 |
| paddy@38 | 83 type ProfileChange struct { |
| paddy@38 | 84 Name *string |
| paddy@38 | 85 Passphrase *string |
| paddy@38 | 86 Iterations *int64 |
| paddy@38 | 87 Salt *string |
| paddy@38 | 88 PassphraseScheme *int |
| paddy@38 | 89 Compromised *bool |
| paddy@38 | 90 LockedUntil *time.Time |
| paddy@38 | 91 PassphraseReset *string |
| paddy@38 | 92 PassphraseResetCreated *time.Time |
| paddy@38 | 93 LastSeen *time.Time |
| paddy@38 | 94 } |
| paddy@38 | 95 |
| paddy@38 | 96 func (c ProfileChange) Validate() error { |
| paddy@48 | 97 if c.Name == nil && c.Passphrase == nil && c.Iterations == nil && c.Salt == nil && c.PassphraseScheme == nil && c.Compromised == nil && c.LockedUntil == nil && c.PassphraseReset == nil && c.PassphraseResetCreated == nil && c.LastSeen == nil { |
| paddy@48 | 98 return ErrEmptyChange |
| paddy@48 | 99 } |
| paddy@48 | 100 if c.PassphraseScheme != nil && c.Passphrase == nil { |
| paddy@48 | 101 return ErrMissingPassphrase |
| paddy@48 | 102 } |
| paddy@48 | 103 if c.PassphraseReset != nil && c.PassphraseResetCreated == nil { |
| paddy@48 | 104 return ErrMissingPassphraseResetCreated |
| paddy@48 | 105 } |
| paddy@48 | 106 if c.PassphraseReset == nil && c.PassphraseResetCreated != nil { |
| paddy@48 | 107 return ErrMissingPassphraseReset |
| paddy@48 | 108 } |
| paddy@48 | 109 if c.Salt != nil && c.Passphrase == nil { |
| paddy@48 | 110 return ErrMissingPassphrase |
| paddy@48 | 111 } |
| paddy@48 | 112 if c.Iterations != nil && c.Passphrase == nil { |
| paddy@48 | 113 return ErrMissingPassphrase |
| paddy@48 | 114 } |
| paddy@48 | 115 if c.Passphrase != nil && len(*c.Passphrase) < MinPassphraseLength { |
| paddy@48 | 116 return ErrPassphraseTooShort |
| paddy@48 | 117 } |
| paddy@48 | 118 if c.Passphrase != nil && len(*c.Passphrase) > MaxPassphraseLength { |
| paddy@48 | 119 return ErrPassphraseTooLong |
| paddy@48 | 120 } |
| paddy@38 | 121 return nil |
| paddy@27 | 122 } |
| paddy@27 | 123 |
| paddy@44 | 124 type BulkProfileChange struct { |
| paddy@44 | 125 Compromised *bool |
| paddy@44 | 126 } |
| paddy@44 | 127 |
| paddy@44 | 128 func (b BulkProfileChange) Validate() error { |
| paddy@48 | 129 if b.Compromised == nil { |
| paddy@48 | 130 return ErrEmptyChange |
| paddy@48 | 131 } |
| paddy@44 | 132 return nil |
| paddy@44 | 133 } |
| paddy@44 | 134 |
| paddy@27 | 135 type Login struct { |
| paddy@27 | 136 Type string |
| paddy@27 | 137 Value string |
| paddy@27 | 138 ProfileID uuid.ID |
| paddy@27 | 139 Created time.Time |
| paddy@27 | 140 LastUsed time.Time |
| paddy@27 | 141 } |
| paddy@27 | 142 |
| paddy@27 | 143 type ProfileStore interface { |
| paddy@27 | 144 GetProfileByID(id uuid.ID) (Profile, error) |
| paddy@44 | 145 GetProfileByLogin(loginType, value string) (Profile, error) |
| paddy@38 | 146 SaveProfile(profile Profile) error |
| paddy@38 | 147 UpdateProfile(id uuid.ID, change ProfileChange) error |
| paddy@44 | 148 UpdateProfiles(ids []uuid.ID, change BulkProfileChange) error |
| paddy@27 | 149 DeleteProfile(id uuid.ID) error |
| paddy@44 | 150 |
| paddy@44 | 151 AddLogin(login Login) error |
| paddy@44 | 152 RemoveLogin(loginType, value string, profile uuid.ID) error |
| paddy@44 | 153 RecordLoginUse(loginType, value string, when time.Time) error |
| paddy@44 | 154 ListLogins(profile uuid.ID, num, offset int) ([]Login, error) |
| paddy@38 | 155 } |
| paddy@27 | 156 |
| paddy@38 | 157 func (m *Memstore) GetProfileByID(id uuid.ID) (Profile, error) { |
| paddy@38 | 158 m.profileLock.RLock() |
| paddy@38 | 159 defer m.profileLock.RUnlock() |
| paddy@38 | 160 p, ok := m.profiles[id.String()] |
| paddy@38 | 161 if !ok { |
| paddy@38 | 162 return Profile{}, ErrProfileNotFound |
| paddy@38 | 163 } |
| paddy@38 | 164 return p, nil |
| paddy@27 | 165 } |
| paddy@38 | 166 |
| paddy@44 | 167 func (m *Memstore) GetProfileByLogin(loginType, value string) (Profile, error) { |
| paddy@44 | 168 m.loginLock.RLock() |
| paddy@44 | 169 defer m.loginLock.RUnlock() |
| paddy@44 | 170 login, ok := m.logins[loginType+":"+value] |
| paddy@44 | 171 if !ok { |
| paddy@44 | 172 return Profile{}, ErrLoginNotFound |
| paddy@44 | 173 } |
| paddy@44 | 174 m.profileLock.RLock() |
| paddy@44 | 175 defer m.profileLock.RUnlock() |
| paddy@44 | 176 profile, ok := m.profiles[login.ProfileID.String()] |
| paddy@44 | 177 if !ok { |
| paddy@44 | 178 return Profile{}, ErrProfileNotFound |
| paddy@44 | 179 } |
| paddy@44 | 180 return profile, nil |
| paddy@38 | 181 } |
| paddy@38 | 182 |
| paddy@38 | 183 func (m *Memstore) SaveProfile(profile Profile) error { |
| paddy@38 | 184 m.profileLock.Lock() |
| paddy@38 | 185 defer m.profileLock.Unlock() |
| paddy@38 | 186 _, ok := m.profiles[profile.ID.String()] |
| paddy@38 | 187 if ok { |
| paddy@38 | 188 return ErrProfileAlreadyExists |
| paddy@38 | 189 } |
| paddy@38 | 190 m.profiles[profile.ID.String()] = profile |
| paddy@38 | 191 return nil |
| paddy@38 | 192 } |
| paddy@38 | 193 |
| paddy@38 | 194 func (m *Memstore) UpdateProfile(id uuid.ID, change ProfileChange) error { |
| paddy@38 | 195 m.profileLock.Lock() |
| paddy@38 | 196 defer m.profileLock.Unlock() |
| paddy@38 | 197 p, ok := m.profiles[id.String()] |
| paddy@38 | 198 if !ok { |
| paddy@38 | 199 return ErrProfileNotFound |
| paddy@38 | 200 } |
| paddy@38 | 201 p.ApplyChange(change) |
| paddy@38 | 202 m.profiles[id.String()] = p |
| paddy@38 | 203 return nil |
| paddy@38 | 204 } |
| paddy@38 | 205 |
| paddy@44 | 206 func (m *Memstore) UpdateProfiles(ids []uuid.ID, change BulkProfileChange) error { |
| paddy@44 | 207 m.profileLock.Lock() |
| paddy@44 | 208 defer m.profileLock.Unlock() |
| paddy@44 | 209 for id, profile := range m.profiles { |
| paddy@44 | 210 for _, i := range ids { |
| paddy@44 | 211 if id == i.String() { |
| paddy@44 | 212 profile.ApplyBulkChange(change) |
| paddy@44 | 213 m.profiles[id] = profile |
| paddy@44 | 214 break |
| paddy@44 | 215 } |
| paddy@44 | 216 } |
| paddy@44 | 217 } |
| paddy@44 | 218 return nil |
| paddy@44 | 219 } |
| paddy@44 | 220 |
| paddy@38 | 221 func (m *Memstore) DeleteProfile(id uuid.ID) error { |
| paddy@38 | 222 m.profileLock.Lock() |
| paddy@38 | 223 defer m.profileLock.Unlock() |
| paddy@38 | 224 _, ok := m.profiles[id.String()] |
| paddy@38 | 225 if !ok { |
| paddy@38 | 226 return ErrProfileNotFound |
| paddy@38 | 227 } |
| paddy@38 | 228 delete(m.profiles, id.String()) |
| paddy@38 | 229 return nil |
| paddy@38 | 230 } |
| paddy@40 | 231 |
| paddy@44 | 232 func (m *Memstore) AddLogin(login Login) error { |
| paddy@44 | 233 m.loginLock.Lock() |
| paddy@44 | 234 defer m.loginLock.Unlock() |
| paddy@44 | 235 _, ok := m.logins[login.Type+":"+login.Value] |
| paddy@44 | 236 if ok { |
| paddy@44 | 237 return ErrLoginAlreadyExists |
| paddy@44 | 238 } |
| paddy@44 | 239 m.logins[login.Type+":"+login.Value] = login |
| paddy@44 | 240 m.profileLoginLookup[login.ProfileID.String()] = append(m.profileLoginLookup[login.ProfileID.String()], login.Type+":"+login.Value) |
| paddy@44 | 241 return nil |
| paddy@44 | 242 } |
| paddy@44 | 243 |
| paddy@44 | 244 func (m *Memstore) RemoveLogin(loginType, value string, profile uuid.ID) error { |
| paddy@44 | 245 m.loginLock.Lock() |
| paddy@44 | 246 defer m.loginLock.Unlock() |
| paddy@44 | 247 l, ok := m.logins[loginType+":"+value] |
| paddy@44 | 248 if !ok { |
| paddy@44 | 249 return ErrLoginNotFound |
| paddy@44 | 250 } |
| paddy@44 | 251 if !l.ProfileID.Equal(profile) { |
| paddy@44 | 252 return ErrLoginNotFound |
| paddy@44 | 253 } |
| paddy@44 | 254 delete(m.logins, loginType+":"+value) |
| paddy@44 | 255 pos := -1 |
| paddy@44 | 256 for p, id := range m.profileLoginLookup[profile.String()] { |
| paddy@44 | 257 if id == loginType+":"+value { |
| paddy@44 | 258 pos = p |
| paddy@44 | 259 break |
| paddy@44 | 260 } |
| paddy@44 | 261 } |
| paddy@44 | 262 if pos >= 0 { |
| paddy@44 | 263 m.profileLoginLookup[profile.String()] = append(m.profileLoginLookup[profile.String()][:pos], m.profileLoginLookup[profile.String()][pos+1:]...) |
| paddy@44 | 264 } |
| paddy@44 | 265 return nil |
| paddy@44 | 266 } |
| paddy@44 | 267 |
| paddy@44 | 268 func (m *Memstore) RecordLoginUse(loginType, value string, when time.Time) error { |
| paddy@44 | 269 m.loginLock.Lock() |
| paddy@44 | 270 defer m.loginLock.Unlock() |
| paddy@44 | 271 l, ok := m.logins[loginType+":"+value] |
| paddy@44 | 272 if !ok { |
| paddy@44 | 273 return ErrLoginNotFound |
| paddy@44 | 274 } |
| paddy@44 | 275 l.LastUsed = when |
| paddy@44 | 276 m.logins[loginType+":"+value] = l |
| paddy@44 | 277 return nil |
| paddy@44 | 278 } |
| paddy@44 | 279 |
| paddy@44 | 280 func (m *Memstore) ListLogins(profile uuid.ID, num, offset int) ([]Login, error) { |
| paddy@44 | 281 m.loginLock.RLock() |
| paddy@44 | 282 defer m.loginLock.RUnlock() |
| paddy@44 | 283 ids, ok := m.profileLoginLookup[profile.String()] |
| paddy@44 | 284 if !ok { |
| paddy@44 | 285 return []Login{}, nil |
| paddy@44 | 286 } |
| paddy@44 | 287 if len(ids) > num+offset { |
| paddy@44 | 288 ids = ids[offset : num+offset] |
| paddy@44 | 289 } else if len(ids) > offset { |
| paddy@44 | 290 ids = ids[offset:] |
| paddy@44 | 291 } else { |
| paddy@44 | 292 return []Login{}, nil |
| paddy@44 | 293 } |
| paddy@44 | 294 logins := []Login{} |
| paddy@44 | 295 for _, id := range ids { |
| paddy@44 | 296 login, ok := m.logins[id] |
| paddy@44 | 297 if !ok { |
| paddy@44 | 298 continue |
| paddy@44 | 299 } |
| paddy@44 | 300 logins = append(logins, login) |
| paddy@44 | 301 } |
| paddy@44 | 302 return logins, nil |
| paddy@44 | 303 } |