auth

Paddy 2015-04-11 Parent:e000b1c24fc0 Child:8ecb60d29b0d

160:48200d8c4036 Go to Latest

auth/request_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@116 1 package auth
paddy@116 2
paddy@116 3 import "fmt"
paddy@116 4
paddy@116 5 func compareErrors(err1, err2 requestError) (success bool, field string, val1, val2 interface{}) {
paddy@116 6 if err1.Slug != err2.Slug {
paddy@116 7 return false, "Slug", err1.Slug, err2.Slug
paddy@116 8 }
paddy@116 9 if err1.Field != err2.Field {
paddy@116 10 return false, "Field", err1.Field, err2.Field
paddy@116 11 }
paddy@116 12 if err1.Param != err2.Param {
paddy@116 13 return false, "Param", err1.Param, err2.Param
paddy@116 14 }
paddy@116 15 if err1.Header != err2.Header {
paddy@116 16 return false, "Header", err1.Header, err2.Header
paddy@116 17 }
paddy@116 18 return true, "", nil, nil
paddy@116 19 }
paddy@116 20
paddy@116 21 func compareResponses(resp1, resp2 response) (success bool, field string, val1, val2 interface{}) {
paddy@116 22 if len(resp1.Errors) != len(resp2.Errors) {
paddy@116 23 return false, "Errors", resp1.Errors, resp2.Errors
paddy@116 24 }
paddy@116 25 if len(resp1.Logins) != len(resp2.Logins) {
paddy@116 26 return false, "Logins", resp1.Logins, resp2.Logins
paddy@116 27 }
paddy@116 28 if len(resp1.Profiles) != len(resp2.Profiles) {
paddy@116 29 return false, "Profiles", resp1.Profiles, resp2.Profiles
paddy@116 30 }
paddy@116 31 if len(resp1.Clients) != len(resp2.Clients) {
paddy@116 32 return false, "Clients", resp1.Clients, resp2.Clients
paddy@116 33 }
paddy@116 34 if len(resp1.Endpoints) != len(resp2.Endpoints) {
paddy@116 35 return false, "Endpoints", resp1.Endpoints, resp2.Endpoints
paddy@116 36 }
paddy@116 37 for pos := range resp1.Errors {
paddy@116 38 success, field, val1, val2 = compareErrors(resp1.Errors[pos], resp2.Errors[pos])
paddy@116 39 if !success {
paddy@116 40 field = fmt.Sprintf("Error %d %s", pos, field)
paddy@116 41 return
paddy@116 42 }
paddy@116 43 }
paddy@116 44 for pos := range resp1.Logins {
paddy@116 45 success, field, val1, val2 = compareLogins(resp1.Logins[pos], resp2.Logins[pos])
paddy@116 46 if !success {
paddy@116 47 field = fmt.Sprintf("Login %d %s", pos, field)
paddy@116 48 return
paddy@116 49 }
paddy@116 50 }
paddy@116 51 for pos := range resp1.Profiles {
paddy@116 52 success, field, val1, val2 = compareProfiles(resp1.Profiles[pos], resp2.Profiles[pos])
paddy@116 53 if !success {
paddy@116 54 field = fmt.Sprintf("Profile %d %s", pos, field)
paddy@116 55 return
paddy@116 56 }
paddy@116 57 }
paddy@116 58 for pos := range resp1.Clients {
paddy@116 59 success, field, val1, val2 = compareClients(resp1.Clients[pos], resp2.Clients[pos])
paddy@116 60 if !success {
paddy@116 61 field = fmt.Sprintf("Client %d %s", pos, field)
paddy@116 62 return
paddy@116 63 }
paddy@116 64 }
paddy@116 65 for pos := range resp1.Endpoints {
paddy@116 66 success, field, val1, val2 = compareEndpoints(resp1.Endpoints[pos], resp2.Endpoints[pos])
paddy@116 67 if !success {
paddy@116 68 field = fmt.Sprintf("Endpoint %d %s", pos, field)
paddy@116 69 return
paddy@116 70 }
paddy@116 71 }
paddy@116 72 return true, "", nil, nil
paddy@116 73 }
paddy@116 74
paddy@116 75 func fillInServerGenerated(expectation, result response) {
paddy@116 76 if len(expectation.Profiles) > 0 {
paddy@116 77 for pos, profile := range expectation.Profiles {
paddy@116 78 profile.ID = result.Profiles[pos].ID
paddy@116 79 profile.Created = result.Profiles[pos].Created
paddy@116 80 profile.LastSeen = result.Profiles[pos].LastSeen
paddy@116 81 expectation.Profiles[pos] = profile
paddy@116 82 }
paddy@116 83 }
paddy@116 84 if len(expectation.Logins) > 0 {
paddy@116 85 for pos, login := range expectation.Logins {
paddy@116 86 login.ProfileID = result.Logins[pos].ProfileID
paddy@116 87 login.Created = result.Logins[pos].Created
paddy@116 88 login.LastUsed = result.Logins[pos].LastUsed
paddy@116 89 expectation.Logins[pos] = login
paddy@116 90 }
paddy@116 91 }
paddy@116 92 if len(expectation.Clients) > 0 {
paddy@116 93 for pos, client := range expectation.Clients {
paddy@116 94 client.ID = result.Clients[pos].ID
paddy@116 95 client.Secret = result.Clients[pos].Secret
paddy@116 96 client.OwnerID = result.Clients[pos].OwnerID
paddy@116 97 expectation.Clients[pos] = client
paddy@116 98 }
paddy@116 99 }
paddy@116 100 if len(expectation.Endpoints) > 0 {
paddy@116 101 for pos, endpoint := range expectation.Endpoints {
paddy@116 102 endpoint.ID = result.Endpoints[pos].ID
paddy@116 103 endpoint.ClientID = result.Endpoints[pos].ClientID
paddy@116 104 endpoint.Added = result.Endpoints[pos].Added
paddy@116 105 expectation.Endpoints[pos] = endpoint
paddy@116 106 }
paddy@116 107 }
paddy@116 108 }