auth
auth/scope_postgres.go
Implement postgres version of the tokenStore. Create a postgres implementation for the tokenStore. Note that because pq doesn't support Postgres' array types (see https://github.com/lib/pq/issues/49), we couldn't just store the token.Scopes field as a Postgres array of varchars, which would have been the ideal. Instead, we need a many-to-many table that maps tokens to scopes. This meant we needed a special tokenScope type for our database mapping, and we needed to complicate the token storage/retrieval functions a little bit. It's kind of ugly, I'm not a huge fan of it, and I'd much rather be using the Postgres array types, but... well, here we are. We also added the postgres tokenStore to our slice of tokenStores to test when the correct environment variables are present. We wrote initialization SQL for the tables required by the postgres tokenStore. Also, added a helper script for emptying the test database, because I got tired of doing it by hand. We should be doing that in an automated fashion in the tests themselves, but that would mean extending the *Store interfaces.
| 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 } |