auth

Paddy 2015-01-18 Parent:c03b5eb3179e Child:d14f0a81498c

123:0a1e16b9c141 Go to Latest

auth/authcode.go

Refactor verifyClient, implement refresh tokens. Refactor verifyClient into verifyClient and getClientAuth. We moved verifyClient out of each of the GrantType's validation functions and into the access token endpoint, where it will be called before the GrantType's validation function. Yay, less code repetition. And seeing as we always want to verify the client, that seems like a good way to prevent things like 118a69954621 from happening. This did, however, force us to add an AllowsPublic property to the GrantType, so the token endpoint knows whether or not a public Client is valid for any given GrantType. We also implemented the refresh token grant type, which required adding ClientID and RefreshRevoked as properties on the Token type. We need ClientID because we need to constrain refresh tokens to the client that issued them. We also should probably keep track of which tokens belong to which clients, just as a general rule of thumb. RefreshRevoked had to be created, next to Revoked, because the AccessToken could be revoked and the RefreshToken still valid, or vice versa. Notably, when you issue a new refresh token, the old one is revoked, but the access token is still valid. It remains to be seen whether this is a good way to track things or not. The number of duplicated properties lead me to believe our type is not a great representation of the underlying concepts.

History
paddy@26 1 package auth
paddy@26 2
paddy@26 3 import (
paddy@84 4 "encoding/json"
paddy@29 5 "errors"
paddy@84 6 "net/http"
paddy@26 7 "time"
paddy@26 8
paddy@107 9 "code.secondbit.org/uuid.hg"
paddy@26 10 )
paddy@26 11
paddy@84 12 func init() {
paddy@84 13 RegisterGrantType("authorization_code", GrantType{
paddy@84 14 Validate: authCodeGrantValidate,
paddy@94 15 Invalidate: authCodeGrantInvalidate,
paddy@84 16 IssuesRefresh: true,
paddy@85 17 ReturnToken: RenderJSONToken,
paddy@123 18 AllowsPublic: true,
paddy@84 19 })
paddy@84 20 }
paddy@84 21
paddy@29 22 var (
paddy@87 23 // ErrNoAuthorizationCodeStore is returned when a Context tries to act on a authorizationCodeStore without setting one first.
paddy@87 24 ErrNoAuthorizationCodeStore = errors.New("no authorizationCodeStore was specified for the Context")
paddy@87 25 // ErrAuthorizationCodeNotFound is returned when an AuthorizationCode is requested but not found in the authorizationCodeStore.
paddy@87 26 ErrAuthorizationCodeNotFound = errors.New("authorization code not found in authorizationCodeStore")
paddy@87 27 // ErrAuthorizationCodeAlreadyExists is returned when an AuthorizationCode is added to a authorizationCodeStore, but another AuthorizationCode with the
paddy@87 28 // same Code already exists in the authorizationCodeStore.
paddy@87 29 ErrAuthorizationCodeAlreadyExists = errors.New("authorization code already exists in authorizationCodeStore")
paddy@29 30 )
paddy@29 31
paddy@87 32 // AuthorizationCode represents an authorization grant made by a user to a Client, to
paddy@57 33 // access user data within a defined Scope for a limited amount of time.
paddy@87 34 type AuthorizationCode struct {
paddy@26 35 Code string
paddy@26 36 Created time.Time
paddy@26 37 ExpiresIn int32
paddy@26 38 ClientID uuid.ID
paddy@26 39 Scope string
paddy@26 40 RedirectURI string
paddy@26 41 State string
paddy@69 42 ProfileID uuid.ID
paddy@94 43 Used bool
paddy@26 44 }
paddy@26 45
paddy@87 46 type authorizationCodeStore interface {
paddy@87 47 getAuthorizationCode(code string) (AuthorizationCode, error)
paddy@87 48 saveAuthorizationCode(authCode AuthorizationCode) error
paddy@87 49 deleteAuthorizationCode(code string) error
paddy@94 50 useAuthorizationCode(code string) error
paddy@26 51 }
paddy@29 52
paddy@87 53 func (m *memstore) getAuthorizationCode(code string) (AuthorizationCode, error) {
paddy@87 54 m.authCodeLock.RLock()
paddy@87 55 defer m.authCodeLock.RUnlock()
paddy@87 56 authCode, ok := m.authCodes[code]
paddy@29 57 if !ok {
paddy@87 58 return AuthorizationCode{}, ErrAuthorizationCodeNotFound
paddy@29 59 }
paddy@87 60 return authCode, nil
paddy@29 61 }
paddy@29 62
paddy@87 63 func (m *memstore) saveAuthorizationCode(authCode AuthorizationCode) error {
paddy@87 64 m.authCodeLock.Lock()
paddy@87 65 defer m.authCodeLock.Unlock()
paddy@87 66 _, ok := m.authCodes[authCode.Code]
paddy@29 67 if ok {
paddy@87 68 return ErrAuthorizationCodeAlreadyExists
paddy@29 69 }
paddy@87 70 m.authCodes[authCode.Code] = authCode
paddy@29 71 return nil
paddy@29 72 }
paddy@29 73
paddy@87 74 func (m *memstore) deleteAuthorizationCode(code string) error {
paddy@87 75 m.authCodeLock.Lock()
paddy@87 76 defer m.authCodeLock.Unlock()
paddy@87 77 _, ok := m.authCodes[code]
paddy@29 78 if !ok {
paddy@87 79 return ErrAuthorizationCodeNotFound
paddy@29 80 }
paddy@87 81 delete(m.authCodes, code)
paddy@29 82 return nil
paddy@29 83 }
paddy@84 84
paddy@94 85 func (m *memstore) useAuthorizationCode(code string) error {
paddy@94 86 m.authCodeLock.Lock()
paddy@94 87 defer m.authCodeLock.Unlock()
paddy@94 88 a, ok := m.authCodes[code]
paddy@94 89 if !ok {
paddy@94 90 return ErrAuthorizationCodeNotFound
paddy@94 91 }
paddy@94 92 a.Used = true
paddy@94 93 m.authCodes[code] = a
paddy@94 94 return nil
paddy@94 95 }
paddy@94 96
paddy@84 97 func authCodeGrantValidate(w http.ResponseWriter, r *http.Request, context Context) (scope string, profileID uuid.ID, valid bool) {
paddy@84 98 enc := json.NewEncoder(w)
paddy@84 99 code := r.PostFormValue("code")
paddy@84 100 if code == "" {
paddy@84 101 w.WriteHeader(http.StatusBadRequest)
paddy@84 102 renderJSONError(enc, "invalid_request")
paddy@84 103 return
paddy@84 104 }
paddy@123 105 clientID, _, ok := getClientAuth(w, r, true)
paddy@123 106 if !ok {
paddy@84 107 return
paddy@84 108 }
paddy@87 109 authCode, err := context.GetAuthorizationCode(code)
paddy@84 110 if err != nil {
paddy@87 111 if err == ErrAuthorizationCodeNotFound {
paddy@84 112 w.WriteHeader(http.StatusBadRequest)
paddy@84 113 renderJSONError(enc, "invalid_grant")
paddy@84 114 return
paddy@84 115 }
paddy@84 116 w.WriteHeader(http.StatusInternalServerError)
paddy@84 117 renderJSONError(enc, "server_error")
paddy@84 118 return
paddy@84 119 }
paddy@85 120 redirectURI := r.PostFormValue("redirect_uri")
paddy@87 121 if authCode.RedirectURI != redirectURI {
paddy@84 122 w.WriteHeader(http.StatusBadRequest)
paddy@84 123 renderJSONError(enc, "invalid_grant")
paddy@84 124 return
paddy@84 125 }
paddy@87 126 if !authCode.ClientID.Equal(clientID) {
paddy@84 127 w.WriteHeader(http.StatusBadRequest)
paddy@84 128 renderJSONError(enc, "invalid_grant")
paddy@84 129 return
paddy@84 130 }
paddy@87 131 return authCode.Scope, authCode.ProfileID, true
paddy@84 132 }
paddy@90 133
paddy@90 134 func authCodeGrantInvalidate(r *http.Request, context Context) error {
paddy@94 135 code := r.PostFormValue("code")
paddy@94 136 if code == "" {
paddy@94 137 return ErrAuthorizationCodeNotFound
paddy@94 138 }
paddy@94 139 return context.UseAuthorizationCode(code)
paddy@90 140 }