auth

Paddy 2015-04-11 Parent:3e8964a914ef

160:48200d8c4036 Go to Latest

auth/scope_test.go

Start to support deleting profiles through the API. Create a removeLoginsByProfile method on the profileStore, to allow an easy way to bulk-delete logins associated with a Profile after the Profile has been deleted. Create postgres and memstore implementations of the removeLoginsByProfile method. Create a cleanUpAfterProfileDeletion helper method that will clean up the child objects of a Profile (its Sessions, Tokens, Clients, etc.). The intended usage is to call this in a goroutine after a Profile has been deleted, to try and get things back in order. Detect when the UpdateProfileHandler API is used to set the Deleted flag of a Profile to true, and clean up after the Profile when that's the case. Add a DeleteProfileHandler API endpoint that is a shortcut to setting the Deleted flag of a Profile to true and cleaning up after the Profile. The problem with our approach thus far is that some of it is reversible and some is not. If a Profile is maliciously/accidentally deleted, it's simple enough to use the API as a superuser to restore the Profile. But doing that will not (and cannot) restore the Logins associated with that Profile, for example. While it would be nice to add a Deleted flag to our Logins that we could simply toggle, that would wreak havoc with our database constraints and ensuring uniqueness of Login values. I still don't have a solution for this, outside the superuser manually restoring a Login for the Profile, after which the user can authenticate themselves and add more Logins as desired. But there has to be a better way. I suppose since the passphrase is being stored with the Profile and not the Login, we could offer an endpoint that would automate this, but... well, that would be tricky. It would require the user remembering their Profile ID, and let's be honest, nobody's going to remember a UUID. Maybe such an endpoint would help from a customer service standpoint: we identify their Profile manually, then send them to /profiles/ID/restorelogin or something, and that lets them add a Login back to the Profile. I'll figure it out later. For now, we know we at least have enough information to identify a user is who they say they are and resolve the situation manually.

