auth

Paddy 2015-04-25 Parent:73e12d5a1124 Child:581c60f8dd23

164:cf1aef6eb81f Go to Latest

auth/token_postgres.go

Clean up after Client deletion, finish cleaning up after Profile deletion. 6f473576c6ae started cleaning up after Profiles when they're deleted, but didn't clean up the Clients created by that Profile. This fixes that, and also fixes a BUG note about cleaning up after a Client when it's deleted. Extend the authorizationCodeStore to have a deleteAuthorizationCodesByClientID method that will delete the AuthorizationCodes that have been granted by the Client specified by the passed ID. We also implemented this in memstore and postgres, so tests continue to pass. Extend the clientStore to have a deleteClientsByOwner method that will delete the Clients that were created by the Profile specified by the passed ID. We also implemented this in memstore and postgres, so tests continue to pass. Extend the clientStore to have a removeEndpointsByClientID method that will delete the Endpoints that belong(ed) to a the Client specified by the passed ID. We also implemented this in memstore and postgres, so tests continue to pass. Extend the tokenStore to have a revokeTokensByClientID method that will revoke all the Tokens that were granted to the Client specified by the passed ID. We also implemented this in memstore and postgres, so tests continue to pass. When listing Clients by their owner, allow setting the num argument (which controls how many to return) to 0 or lower, and using that to signal "return all Clients belonging to this owner", instead of paging. This is useful when deleting the Clients belonging to a Profile as part of the cleanup after deleting the Profile. Create a cleanUpAfterClientDeletion helper function that will delete the Endpoints and AuthorizationCodes belonging to a Client, and revoke the Tokens belonging to a Client, as part of cleaning up after a Client has been deleted. Add a check in the handler for listing Clients owned by a Profile to disallow the num argument to be lower than 1, because the API should be forced to page. Call our cleanUpAfterClientDeletion once the Client has been deleted in the appropriate handler. Fill out our Context with new methods to wrap all the new methods we're adding to our *Stores. In cleanUpAfterProfileDeletion, obtain a list of clients belonging to the owner, use our new DeleteClientsByOwner method to remove all of them, and then use the list to run our new cleanUpAfterClientDeletion function to clear away the final remnants of a Profile when it's deleted.

