auth

Paddy 2015-04-11 Parent:3e8964a914ef Child:73e12d5a1124

160:48200d8c4036 Go to Latest

auth/scope_postgres.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@152 1 package auth
paddy@152 2
paddy@152 3 import (
paddy@153 4 "github.com/lib/pq"
paddy@152 5 "github.com/secondbit/pan"
paddy@152 6 )
paddy@152 7
paddy@152 8 func (s Scope) GetSQLTableName() string {
paddy@152 9 return "scopes"
paddy@152 10 }
paddy@152 11
paddy@152 12 func (p *postgres) createScopesSQL(scopes []Scope) *pan.Query {
paddy@152 13 fields, _ := pan.GetFields(scopes[0])
paddy@152 14 query := pan.New(pan.POSTGRES, "INSERT INTO "+pan.GetTableName(scopes[0]))
paddy@152 15 query.Include("(" + pan.QueryList(fields) + ")")
paddy@152 16 query.Include("VALUES")
paddy@153 17 query.FlushExpressions(" ")
paddy@152 18 for _, scope := range scopes {
paddy@152 19 _, values := pan.GetFields(scope)
paddy@152 20 query.Include("("+pan.VariableList(len(values))+")", values...)
paddy@152 21 }
paddy@153 22 return query.FlushExpressions(", ")
paddy@152 23 }
paddy@152 24
paddy@152 25 func (p *postgres) createScopes(scopes []Scope) error {
paddy@152 26 if len(scopes) < 1 {
paddy@152 27 return nil
paddy@152 28 }
paddy@152 29 query := p.createScopesSQL(scopes)
paddy@152 30 _, err := p.db.Exec(query.String(), query.Args...)
paddy@153 31 if e, ok := err.(*pq.Error); ok && e.Constraint == "scopes_pkey" {
paddy@153 32 err = ErrScopeAlreadyExists
paddy@153 33 }
paddy@152 34 return err
paddy@152 35 }
paddy@152 36
paddy@152 37 func (p *postgres) getScopesSQL(ids []string) *pan.Query {
paddy@152 38 var scope Scope
paddy@152 39 intids := make([]interface{}, len(ids))
paddy@152 40 for pos, id := range ids {
paddy@152 41 intids[pos] = id
paddy@152 42 }
paddy@152 43 fields, _ := pan.GetFields(scope)
paddy@152 44 query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(scope))
paddy@152 45 query.IncludeWhere()
paddy@152 46 query.Include(pan.GetUnquotedColumn(scope, "ID") + " IN")
paddy@152 47 query.Include("("+pan.VariableList(len(ids))+")", intids...)
paddy@152 48 return query.FlushExpressions(" ")
paddy@152 49 }
paddy@152 50
paddy@152 51 func (p *postgres) getScopes(ids []string) ([]Scope, error) {
paddy@152 52 query := p.getScopesSQL(ids)
paddy@152 53 rows, err := p.db.Query(query.String(), query.Args...)
paddy@152 54 if err != nil {
paddy@152 55 return []Scope{}, err
paddy@152 56 }
paddy@152 57 var scopes []Scope
paddy@152 58 for rows.Next() {
paddy@152 59 var scope Scope
paddy@152 60 err := pan.Unmarshal(rows, &scope)
paddy@152 61 if err != nil {
paddy@152 62 return scopes, err
paddy@152 63 }
paddy@152 64 scopes = append(scopes, scope)
paddy@152 65 }
paddy@152 66 if err = rows.Err(); err != nil {
paddy@152 67 return scopes, err
paddy@152 68 }
paddy@152 69 return scopes, nil
paddy@152 70 }
paddy@152 71
paddy@152 72 func (p *postgres) updateScopeSQL(id string, change ScopeChange) *pan.Query {
paddy@152 73 var scope Scope
paddy@152 74 query := pan.New(pan.POSTGRES, "UPDATE "+pan.GetTableName(scope)+" SET ")
paddy@152 75 query.IncludeIfNotNil(pan.GetUnquotedColumn(scope, "Name")+" = ?", change.Name)
paddy@152 76 query.IncludeIfNotNil(pan.GetUnquotedColumn(scope, "Description")+" = ?", change.Description)
paddy@152 77 query.FlushExpressions(", ")
paddy@152 78 query.IncludeWhere()
paddy@152 79 query.Include(pan.GetUnquotedColumn(scope, "ID")+" = ?", id)
paddy@152 80 return query.FlushExpressions(" ")
paddy@152 81 }
paddy@152 82
paddy@152 83 func (p *postgres) updateScope(id string, change ScopeChange) error {
paddy@152 84 if change.Empty() {
paddy@152 85 return nil
paddy@152 86 }
paddy@152 87 query := p.updateScopeSQL(id, change)
paddy@153 88 res, err := p.db.Exec(query.String(), query.Args...)
paddy@153 89 if err != nil {
paddy@153 90 return err
paddy@153 91 }
paddy@153 92 rows, err := res.RowsAffected()
paddy@153 93 if err != nil {
paddy@153 94 return err
paddy@153 95 }
paddy@153 96 if rows < 1 {
paddy@153 97 return ErrScopeNotFound
paddy@153 98 }
paddy@152 99 return err
paddy@152 100 }
paddy@152 101
paddy@152 102 func (p *postgres) removeScopesSQL(ids []string) *pan.Query {
paddy@152 103 var scope Scope
paddy@152 104 intids := make([]interface{}, len(ids))
paddy@152 105 for pos, id := range ids {
paddy@152 106 intids[pos] = id
paddy@152 107 }
paddy@152 108 query := pan.New(pan.POSTGRES, "DELETE FROM "+pan.GetTableName(scope))
paddy@152 109 query.IncludeWhere()
paddy@152 110 query.Include(pan.GetUnquotedColumn(scope, "ID") + " IN")
paddy@152 111 query.Include("("+pan.VariableList(len(ids))+")", intids...)
paddy@152 112 return query.FlushExpressions(" ")
paddy@152 113 }
paddy@152 114
paddy@152 115 func (p *postgres) removeScopes(ids []string) error {
paddy@152 116 query := p.removeScopesSQL(ids)
paddy@153 117 res, err := p.db.Exec(query.String(), query.Args...)
paddy@152 118 if err != nil {
paddy@152 119 return err
paddy@152 120 }
paddy@153 121 rows, err := res.RowsAffected()
paddy@153 122 if err != nil {
paddy@153 123 return err
paddy@153 124 }
paddy@153 125 if rows < 1 {
paddy@153 126 return ErrScopeNotFound
paddy@153 127 }
paddy@152 128 return nil
paddy@152 129 }
paddy@152 130
paddy@152 131 func (p *postgres) listScopesSQL() *pan.Query {
paddy@152 132 var scope Scope
paddy@152 133 fields, _ := pan.GetFields(scope)
paddy@152 134 query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(scope))
paddy@152 135 return query.FlushExpressions(" ")
paddy@152 136 }
paddy@152 137
paddy@152 138 func (p *postgres) listScopes() ([]Scope, error) {
paddy@152 139 query := p.listScopesSQL()
paddy@152 140 rows, err := p.db.Query(query.String(), query.Args...)
paddy@152 141 if err != nil {
paddy@152 142 return []Scope{}, err
paddy@152 143 }
paddy@152 144 var scopes []Scope
paddy@152 145 for rows.Next() {
paddy@152 146 var scope Scope
paddy@152 147 err = pan.Unmarshal(rows, &scope)
paddy@152 148 if err != nil {
paddy@152 149 return scopes, err
paddy@152 150 }
paddy@152 151 scopes = append(scopes, scope)
paddy@152 152 }
paddy@152 153 if err = rows.Err(); err != nil {
paddy@152 154 return scopes, err
paddy@152 155 }
paddy@152 156 return scopes, nil
paddy@152 157 }