auth

Paddy 2014-10-15 Parent:73a9f7a6af54 Child:e45bfa2abc00

51:116342ffc65f Go to Latest

auth/profile.go

Create a grant confirmation endpoint and its first test. Lay the framework for how we're going to write endpoints, and how we're going to test them by doing a super simple grant confirmation endpoint (where the user authorizes the grant, which can then be exchanged for a token) and a simple test to ensure that a page gets rendered when valid input is provided. We're still missing a lot of test cases: when different forms of valid input are provided (e.g., no scope, no redirect URI, etc.); when invalid input is provided; etc.

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@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 }