auth

Paddy 2014-09-07 Parent:111382010276 Child:690561c6619a

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