History
paddy@155 1 package auth
paddy@155 2
paddy@155 3 import (
paddy@155 4 "code.secondbit.org/uuid.hg"
paddy@155 5
paddy@155 6 "github.com/lib/pq"
paddy@155 7 "github.com/secondbit/pan"
paddy@155 8 )
paddy@155 9
paddy@155 10 func (t Token) GetSQLTableName() string {
paddy@155 11 return "tokens"
paddy@155 12 }
paddy@155 13
paddy@155 14 func (p *postgres) getTokenSQL(token string, refresh bool) *pan.Query {
paddy@155 15 var t Token
paddy@155 16 fields, _ := pan.GetFields(t)
paddy@155 17 query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(t))
paddy@155 18 query.IncludeWhere()
paddy@155 19 if !refresh {
paddy@155 20 query.Include(pan.GetUnquotedColumn(t, "AccessToken")+" = ?", token)
paddy@155 21 } else {
paddy@155 22 query.Include(pan.GetUnquotedColumn(t, "RefreshToken")+" = ?", token)
paddy@155 23 }
paddy@155 24 return query.FlushExpressions(" ")
paddy@155 25 }
paddy@155 26
paddy@155 27 func (p *postgres) getToken(token string, refresh bool) (Token, error) {
paddy@155 28 query := p.getTokenSQL(token, refresh)
paddy@155 29 rows, err := p.db.Query(query.String(), query.Args...)
paddy@155 30 if err != nil {
paddy@155 31 return Token{}, err
paddy@155 32 }
paddy@155 33 var t Token
paddy@155 34 var found bool
paddy@155 35 for rows.Next() {
paddy@155 36 err := pan.Unmarshal(rows, &t)
paddy@155 37 if err != nil {
paddy@155 38 return t, err
paddy@155 39 }
paddy@155 40 found = true
paddy@155 41 }
paddy@155 42 if err = rows.Err(); err != nil {
paddy@155 43 return t, err
paddy@155 44 }
paddy@155 45 if !found {
paddy@155 46 return t, ErrTokenNotFound
paddy@155 47 }
paddy@155 48 return t, nil
paddy@155 49 }
paddy@155 50
paddy@155 51 func (p *postgres) saveTokenSQL(token Token) *pan.Query {
paddy@155 52 fields, values := pan.GetFields(token)
paddy@155 53 query := pan.New(pan.POSTGRES, "INSERT INTO "+pan.GetTableName(token))
paddy@155 54 query.Include("(" + pan.QueryList(fields) + ")")
paddy@155 55 query.Include("VALUES")
paddy@155 56 query.Include("("+pan.VariableList(len(values))+")", values...)
paddy@155 57 return query.FlushExpressions(" ")
paddy@155 58 }
paddy@155 59
paddy@155 60 func (p *postgres) saveToken(token Token) error {
paddy@155 61 query := p.saveTokenSQL(token)
paddy@155 62 _, err := p.db.Exec(query.String(), query.Args...)
paddy@155 63 if e, ok := err.(*pq.Error); ok && e.Constraint == "tokens_pkey" {
paddy@155 64 err = ErrTokenAlreadyExists
paddy@155 65 }
paddy@155 66 if err != nil || len(token.Scopes) < 1 {
paddy@155 67 return err
paddy@155 68 }
paddy@155 69 return err
paddy@155 70 }
paddy@155 71
paddy@155 72 func (p *postgres) revokeTokenSQL(token string, refresh bool) *pan.Query {
paddy@155 73 var t Token
paddy@155 74 query := pan.New(pan.POSTGRES, "UPDATE "+pan.GetTableName(t)+" SET ")
paddy@155 75 query.Include(pan.GetUnquotedColumn(t, "Revoked")+" = ?", true)
paddy@155 76 query.IncludeWhere()
paddy@155 77 if !refresh {
paddy@155 78 query.Include(pan.GetUnquotedColumn(t, "AccessToken")+" = ?", token)
paddy@155 79 } else {
paddy@155 80 query.Include(pan.GetUnquotedColumn(t, "RefreshToken")+" = ?", token)
paddy@155 81 }
paddy@155 82 return query.FlushExpressions(" ")
paddy@155 83 }
paddy@155 84
paddy@155 85 func (p *postgres) revokeToken(token string, refresh bool) error {
paddy@155 86 query := p.revokeTokenSQL(token, refresh)
paddy@155 87 res, err := p.db.Exec(query.String(), query.Args...)
paddy@155 88 if err != nil {
paddy@155 89 return err
paddy@155 90 }
paddy@155 91 rows, err := res.RowsAffected()
paddy@155 92 if err != nil {
paddy@155 93 return err
paddy@155 94 }
paddy@155 95 if rows == 0 {
paddy@155 96 return ErrTokenNotFound
paddy@155 97 }
paddy@155 98 return nil
paddy@155 99 }
paddy@155 100
paddy@162 101 func (p *postgres) revokeTokensByProfileIDSQL(profileID uuid.ID) *pan.Query {
paddy@162 102 var t Token
paddy@162 103 query := pan.New(pan.POSTGRES, "UPDATE "+pan.GetTableName(t)+" SET ")
paddy@162 104 query.Include(pan.GetUnquotedColumn(t, "Revoked")+" = ?", true)
paddy@162 105 query.IncludeWhere()
paddy@162 106 query.Include(pan.GetUnquotedColumn(t, "ProfileID")+" = ?", profileID)
paddy@162 107 return query.FlushExpressions(" ")
paddy@162 108 }
paddy@162 109
paddy@162 110 func (p *postgres) revokeTokensByProfileID(profileID uuid.ID) error {
paddy@162 111 query := p.revokeTokensByProfileIDSQL(profileID)
paddy@162 112 res, err := p.db.Exec(query.String(), query.Args...)
paddy@162 113 if err != nil {
paddy@162 114 return err
paddy@162 115 }
paddy@162 116 rows, err := res.RowsAffected()
paddy@162 117 if err != nil {
paddy@162 118 return err
paddy@162 119 }
paddy@162 120 if rows == 0 {
paddy@162 121 return ErrProfileNotFound
paddy@162 122 }
paddy@162 123 return nil
paddy@162 124 }
paddy@162 125
paddy@164 126 func (p *postgres) revokeTokensByClientIDSQL(clientID uuid.ID) *pan.Query {
paddy@164 127 var t Token
paddy@164 128 query := pan.New(pan.POSTGRES, "UPDATE "+pan.GetTableName(t)+" SET ")
paddy@164 129 query.Include(pan.GetUnquotedColumn(t, "Revoked")+" = ?", true)
paddy@164 130 query.IncludeWhere()
paddy@164 131 query.Include(pan.GetUnquotedColumn(t, "ClientID")+" = ?", clientID)
paddy@164 132 return query.FlushExpressions(" ")
paddy@164 133 }
paddy@164 134
paddy@164 135 func (p *postgres) revokeTokensByClientID(clientID uuid.ID) error {
paddy@164 136 query := p.revokeTokensByClientIDSQL(clientID)
paddy@164 137 res, err := p.db.Exec(query.String(), query.Args...)
paddy@164 138 if err != nil {
paddy@164 139 return err
paddy@164 140 }
paddy@164 141 rows, err := res.RowsAffected()
paddy@164 142 if err != nil {
paddy@164 143 return err
paddy@164 144 }
paddy@164 145 if rows == 0 {
paddy@164 146 return ErrClientNotFound
paddy@164 147 }
paddy@164 148 return nil
paddy@164 149 }
paddy@164 150
paddy@155 151 func (p *postgres) getTokensByProfileIDSQL(profileID uuid.ID, num, offset int) *pan.Query {
paddy@155 152 var token Token
paddy@155 153 fields, _ := pan.GetFields(token)
paddy@155 154 query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(token))
paddy@155 155 query.IncludeWhere()
paddy@155 156 query.Include(pan.GetUnquotedColumn(token, "ProfileID")+" = ?", profileID)
paddy@155 157 query.IncludeLimit(int64(num))
paddy@155 158 query.IncludeOffset(int64(offset))
paddy@155 159 return query.FlushExpressions(" ")
paddy@155 160 }
paddy@155 161
paddy@155 162 func (p *postgres) getTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error) {
paddy@155 163 query := p.getTokensByProfileIDSQL(profileID, num, offset)
paddy@155 164 rows, err := p.db.Query(query.String(), query.Args...)
paddy@155 165 if err != nil {
paddy@155 166 return []Token{}, err
paddy@155 167 }
paddy@155 168 var tokens []Token
paddy@155 169 var tokenIDs []string
paddy@155 170 for rows.Next() {
paddy@155 171 var token Token
paddy@155 172 err = pan.Unmarshal(rows, &token)
paddy@155 173 if err != nil {
paddy@155 174 return tokens, err
paddy@155 175 }
paddy@155 176 tokens = append(tokens, token)
paddy@155 177 tokenIDs = append(tokenIDs, token.AccessToken)
paddy@155 178 }
paddy@155 179 if err = rows.Err(); err != nil {
paddy@155 180 return tokens, err
paddy@155 181 }
paddy@155 182 return tokens, nil
paddy@155 183 }