History
paddy@134 1 package auth
paddy@134 2
paddy@153 3 import "os"
paddy@134 4 import "testing"
paddy@134 5
paddy@153 6 func init() {
paddy@153 7 if os.Getenv("PG_TEST_DB") != "" {
paddy@153 8 p, err := NewPostgres(os.Getenv("PG_TEST_DB"))
paddy@153 9 if err != nil {
paddy@153 10 panic(err)
paddy@153 11 }
paddy@153 12 scopeStores = append(scopeStores, &p)
paddy@153 13 }
paddy@153 14 }
paddy@153 15
paddy@134 16 var scopeStores = []scopeStore{NewMemstore()}
paddy@134 17
paddy@134 18 func compareScopes(scope1, scope2 Scope) (success bool, field string, val1, val2 interface{}) {
paddy@134 19 if scope1.ID != scope2.ID {
paddy@134 20 return false, "ID", scope1.ID, scope2.ID
paddy@134 21 }
paddy@134 22 if scope1.Name != scope2.Name {
paddy@134 23 return false, "Name", scope1.Name, scope2.Name
paddy@134 24 }
paddy@134 25 if scope1.Description != scope2.Description {
paddy@134 26 return false, "Description", scope1.Description, scope2.Description
paddy@134 27 }
paddy@134 28 return true, "", nil, nil
paddy@134 29 }
paddy@134 30
paddy@134 31 func TestScopeInScopeStore(t *testing.T) {
paddy@134 32 scope := Scope{
paddy@134 33 ID: "testscope",
paddy@134 34 Name: "Test Scope",
paddy@134 35 Description: "Access to testing data.",
paddy@134 36 }
paddy@134 37 scope2 := Scope{
paddy@134 38 ID: "testscope2",
paddy@134 39 Name: "Test Scope 2",
paddy@134 40 Description: "Access to minions.",
paddy@134 41 }
paddy@134 42 scope3 := Scope{
paddy@134 43 ID: "testscope3",
paddy@134 44 Name: "Test Scope 3",
paddy@134 45 Description: "Access to bananas.",
paddy@134 46 }
paddy@134 47 updatedName := "Updated Scope"
paddy@134 48 updatedDescription := "An updated scope."
paddy@134 49 update := ScopeChange{
paddy@134 50 Name: &updatedName,
paddy@134 51 }
paddy@134 52 update2 := ScopeChange{
paddy@134 53 Description: &updatedDescription,
paddy@134 54 }
paddy@134 55 update3 := ScopeChange{
paddy@134 56 Name: &updatedName,
paddy@134 57 Description: &updatedDescription,
paddy@134 58 }
paddy@134 59 for _, store := range scopeStores {
paddy@134 60 context := Context{scopes: store}
paddy@134 61 retrieved, err := context.GetScopes([]string{scope.ID})
paddy@134 62 if len(retrieved) != 0 {
paddy@134 63 t.Logf("%+v", retrieved)
paddy@134 64 t.Errorf("Expected %d results, got %d from %T", 0, len(retrieved), store)
paddy@134 65 }
paddy@134 66 err = context.CreateScopes([]Scope{scope})
paddy@134 67 if err != nil {
paddy@134 68 t.Errorf("Error saving scope to %T: %s", store, err)
paddy@134 69 }
paddy@134 70 err = context.CreateScopes([]Scope{scope})
paddy@153 71 if err != ErrScopeAlreadyExists {
paddy@134 72 t.Errorf("Expected ErrScopeAlreadyExists, got %s instead for %T", err, store)
paddy@134 73 }
paddy@134 74 retrieved, err = context.GetScopes([]string{scope.ID})
paddy@134 75 if err != nil {
paddy@134 76 t.Errorf("Unexpected error retrieving scopes from %T: %+v", store, err)
paddy@134 77 }
paddy@134 78 if len(retrieved) != 1 {
paddy@134 79 t.Logf("%+v", retrieved)
paddy@134 80 t.Errorf("Expected %d results, got %d from %T", 1, len(retrieved), store)
paddy@134 81 }
paddy@134 82 success, field, val1, val2 := compareScopes(scope, retrieved[0])
paddy@134 83 if !success {
paddy@134 84 t.Errorf("Expected %s to be %+v, got %+v from %T", field, val1, val2, store)
paddy@134 85 }
paddy@134 86 err = context.CreateScopes([]Scope{scope2, scope3})
paddy@134 87 if err != nil {
paddy@134 88 t.Errorf("Unexpected error trying to create scope2 and scope3 in %T: %+v", store, err)
paddy@134 89 }
paddy@134 90 retrieved, err = context.GetScopes([]string{scope.ID, scope2.ID, scope3.ID})
paddy@134 91 if err != nil {
paddy@134 92 t.Errorf("Unexpected error retrieving scopes from %T: %+v", store, err)
paddy@134 93 }
paddy@134 94 if len(retrieved) != 3 {
paddy@134 95 t.Logf("%+v", retrieved)
paddy@134 96 t.Errorf("Expected %d results, got %d from %T", 3, len(retrieved), store)
paddy@134 97 }
paddy@134 98 for pos, s := range []Scope{scope, scope2, scope3} {
paddy@134 99 success, field, val1, val2 = compareScopes(s, retrieved[pos])
paddy@134 100 if !success {
paddy@134 101 t.Errorf("Expected %s to be %+v for scope %s, got %+v from %T", field, val1, s.ID, val2, store)
paddy@134 102 }
paddy@134 103 }
paddy@152 104 err = context.UpdateScope(scope.ID, update)
paddy@134 105 if err != nil {
paddy@152 106 t.Errorf("Unexpected error updating scope in %T: %+v", store, err)
paddy@134 107 }
paddy@134 108 scope.ApplyChange(update)
paddy@152 109 err = context.UpdateScope(scope2.ID, update2)
paddy@152 110 if err != nil {
paddy@152 111 t.Errorf("Unexpected error updating scope in %T: %+v", store, err)
paddy@152 112 }
paddy@134 113 scope2.ApplyChange(update2)
paddy@152 114 err = context.UpdateScope(scope3.ID, update3)
paddy@152 115 if err != nil {
paddy@152 116 t.Errorf("Unexpected error updating scope in %T: %+v", store, err)
paddy@152 117 }
paddy@134 118 scope3.ApplyChange(update3)
paddy@134 119 retrieved, err = context.ListScopes()
paddy@134 120 if err != nil {
paddy@134 121 t.Errorf("Unexpected error retrieving scopes from %T: %+v", store, err)
paddy@134 122 }
paddy@134 123 if len(retrieved) != 3 {
paddy@134 124 t.Logf("%+v", retrieved)
paddy@134 125 t.Errorf("Expected %d results, got %d from %T", 3, len(retrieved), store)
paddy@134 126 }
paddy@134 127 for pos, s := range []Scope{scope, scope2, scope3} {
paddy@134 128 success, field, val1, val2 = compareScopes(s, retrieved[pos])
paddy@134 129 if !success {
paddy@136 130 t.Errorf("Expected %s to be %+v for scope %s, got %+v from %T", field, val1, s.ID, val2, store)
paddy@134 131 }
paddy@134 132 }
paddy@134 133 err = context.RemoveScopes([]string{scope.ID, scope2.ID, scope3.ID})
paddy@134 134 if err != nil {
paddy@134 135 t.Errorf("Unexpected error removing scopes from %T: %+v", store, err)
paddy@134 136 }
paddy@134 137 retrieved, err = context.ListScopes()
paddy@134 138 if err != nil {
paddy@134 139 t.Errorf("Unexpected error retrieving scopes from %T: %+v", store, err)
paddy@134 140 }
paddy@134 141 if len(retrieved) != 0 {
paddy@134 142 t.Logf("%+v", retrieved)
paddy@134 143 t.Errorf("Expected %d results, got %d from %T", 0, len(retrieved), store)
paddy@134 144 }
paddy@134 145 err = context.RemoveScopes([]string{scope.ID})
paddy@134 146 if err == nil {
paddy@134 147 t.Errorf("No error returned removing non-existent scopes from %T", store)
paddy@134 148 }
paddy@153 149 if err != ErrScopeNotFound {
paddy@134 150 t.Errorf("Unexpected error removing non-existent scopes from %T: %+v", store, err)
paddy@134 151 }
paddy@152 152 err = context.UpdateScope(scope.ID, update)
paddy@153 153 if err != ErrScopeNotFound {
paddy@134 154 t.Errorf("Unexpected error updating non-existent scopes from %T: %+v", store, err)
paddy@134 155 }
paddy@134 156 }
paddy@134 157 }