auth

Paddy 2015-02-20 Parent:bc842183181d Child:d30a3a12d387

134:d103a598548c Browse Files

Introduced scopes. Created a Scope type and a scopeStore interface, along with the memstore methods for the scopeStore. This will allow applications to define access with granularity, so users can grant access to some data, not _all_ data. We're operating on the assumption that there won't be an unreasonable number of scopes defined, so there is no paging operation included for the ListScopes method. This is a decision that may have to be revisited in the future, depending on usecases.

config.go context.go memstore.go scope.go scope_test.go

     1.1 --- a/config.go	Thu Jan 29 20:40:55 2015 -0500
     1.2 +++ b/config.go	Fri Feb 20 22:34:43 2015 -0500
     1.3 @@ -21,6 +21,7 @@
     1.4  	ProfileStore  profileStore
     1.5  	TokenStore    tokenStore
     1.6  	SessionStore  sessionStore
     1.7 +	ScopeStore    scopeStore
     1.8  	Template      *template.Template
     1.9  	LoginURI      string
    1.10  	iterations    int
     2.1 --- a/context.go	Thu Jan 29 20:40:55 2015 -0500
     2.2 +++ b/context.go	Fri Feb 20 22:34:43 2015 -0500
     2.3 @@ -21,6 +21,7 @@
     2.4  	profiles  profileStore
     2.5  	tokens    tokenStore
     2.6  	sessions  sessionStore
     2.7 +	scopes    scopeStore
     2.8  	config    Config
     2.9  }
    2.10  
    2.11 @@ -36,6 +37,7 @@
    2.12  		profiles:  config.ProfileStore,
    2.13  		tokens:    config.TokenStore,
    2.14  		sessions:  config.SessionStore,
    2.15 +		scopes:    config.ScopeStore,
    2.16  		template:  config.Template,
    2.17  		config:    config,
    2.18  	}
    2.19 @@ -363,3 +365,38 @@
    2.20  	}
    2.21  	return c.sessions.listSessions(profile, before, num)
    2.22  }
    2.23 +
    2.24 +func (c Context) CreateScopes(scopes []Scope) error {
    2.25 +	if c.scopes == nil {
    2.26 +		return ErrNoScopeStore
    2.27 +	}
    2.28 +	return c.scopes.createScopes(scopes)
    2.29 +}
    2.30 +
    2.31 +func (c Context) GetScopes(ids []string) ([]Scope, error) {
    2.32 +	if c.scopes == nil {
    2.33 +		return []Scope{}, ErrNoScopeStore
    2.34 +	}
    2.35 +	return c.scopes.getScopes(ids)
    2.36 +}
    2.37 +
    2.38 +func (c Context) UpdateScopes(changes []ScopeChange) ([]Scope, error) {
    2.39 +	if c.scopes == nil {
    2.40 +		return []Scope{}, ErrNoScopeStore
    2.41 +	}
    2.42 +	return c.scopes.updateScopes(changes)
    2.43 +}
    2.44 +
    2.45 +func (c Context) RemoveScopes(ids []string) error {
    2.46 +	if c.scopes == nil {
    2.47 +		return ErrNoScopeStore
    2.48 +	}
    2.49 +	return c.scopes.removeScopes(ids)
    2.50 +}
    2.51 +
    2.52 +func (c Context) ListScopes() ([]Scope, error) {
    2.53 +	if c.scopes == nil {
    2.54 +		return []Scope{}, ErrNoScopeStore
    2.55 +	}
    2.56 +	return c.scopes.listScopes()
    2.57 +}
     3.1 --- a/memstore.go	Thu Jan 29 20:40:55 2015 -0500
     3.2 +++ b/memstore.go	Fri Feb 20 22:34:43 2015 -0500
     3.3 @@ -31,6 +31,9 @@
     3.4  
     3.5  	sessions    map[string]Session
     3.6  	sessionLock sync.RWMutex
     3.7 +
     3.8 +	scopes    map[string]Scope
     3.9 +	scopeLock sync.RWMutex
    3.10  }
    3.11  
    3.12  // NewMemstore returns an in-memory version of our datastores,
    3.13 @@ -50,6 +53,7 @@
    3.14  		logins:              map[string]Login{},
    3.15  		profileLoginLookup:  map[string][]string{},
    3.16  		sessions:            map[string]Session{},
    3.17 +		scopes:              map[string]Scope{},
    3.18  	}
    3.19  }
    3.20  
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/scope.go	Fri Feb 20 22:34:43 2015 -0500
     4.3 @@ -0,0 +1,159 @@
     4.4 +package auth
     4.5 +
     4.6 +import (
     4.7 +	"errors"
     4.8 +	"fmt"
     4.9 +	"sort"
    4.10 +)
    4.11 +
    4.12 +var (
    4.13 +	ErrNoScopeStore = errors.New("scopeStore not set in Context")
    4.14 +)
    4.15 +
    4.16 +type ErrScopeNotFound struct {
    4.17 +	Pos int
    4.18 +	ID  string
    4.19 +}
    4.20 +
    4.21 +func (e ErrScopeNotFound) Error() string {
    4.22 +	return fmt.Sprintf("scope %s couldn't be found", e.ID)
    4.23 +}
    4.24 +
    4.25 +type ErrScopeAlreadyExists struct {
    4.26 +	Pos int
    4.27 +	ID  string
    4.28 +}
    4.29 +
    4.30 +func (e ErrScopeAlreadyExists) Error() string {
    4.31 +	return fmt.Sprintf("scope %s already exists", e.ID)
    4.32 +}
    4.33 +
    4.34 +// Scope represents a limit on the access that a grant provides.
    4.35 +type Scope struct {
    4.36 +	ID          string
    4.37 +	Name        string
    4.38 +	Description string
    4.39 +}
    4.40 +
    4.41 +func (s *Scope) ApplyChange(change ScopeChange) {
    4.42 +	if change.Name != nil {
    4.43 +		s.Name = *change.Name
    4.44 +	}
    4.45 +	if change.Description != nil {
    4.46 +		s.Description = *change.Description
    4.47 +	}
    4.48 +}
    4.49 +
    4.50 +type sortedScopes []Scope
    4.51 +
    4.52 +func (s sortedScopes) Len() int {
    4.53 +	return len(s)
    4.54 +}
    4.55 +
    4.56 +func (s sortedScopes) Swap(i, j int) {
    4.57 +	s[i], s[j] = s[j], s[i]
    4.58 +}
    4.59 +
    4.60 +func (s sortedScopes) Less(i, j int) bool {
    4.61 +	return s[i].ID < s[j].ID
    4.62 +}
    4.63 +
    4.64 +// ScopeChange represents a change to a Scope.
    4.65 +type ScopeChange struct {
    4.66 +	ID          string
    4.67 +	Name        *string
    4.68 +	Description *string
    4.69 +}
    4.70 +
    4.71 +type scopeStore interface {
    4.72 +	createScopes(scopes []Scope) error
    4.73 +	getScopes(ids []string) ([]Scope, error)
    4.74 +	updateScopes(changes []ScopeChange) ([]Scope, error)
    4.75 +	removeScopes(ids []string) error
    4.76 +	listScopes() ([]Scope, error)
    4.77 +}
    4.78 +
    4.79 +func (m *memstore) createScopes(scopes []Scope) error {
    4.80 +	m.scopeLock.Lock()
    4.81 +	defer m.scopeLock.Unlock()
    4.82 +
    4.83 +	for pos, scope := range scopes {
    4.84 +		if _, ok := m.scopes[scope.ID]; ok {
    4.85 +			return ErrScopeAlreadyExists{Pos: pos, ID: scope.ID}
    4.86 +		}
    4.87 +	}
    4.88 +	for _, scope := range scopes {
    4.89 +		m.scopes[scope.ID] = scope
    4.90 +	}
    4.91 +	return nil
    4.92 +}
    4.93 +
    4.94 +func (m *memstore) getScopes(ids []string) ([]Scope, error) {
    4.95 +	m.scopeLock.RLock()
    4.96 +	defer m.scopeLock.RUnlock()
    4.97 +
    4.98 +	scopes := []Scope{}
    4.99 +	for pos, id := range ids {
   4.100 +		scope, ok := m.scopes[id]
   4.101 +		if !ok {
   4.102 +			return []Scope{}, ErrScopeNotFound{ID: id, Pos: pos}
   4.103 +		}
   4.104 +		scopes = append(scopes, scope)
   4.105 +	}
   4.106 +	sorted := sortedScopes(scopes)
   4.107 +	sort.Sort(sorted)
   4.108 +	scopes = sorted
   4.109 +	return scopes, nil
   4.110 +}
   4.111 +
   4.112 +func (m *memstore) updateScopes(changes []ScopeChange) ([]Scope, error) {
   4.113 +	m.scopeLock.Lock()
   4.114 +	defer m.scopeLock.Unlock()
   4.115 +
   4.116 +	scopes := []Scope{}
   4.117 +
   4.118 +	for pos, change := range changes {
   4.119 +		if _, ok := m.scopes[change.ID]; !ok {
   4.120 +			return []Scope{}, ErrScopeNotFound{Pos: pos, ID: change.ID}
   4.121 +		}
   4.122 +	}
   4.123 +	for _, change := range changes {
   4.124 +		scope := m.scopes[change.ID]
   4.125 +		scope.ApplyChange(change)
   4.126 +		m.scopes[change.ID] = scope
   4.127 +		scopes = append(scopes, scope)
   4.128 +	}
   4.129 +	sorted := sortedScopes(scopes)
   4.130 +	sort.Sort(sorted)
   4.131 +	scopes = sorted
   4.132 +	return scopes, nil
   4.133 +}
   4.134 +
   4.135 +func (m *memstore) removeScopes(ids []string) error {
   4.136 +	m.scopeLock.Lock()
   4.137 +	defer m.scopeLock.Unlock()
   4.138 +
   4.139 +	for pos, id := range ids {
   4.140 +		if _, ok := m.scopes[id]; !ok {
   4.141 +			return ErrScopeNotFound{Pos: pos, ID: id}
   4.142 +		}
   4.143 +	}
   4.144 +	for _, id := range ids {
   4.145 +		delete(m.scopes, id)
   4.146 +	}
   4.147 +	return nil
   4.148 +}
   4.149 +
   4.150 +func (m *memstore) listScopes() ([]Scope, error) {
   4.151 +	m.scopeLock.RLock()
   4.152 +	defer m.scopeLock.RUnlock()
   4.153 +
   4.154 +	scopes := []Scope{}
   4.155 +	for _, scope := range m.scopes {
   4.156 +		scopes = append(scopes, scope)
   4.157 +	}
   4.158 +	sorted := sortedScopes(scopes)
   4.159 +	sort.Sort(sorted)
   4.160 +	scopes = sorted
   4.161 +	return scopes, nil
   4.162 +}
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/scope_test.go	Fri Feb 20 22:34:43 2015 -0500
     5.3 @@ -0,0 +1,197 @@
     5.4 +package auth
     5.5 +
     5.6 +import "testing"
     5.7 +
     5.8 +var scopeStores = []scopeStore{NewMemstore()}
     5.9 +
    5.10 +func compareScopes(scope1, scope2 Scope) (success bool, field string, val1, val2 interface{}) {
    5.11 +	if scope1.ID != scope2.ID {
    5.12 +		return false, "ID", scope1.ID, scope2.ID
    5.13 +	}
    5.14 +	if scope1.Name != scope2.Name {
    5.15 +		return false, "Name", scope1.Name, scope2.Name
    5.16 +	}
    5.17 +	if scope1.Description != scope2.Description {
    5.18 +		return false, "Description", scope1.Description, scope2.Description
    5.19 +	}
    5.20 +	return true, "", nil, nil
    5.21 +}
    5.22 +
    5.23 +func TestScopeInScopeStore(t *testing.T) {
    5.24 +	scope := Scope{
    5.25 +		ID:          "testscope",
    5.26 +		Name:        "Test Scope",
    5.27 +		Description: "Access to testing data.",
    5.28 +	}
    5.29 +	scope2 := Scope{
    5.30 +		ID:          "testscope2",
    5.31 +		Name:        "Test Scope 2",
    5.32 +		Description: "Access to minions.",
    5.33 +	}
    5.34 +	scope3 := Scope{
    5.35 +		ID:          "testscope3",
    5.36 +		Name:        "Test Scope 3",
    5.37 +		Description: "Access to bananas.",
    5.38 +	}
    5.39 +	updatedName := "Updated Scope"
    5.40 +	updatedDescription := "An updated scope."
    5.41 +	update := ScopeChange{
    5.42 +		ID:   scope.ID,
    5.43 +		Name: &updatedName,
    5.44 +	}
    5.45 +	update2 := ScopeChange{
    5.46 +		ID:          scope2.ID,
    5.47 +		Description: &updatedDescription,
    5.48 +	}
    5.49 +	update3 := ScopeChange{
    5.50 +		ID:          scope3.ID,
    5.51 +		Name:        &updatedName,
    5.52 +		Description: &updatedDescription,
    5.53 +	}
    5.54 +	for _, store := range scopeStores {
    5.55 +		context := Context{scopes: store}
    5.56 +		retrieved, err := context.GetScopes([]string{scope.ID})
    5.57 +		if len(retrieved) != 0 {
    5.58 +			t.Logf("%+v", retrieved)
    5.59 +			t.Errorf("Expected %d results, got %d from %T", 0, len(retrieved), store)
    5.60 +		}
    5.61 +		if e, ok := err.(ErrScopeNotFound); !ok {
    5.62 +			t.Errorf("Expected ErrScopeNotFound, got %+v instead for %T", err, store)
    5.63 +		} else {
    5.64 +			if e.Pos != 0 {
    5.65 +				t.Errorf("Expected the error to be in position %d, got position %d from %T", 0, e.Pos, store)
    5.66 +			}
    5.67 +			if e.ID != scope.ID {
    5.68 +				t.Errorf("Expected the error to be with scope %s, got %s from %T", scope.ID, e.ID, store)
    5.69 +			}
    5.70 +		}
    5.71 +		err = context.CreateScopes([]Scope{scope})
    5.72 +		if err != nil {
    5.73 +			t.Errorf("Error saving scope to %T: %s", store, err)
    5.74 +		}
    5.75 +		err = context.CreateScopes([]Scope{scope})
    5.76 +		if e, ok := err.(ErrScopeAlreadyExists); !ok {
    5.77 +			t.Errorf("Expected ErrScopeAlreadyExists, got %s instead for %T", err, store)
    5.78 +		} else {
    5.79 +			if e.Pos != 0 {
    5.80 +				t.Errorf("Expected the error to be in position %d, got position %d from %T", 0, e.Pos, store)
    5.81 +			}
    5.82 +			if e.ID != scope.ID {
    5.83 +				t.Errorf("Expected the error to be for ID %s, got %s from %T", scope.ID, e.ID, store)
    5.84 +			}
    5.85 +		}
    5.86 +		retrieved, err = context.GetScopes([]string{scope.ID})
    5.87 +		if err != nil {
    5.88 +			t.Errorf("Unexpected error retrieving scopes from %T: %+v", store, err)
    5.89 +		}
    5.90 +		if len(retrieved) != 1 {
    5.91 +			t.Logf("%+v", retrieved)
    5.92 +			t.Errorf("Expected %d results, got %d from %T", 1, len(retrieved), store)
    5.93 +		}
    5.94 +		success, field, val1, val2 := compareScopes(scope, retrieved[0])
    5.95 +		if !success {
    5.96 +			t.Errorf("Expected %s to be %+v, got %+v from %T", field, val1, val2, store)
    5.97 +		}
    5.98 +		err = context.CreateScopes([]Scope{scope2, scope3})
    5.99 +		if err != nil {
   5.100 +			t.Errorf("Unexpected error trying to create scope2 and scope3 in %T: %+v", store, err)
   5.101 +		}
   5.102 +		retrieved, err = context.GetScopes([]string{scope.ID, scope2.ID, scope3.ID})
   5.103 +		if err != nil {
   5.104 +			t.Errorf("Unexpected error retrieving scopes from  %T: %+v", store, err)
   5.105 +		}
   5.106 +		if len(retrieved) != 3 {
   5.107 +			t.Logf("%+v", retrieved)
   5.108 +			t.Errorf("Expected %d results, got %d from %T", 3, len(retrieved), store)
   5.109 +		}
   5.110 +		for pos, s := range []Scope{scope, scope2, scope3} {
   5.111 +			success, field, val1, val2 = compareScopes(s, retrieved[pos])
   5.112 +			if !success {
   5.113 +				t.Errorf("Expected %s to be %+v for scope %s, got %+v from %T", field, val1, s.ID, val2, store)
   5.114 +			}
   5.115 +		}
   5.116 +		updated, err := context.UpdateScopes([]ScopeChange{update, update2, update3})
   5.117 +		if err != nil {
   5.118 +			t.Errorf("Unexpected error updating scopes in %T: %+v", store, err)
   5.119 +		}
   5.120 +		if len(updated) != 3 {
   5.121 +			t.Logf("%+v", updated)
   5.122 +			t.Errorf("Expected %d results, got %d from %T", 3, len(updated), store)
   5.123 +		}
   5.124 +		scope.ApplyChange(update)
   5.125 +		scope2.ApplyChange(update2)
   5.126 +		scope3.ApplyChange(update3)
   5.127 +		for pos, s := range []Scope{scope, scope2, scope3} {
   5.128 +			success, field, val1, val2 = compareScopes(s, updated[pos])
   5.129 +			if !success {
   5.130 +				t.Errorf("Expected %s to be %+v for scope %s, got %+v from %T", field, val1, s.ID, val2, store)
   5.131 +			}
   5.132 +		}
   5.133 +		retrieved, err = context.ListScopes()
   5.134 +		if err != nil {
   5.135 +			t.Errorf("Unexpected error retrieving scopes from %T: %+v", store, err)
   5.136 +		}
   5.137 +		if len(retrieved) != 3 {
   5.138 +			t.Logf("%+v", retrieved)
   5.139 +			t.Errorf("Expected %d results, got %d from %T", 3, len(retrieved), store)
   5.140 +		}
   5.141 +		for pos, s := range []Scope{scope, scope2, scope3} {
   5.142 +			success, field, val1, val2 = compareScopes(s, retrieved[pos])
   5.143 +			if !success {
   5.144 +				t.Errorf("Expected %s to be %+v for scope %s, got $+v from %T", field, val1, s.ID, val2, store)
   5.145 +			}
   5.146 +		}
   5.147 +		err = context.RemoveScopes([]string{scope.ID, scope2.ID, scope3.ID})
   5.148 +		if err != nil {
   5.149 +			t.Errorf("Unexpected error removing scopes from %T: %+v", store, err)
   5.150 +		}
   5.151 +		retrieved, err = context.ListScopes()
   5.152 +		if err != nil {
   5.153 +			t.Errorf("Unexpected error retrieving scopes from %T: %+v", store, err)
   5.154 +		}
   5.155 +		if len(retrieved) != 0 {
   5.156 +			t.Logf("%+v", retrieved)
   5.157 +			t.Errorf("Expected %d results, got %d from %T", 0, len(retrieved), store)
   5.158 +		}
   5.159 +		err = context.RemoveScopes([]string{scope.ID})
   5.160 +		if err == nil {
   5.161 +			t.Errorf("No error returned removing non-existent scopes from %T", store)
   5.162 +		}
   5.163 +		if e, ok := err.(ErrScopeNotFound); !ok {
   5.164 +			t.Errorf("Unexpected error removing non-existent scopes from %T: %+v", store, err)
   5.165 +		} else {
   5.166 +			if e.Pos != 0 {
   5.167 +				t.Errorf("Expected error to be for scope ID in pos %d, but got %d from %T", 0, e.Pos, store)
   5.168 +			}
   5.169 +			if e.ID != scope.ID {
   5.170 +				t.Errorf("Expected error to be for scope ID %s, but got %s from %T", scope.ID, e.ID, store)
   5.171 +			}
   5.172 +		}
   5.173 +		updated, err = context.UpdateScopes([]ScopeChange{update})
   5.174 +		if err == nil {
   5.175 +			t.Errorf("No error returned updating non-existent scopes from %T", store)
   5.176 +		}
   5.177 +		if e, ok := err.(ErrScopeNotFound); !ok {
   5.178 +			t.Errorf("Unexpected error updating non-existent scopes from %T: %+v", store, err)
   5.179 +		} else {
   5.180 +			if e.Pos != 0 {
   5.181 +				t.Errorf("Expected error to be for scope ID in pos %d, but got %d from %T", 0, e.Pos, store)
   5.182 +			}
   5.183 +			if e.ID != scope.ID {
   5.184 +				t.Errorf("Expected error to be for scope ID %s, but got %s from %T", scope.ID, e.ID, store)
   5.185 +			}
   5.186 +		}
   5.187 +	}
   5.188 +}
   5.189 +
   5.190 +func TestScopeErrors(t *testing.T) {
   5.191 +	errors := map[string]error{
   5.192 +		"scope test couldn't be found": ErrScopeNotFound{ID: "test", Pos: 0},
   5.193 +		"scope test already exists":    ErrScopeAlreadyExists{ID: "test", Pos: 0},
   5.194 +	}
   5.195 +	for expectation, e := range errors {
   5.196 +		if e.Error() != expectation {
   5.197 +			t.Errorf("Expected %+v to produce '%s', produced '%s'", e, expectation, e.Error())
   5.198 +		}
   5.199 +	}
   5.200 +}