auth

Paddy 2014-12-13 Parent:42bc3e44f4fe Child:5bccbed6631b

90:d5561856f45e Go to Latest

auth/profile.go

Log ignored errors, grant revocations can return an error. Turn a few TODOs for logging errors into calls to log the actual error. Change the return type of grant revocations for GrantTypes to an error, so they can be logged by the system, not each GrantType. Implement a stub of the revocation function for the authcode GrantType.

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@48 10 const (
paddy@57 11 // MinPassphraseLength is the minimum length, in bytes, of a passphrase, exclusive.
paddy@48 12 MinPassphraseLength = 6
paddy@57 13 // MaxPassphraseLength is the maximum length, in bytes, of a passphrase, exclusive.
paddy@48 14 MaxPassphraseLength = 64
paddy@69 15 // CurPassphraseScheme is the current passphrase scheme. Incrememnt it when we use a different passphrase scheme
paddy@69 16 CurPassphraseScheme = 1
paddy@48 17 )
paddy@48 18
paddy@38 19 var (
paddy@57 20 // ErrNoProfileStore is returned when a Context tries to act on a profileStore without setting one first.
paddy@57 21 ErrNoProfileStore = errors.New("no profileStore was specified for the Context")
paddy@57 22 // ErrProfileAlreadyExists is returned when a Profile is added to a profileStore, but another Profile with
paddy@57 23 // the same ID already exists in the profileStore.
paddy@57 24 ErrProfileAlreadyExists = errors.New("profile already exists in profileStore")
paddy@57 25 // ErrProfileNotFound is returned when a Profile is requested but not found in the profileStore.
paddy@57 26 ErrProfileNotFound = errors.New("profile not found in profileStore")
paddy@57 27 // ErrLoginAlreadyExists is returned when a Login is added to a profileStore, but another Login with the same
paddy@57 28 // Type and Value already exists in the profileStore.
paddy@57 29 ErrLoginAlreadyExists = errors.New("login already exists in profileStore")
paddy@57 30 // ErrLoginNotFound is returned when a Login is requested but not found in the profileStore.
paddy@57 31 ErrLoginNotFound = errors.New("login not found in profileStore")
paddy@48 32
paddy@57 33 // ErrMissingPassphrase is returned when a ProfileChange is validated but does not contain a
paddy@57 34 // Passphrase, and requires one.
paddy@57 35 ErrMissingPassphrase = errors.New("missing passphrase")
paddy@57 36 // ErrMissingPassphraseReset is returned when a ProfileChange is validated but does not contain
paddy@57 37 // a PassphraseReset, and requires one.
paddy@57 38 ErrMissingPassphraseReset = errors.New("missing passphrase reset")
paddy@57 39 // ErrMissingPassphraseResetCreated is returned when a ProfileChange is validated but does not
paddy@57 40 // contain a PassphraseResetCreated, and requires one.
paddy@48 41 ErrMissingPassphraseResetCreated = errors.New("missing passphrase reset created timestamp")
paddy@57 42 // ErrPassphraseTooShort is returned when a ProfileChange is validated and contains a Passphrase,
paddy@57 43 // but the Passphrase is shorter than MinPassphraseLength.
paddy@57 44 ErrPassphraseTooShort = errors.New("passphrase too short")
paddy@57 45 // ErrPassphraseTooLong is returned when a ProfileChange is validated and contains a Passphrase,
paddy@57 46 // but the Passphrase is longer than MaxPassphraseLength.
paddy@57 47 ErrPassphraseTooLong = errors.New("passphrase too long")
paddy@38 48 )
paddy@38 49
paddy@57 50 // Profile represents a single user of the service,
paddy@57 51 // including their authentication information, but not
paddy@57 52 // including their username or email.
paddy@27 53 type Profile struct {
paddy@38 54 ID uuid.ID
paddy@38 55 Name string
paddy@38 56 Passphrase string
paddy@69 57 Iterations int
paddy@38 58 Salt string
paddy@38 59 PassphraseScheme int
paddy@38 60 Compromised bool
paddy@38 61 LockedUntil time.Time
paddy@38 62 PassphraseReset string
paddy@38 63 PassphraseResetCreated time.Time
paddy@38 64 Created time.Time
paddy@38 65 LastSeen time.Time
paddy@38 66 }
paddy@38 67
paddy@57 68 // ApplyChange applies the properties of the passed ProfileChange
paddy@57 69 // to the Profile it is called on.
paddy@38 70 func (p *Profile) ApplyChange(change ProfileChange) {
paddy@38 71 if change.Name != nil {
paddy@38 72 p.Name = *change.Name
paddy@38 73 }
paddy@38 74 if change.Passphrase != nil {
paddy@38 75 p.Passphrase = *change.Passphrase
paddy@38 76 }
paddy@38 77 if change.Iterations != nil {
paddy@38 78 p.Iterations = *change.Iterations
paddy@38 79 }
paddy@38 80 if change.Salt != nil {
paddy@38 81 p.Salt = *change.Salt
paddy@38 82 }
paddy@38 83 if change.PassphraseScheme != nil {
paddy@38 84 p.PassphraseScheme = *change.PassphraseScheme
paddy@38 85 }
paddy@38 86 if change.Compromised != nil {
paddy@38 87 p.Compromised = *change.Compromised
paddy@38 88 }
paddy@38 89 if change.LockedUntil != nil {
paddy@38 90 p.LockedUntil = *change.LockedUntil
paddy@38 91 }
paddy@38 92 if change.PassphraseReset != nil {
paddy@38 93 p.PassphraseReset = *change.PassphraseReset
paddy@38 94 }
paddy@38 95 if change.PassphraseResetCreated != nil {
paddy@38 96 p.PassphraseResetCreated = *change.PassphraseResetCreated
paddy@38 97 }
paddy@38 98 if change.LastSeen != nil {
paddy@38 99 p.LastSeen = *change.LastSeen
paddy@38 100 }
paddy@38 101 }
paddy@38 102
paddy@57 103 // ApplyBulkChange applies the properties of the passed BulkProfileChange
paddy@57 104 // to the Profile it is called on.
paddy@44 105 func (p *Profile) ApplyBulkChange(change BulkProfileChange) {
paddy@44 106 if change.Compromised != nil {
paddy@44 107 p.Compromised = *change.Compromised
paddy@44 108 }
paddy@44 109 }
paddy@44 110
paddy@57 111 // ProfileChange represents a single atomic change to a Profile's mutable data.
paddy@38 112 type ProfileChange struct {
paddy@38 113 Name *string
paddy@38 114 Passphrase *string
paddy@69 115 Iterations *int
paddy@38 116 Salt *string
paddy@38 117 PassphraseScheme *int
paddy@38 118 Compromised *bool
paddy@38 119 LockedUntil *time.Time
paddy@38 120 PassphraseReset *string
paddy@38 121 PassphraseResetCreated *time.Time
paddy@38 122 LastSeen *time.Time
paddy@38 123 }
paddy@38 124
paddy@57 125 // Validate checks the ProfileChange it is called on
paddy@57 126 // and asserts its internal validity, or lack thereof.
paddy@57 127 // A descriptive error will be returned in the case of
paddy@57 128 // an invalid change.
paddy@38 129 func (c ProfileChange) Validate() error {
paddy@48 130 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 131 return ErrEmptyChange
paddy@48 132 }
paddy@48 133 if c.PassphraseScheme != nil && c.Passphrase == nil {
paddy@48 134 return ErrMissingPassphrase
paddy@48 135 }
paddy@48 136 if c.PassphraseReset != nil && c.PassphraseResetCreated == nil {
paddy@48 137 return ErrMissingPassphraseResetCreated
paddy@48 138 }
paddy@48 139 if c.PassphraseReset == nil && c.PassphraseResetCreated != nil {
paddy@48 140 return ErrMissingPassphraseReset
paddy@48 141 }
paddy@48 142 if c.Salt != nil && c.Passphrase == nil {
paddy@48 143 return ErrMissingPassphrase
paddy@48 144 }
paddy@48 145 if c.Iterations != nil && c.Passphrase == nil {
paddy@48 146 return ErrMissingPassphrase
paddy@48 147 }
paddy@48 148 if c.Passphrase != nil && len(*c.Passphrase) < MinPassphraseLength {
paddy@48 149 return ErrPassphraseTooShort
paddy@48 150 }
paddy@48 151 if c.Passphrase != nil && len(*c.Passphrase) > MaxPassphraseLength {
paddy@48 152 return ErrPassphraseTooLong
paddy@48 153 }
paddy@38 154 return nil
paddy@27 155 }
paddy@27 156
paddy@57 157 // BulkProfileChange represents a single atomic change to many Profiles' mutable data.
paddy@57 158 // It is a subset of a ProfileChange, as it doesn't make sense to mutate some of the
paddy@57 159 // ProfileChange values across many Profiles all at once.
paddy@44 160 type BulkProfileChange struct {
paddy@44 161 Compromised *bool
paddy@44 162 }
paddy@44 163
paddy@57 164 // Validate checks the BulkProfileChange it is called on
paddy@57 165 // and asserts its internal validity, or lack thereof.
paddy@57 166 // A descriptive error will be returned in the case of an
paddy@57 167 // invalid change.
paddy@44 168 func (b BulkProfileChange) Validate() error {
paddy@48 169 if b.Compromised == nil {
paddy@48 170 return ErrEmptyChange
paddy@48 171 }
paddy@44 172 return nil
paddy@44 173 }
paddy@44 174
paddy@57 175 // Login represents a single human-friendly identifier for
paddy@57 176 // a given Profile that can be used to log into that Profile.
paddy@57 177 // Each Profile may only have one Login for each Type.
paddy@27 178 type Login struct {
paddy@27 179 Type string
paddy@27 180 Value string
paddy@27 181 ProfileID uuid.ID
paddy@27 182 Created time.Time
paddy@27 183 LastUsed time.Time
paddy@27 184 }
paddy@27 185
paddy@57 186 type profileStore interface {
paddy@57 187 getProfileByID(id uuid.ID) (Profile, error)
paddy@69 188 getProfileByLogin(value string) (Profile, error)
paddy@57 189 saveProfile(profile Profile) error
paddy@57 190 updateProfile(id uuid.ID, change ProfileChange) error
paddy@57 191 updateProfiles(ids []uuid.ID, change BulkProfileChange) error
paddy@57 192 deleteProfile(id uuid.ID) error
paddy@44 193
paddy@57 194 addLogin(login Login) error
paddy@69 195 removeLogin(value string, profile uuid.ID) error
paddy@69 196 recordLoginUse(value string, when time.Time) error
paddy@57 197 listLogins(profile uuid.ID, num, offset int) ([]Login, error)
paddy@38 198 }
paddy@27 199
paddy@57 200 func (m *memstore) getProfileByID(id uuid.ID) (Profile, error) {
paddy@38 201 m.profileLock.RLock()
paddy@38 202 defer m.profileLock.RUnlock()
paddy@38 203 p, ok := m.profiles[id.String()]
paddy@38 204 if !ok {
paddy@38 205 return Profile{}, ErrProfileNotFound
paddy@38 206 }
paddy@38 207 return p, nil
paddy@27 208 }
paddy@38 209
paddy@69 210 func (m *memstore) getProfileByLogin(value string) (Profile, error) {
paddy@44 211 m.loginLock.RLock()
paddy@44 212 defer m.loginLock.RUnlock()
paddy@69 213 login, ok := m.logins[value]
paddy@44 214 if !ok {
paddy@44 215 return Profile{}, ErrLoginNotFound
paddy@44 216 }
paddy@44 217 m.profileLock.RLock()
paddy@44 218 defer m.profileLock.RUnlock()
paddy@44 219 profile, ok := m.profiles[login.ProfileID.String()]
paddy@44 220 if !ok {
paddy@44 221 return Profile{}, ErrProfileNotFound
paddy@44 222 }
paddy@44 223 return profile, nil
paddy@38 224 }
paddy@38 225
paddy@57 226 func (m *memstore) saveProfile(profile Profile) error {
paddy@38 227 m.profileLock.Lock()
paddy@38 228 defer m.profileLock.Unlock()
paddy@38 229 _, ok := m.profiles[profile.ID.String()]
paddy@38 230 if ok {
paddy@38 231 return ErrProfileAlreadyExists
paddy@38 232 }
paddy@38 233 m.profiles[profile.ID.String()] = profile
paddy@38 234 return nil
paddy@38 235 }
paddy@38 236
paddy@57 237 func (m *memstore) updateProfile(id uuid.ID, change ProfileChange) error {
paddy@38 238 m.profileLock.Lock()
paddy@38 239 defer m.profileLock.Unlock()
paddy@38 240 p, ok := m.profiles[id.String()]
paddy@38 241 if !ok {
paddy@38 242 return ErrProfileNotFound
paddy@38 243 }
paddy@38 244 p.ApplyChange(change)
paddy@38 245 m.profiles[id.String()] = p
paddy@38 246 return nil
paddy@38 247 }
paddy@38 248
paddy@57 249 func (m *memstore) updateProfiles(ids []uuid.ID, change BulkProfileChange) error {
paddy@44 250 m.profileLock.Lock()
paddy@44 251 defer m.profileLock.Unlock()
paddy@44 252 for id, profile := range m.profiles {
paddy@44 253 for _, i := range ids {
paddy@44 254 if id == i.String() {
paddy@44 255 profile.ApplyBulkChange(change)
paddy@44 256 m.profiles[id] = profile
paddy@44 257 break
paddy@44 258 }
paddy@44 259 }
paddy@44 260 }
paddy@44 261 return nil
paddy@44 262 }
paddy@44 263
paddy@57 264 func (m *memstore) deleteProfile(id uuid.ID) error {
paddy@38 265 m.profileLock.Lock()
paddy@38 266 defer m.profileLock.Unlock()
paddy@38 267 _, ok := m.profiles[id.String()]
paddy@38 268 if !ok {
paddy@38 269 return ErrProfileNotFound
paddy@38 270 }
paddy@38 271 delete(m.profiles, id.String())
paddy@38 272 return nil
paddy@38 273 }
paddy@40 274
paddy@57 275 func (m *memstore) addLogin(login Login) error {
paddy@44 276 m.loginLock.Lock()
paddy@44 277 defer m.loginLock.Unlock()
paddy@69 278 _, ok := m.logins[login.Value]
paddy@44 279 if ok {
paddy@44 280 return ErrLoginAlreadyExists
paddy@44 281 }
paddy@69 282 m.logins[login.Value] = login
paddy@69 283 m.profileLoginLookup[login.ProfileID.String()] = append(m.profileLoginLookup[login.ProfileID.String()], login.Value)
paddy@44 284 return nil
paddy@44 285 }
paddy@44 286
paddy@69 287 func (m *memstore) removeLogin(value string, profile uuid.ID) error {
paddy@44 288 m.loginLock.Lock()
paddy@44 289 defer m.loginLock.Unlock()
paddy@69 290 l, ok := m.logins[value]
paddy@44 291 if !ok {
paddy@44 292 return ErrLoginNotFound
paddy@44 293 }
paddy@44 294 if !l.ProfileID.Equal(profile) {
paddy@44 295 return ErrLoginNotFound
paddy@44 296 }
paddy@69 297 delete(m.logins, value)
paddy@44 298 pos := -1
paddy@44 299 for p, id := range m.profileLoginLookup[profile.String()] {
paddy@69 300 if id == value {
paddy@44 301 pos = p
paddy@44 302 break
paddy@44 303 }
paddy@44 304 }
paddy@44 305 if pos >= 0 {
paddy@44 306 m.profileLoginLookup[profile.String()] = append(m.profileLoginLookup[profile.String()][:pos], m.profileLoginLookup[profile.String()][pos+1:]...)
paddy@44 307 }
paddy@44 308 return nil
paddy@44 309 }
paddy@44 310
paddy@69 311 func (m *memstore) recordLoginUse(value string, when time.Time) error {
paddy@44 312 m.loginLock.Lock()
paddy@44 313 defer m.loginLock.Unlock()
paddy@69 314 l, ok := m.logins[value]
paddy@44 315 if !ok {
paddy@44 316 return ErrLoginNotFound
paddy@44 317 }
paddy@44 318 l.LastUsed = when
paddy@69 319 m.logins[value] = l
paddy@44 320 return nil
paddy@44 321 }
paddy@44 322
paddy@57 323 func (m *memstore) listLogins(profile uuid.ID, num, offset int) ([]Login, error) {
paddy@44 324 m.loginLock.RLock()
paddy@44 325 defer m.loginLock.RUnlock()
paddy@44 326 ids, ok := m.profileLoginLookup[profile.String()]
paddy@44 327 if !ok {
paddy@44 328 return []Login{}, nil
paddy@44 329 }
paddy@44 330 if len(ids) > num+offset {
paddy@44 331 ids = ids[offset : num+offset]
paddy@44 332 } else if len(ids) > offset {
paddy@44 333 ids = ids[offset:]
paddy@44 334 } else {
paddy@44 335 return []Login{}, nil
paddy@44 336 }
paddy@44 337 logins := []Login{}
paddy@44 338 for _, id := range ids {
paddy@44 339 login, ok := m.logins[id]
paddy@44 340 if !ok {
paddy@44 341 continue
paddy@44 342 }
paddy@44 343 logins = append(logins, login)
paddy@44 344 }
paddy@44 345 return logins, nil
paddy@44 346 }