auth
2014-09-01
auth/access.go.old
Deprecate old implementations. Let's remove all of the osin stuff altogether, in favour of a more testable, unit-based approach. Leave all the old files around, for easy reference, but add the .old suffix so the go tools don't pick them up.
| paddy@23 | 1 package auth |
| paddy@23 | 2 |
| paddy@23 | 3 import ( |
| paddy@23 | 4 "net/http" |
| paddy@23 | 5 "net/url" |
| paddy@23 | 6 "time" |
| paddy@23 | 7 |
| paddy@23 | 8 "strconv" |
| paddy@23 | 9 "secondbit.org/uuid" |
| paddy@23 | 10 ) |
| paddy@23 | 11 |
| paddy@23 | 12 // GrantType is the type for OAuth param `grant_type` |
| paddy@23 | 13 type GrantType string |
| paddy@23 | 14 |
| paddy@23 | 15 const ( |
| paddy@23 | 16 AuthorizationCodeGrant GrantType = "authorization_code" |
| paddy@23 | 17 RefreshTokenGrant = "refresh_token" |
| paddy@23 | 18 PasswordGrant = "password" |
| paddy@23 | 19 ClientCredentialsGrant = "client_credentials" |
| paddy@23 | 20 ) |
| paddy@23 | 21 |
| paddy@23 | 22 // AccessData represents an access grant (tokens, expiration, client, etc) |
| paddy@23 | 23 type AccessData struct { |
| paddy@23 | 24 PreviousAuthorizeData *AuthorizeData `json:"-"` |
| paddy@23 | 25 PreviousAccessData *AccessData `json:"-"` // previous access data, when refreshing |
| paddy@23 | 26 AccessToken string `json:"access_token"` |
| paddy@23 | 27 RefreshToken string `json:"refresh_token,omitempty"` |
| paddy@23 | 28 ExpiresIn int32 `json:"expires_in"` |
| paddy@23 | 29 CreatedAt time.Time `json:"-"` |
| paddy@23 | 30 TokenType string `json:"token_type"` |
| paddy@23 | 31 Scope string `json:"scope,omitempty"` |
| paddy@23 | 32 ProfileID uuid.ID `json:"-"` |
| paddy@23 | 33 AuthRequest `json:"-"` |
| paddy@23 | 34 } |
| paddy@23 | 35 |
| paddy@23 | 36 // IsExpired returns true if access expired |
| paddy@23 | 37 func (d *AccessData) IsExpired() bool { |
| paddy@23 | 38 return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second).Before(time.Now()) |
| paddy@23 | 39 } |
| paddy@23 | 40 |
| paddy@23 | 41 // ExpireAt returns the expiration date |
| paddy@23 | 42 func (d *AccessData) ExpireAt() time.Time { |
| paddy@23 | 43 return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second) |
| paddy@23 | 44 } |
| paddy@23 | 45 |
| paddy@23 | 46 // HandleOAuth2AccessRequest is the http.HandlerFunc for handling access token requests. |
| paddy@23 | 47 func HandleOAuth2AccessRequest(w http.ResponseWriter, r *http.Request, ctx Context) { |
| paddy@23 | 48 // Only allow GET or POST |
| paddy@23 | 49 if r.Method != "POST" { |
| paddy@23 | 50 if r.Method != "GET" || !ctx.Config.AllowGetAccessRequest { |
| paddy@23 | 51 ctx.RenderJSONError(w, ErrorInvalidRequest, "Invalid request method.", ctx.Config.DocumentationDomain) |
| paddy@23 | 52 return |
| paddy@23 | 53 } |
| paddy@23 | 54 } |
| paddy@23 | 55 |
| paddy@23 | 56 grantType := GrantType(r.Form.Get("grant_type")) |
| paddy@23 | 57 if ctx.Config.AllowedAccessTypes.Exists(grantType) { |
| paddy@23 | 58 switch grantType { |
| paddy@23 | 59 case AuthorizationCodeGrant: |
| paddy@23 | 60 handleAuthorizationCodeRequest(w, r, ctx) |
| paddy@23 | 61 case RefreshTokenGrant: |
| paddy@23 | 62 handleRefreshTokenRequest(w, r, ctx) |
| paddy@23 | 63 case PasswordGrant: |
| paddy@23 | 64 handlePasswordRequest(w, r, ctx) |
| paddy@23 | 65 case ClientCredentialsGrant: |
| paddy@23 | 66 handleClientCredentialsRequest(w, r, ctx) |
| paddy@23 | 67 default: |
| paddy@23 | 68 ctx.RenderJSONError(w, ErrorUnsupportedGrantType, "Unsupported grant type.", ctx.Config.DocumentationDomain) |
| paddy@23 | 69 return |
| paddy@23 | 70 } |
| paddy@23 | 71 } |
| paddy@23 | 72 } |
| paddy@23 | 73 |
| paddy@23 | 74 func handleAuthorizationCodeRequest(w http.ResponseWriter, r *http.Request, ctx Context) { |
| paddy@23 | 75 // get client authentication |
| paddy@23 | 76 auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams) |
| paddy@23 | 77 if err != nil { |
| paddy@23 | 78 ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) |
| paddy@23 | 79 return |
| paddy@23 | 80 } |
| paddy@23 | 81 |
| paddy@23 | 82 code := r.Form.Get("code") |
| paddy@23 | 83 // "code" is required |
| paddy@23 | 84 if code == "" { |
| paddy@23 | 85 ctx.RenderJSONError(w, ErrorInvalidRequest, "Code must be supplied.", ctx.Config.DocumentationDomain) |
| paddy@23 | 86 return |
| paddy@23 | 87 } |
| paddy@23 | 88 |
| paddy@23 | 89 // must have a valid client |
| paddy@23 | 90 client, err := getClient(auth, ctx) |
| paddy@23 | 91 if err != nil { |
| paddy@23 | 92 if err == ClientNotFoundError || err == InvalidClientError { |
| paddy@23 | 93 ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) |
| paddy@23 | 94 return |
| paddy@23 | 95 } |
| paddy@23 | 96 ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) |
| paddy@23 | 97 return |
| paddy@23 | 98 } |
| paddy@23 | 99 |
| paddy@23 | 100 // must be a valid authorization code |
| paddy@23 | 101 authData, err := ctx.Tokens.GetAuthorization(code) |
| paddy@23 | 102 if err != nil { |
| paddy@23 | 103 if err == AuthorizationNotFoundError { |
| paddy@23 | 104 ctx.RenderJSONError(w, ErrorInvalidGrant, "Invalid authorization.", ctx.Config.DocumentationDomain) |
| paddy@23 | 105 return |
| paddy@23 | 106 } |
| paddy@23 | 107 ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) |
| paddy@23 | 108 return |
| paddy@23 | 109 } |
| paddy@23 | 110 if authData.RedirectURI == "" { |
| paddy@23 | 111 ctx.RenderJSONError(w, ErrorInvalidGrant, "Invalid redirect on grant.", ctx.Config.DocumentationDomain) |
| paddy@23 | 112 return |
| paddy@23 | 113 } |
| paddy@23 | 114 if authData.IsExpired() { |
| paddy@23 | 115 ctx.RenderJSONError(w, ErrorInvalidGrant, "Authorization is expired.", ctx.Config.DocumentationDomain) |
| paddy@23 | 116 return |
| paddy@23 | 117 } |
| paddy@23 | 118 |
| paddy@23 | 119 // code must be from the client |
| paddy@23 | 120 if !authData.Client.ID.Equal(client.ID) { |
| paddy@23 | 121 ctx.RenderJSONError(w, ErrorInvalidGrant, "Grant issued to another client.", ctx.Config.DocumentationDomain) |
| paddy@23 | 122 return |
| paddy@23 | 123 } |
| paddy@23 | 124 |
| paddy@23 | 125 // check redirect uri |
| paddy@23 | 126 redirectURI := r.Form.Get("redirect_uri") |
| paddy@23 | 127 if redirectURI == "" { |
| paddy@23 | 128 redirectURI = client.RedirectURI |
| paddy@23 | 129 } |
| paddy@23 | 130 if err = validateURI(client.RedirectURI, redirectURI); err != nil { |
| paddy@23 | 131 ctx.RenderJSONError(w, ErrorInvalidGrant, "Redirect URI doesn't match client.", ctx.Config.DocumentationDomain) |
| paddy@23 | 132 return |
| paddy@23 | 133 } |
| paddy@23 | 134 if authData.RedirectURI != redirectURI { |
| paddy@23 | 135 ctx.RenderJSONError(w, ErrorInvalidGrant, "Redirect URI doesn't match auth redirect.", ctx.Config.DocumentationDomain) |
| paddy@23 | 136 return |
| paddy@23 | 137 } |
| paddy@23 | 138 |
| paddy@23 | 139 data := AccessData{ |
| paddy@23 | 140 AuthRequest: AuthRequest{ |
| paddy@23 | 141 Client: client, |
| paddy@23 | 142 RedirectURI: redirectURI, |
| paddy@23 | 143 Scope: authData.Scope, |
| paddy@23 | 144 }, |
| paddy@23 | 145 Scope: authData.Scope, |
| paddy@23 | 146 PreviousAuthorizeData: &authData, |
| paddy@23 | 147 } |
| paddy@23 | 148 |
| paddy@23 | 149 err = fillTokens(&data, true, ctx) |
| paddy@23 | 150 if err != nil { |
| paddy@23 | 151 ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) |
| paddy@23 | 152 return |
| paddy@23 | 153 } |
| paddy@23 | 154 ctx.RenderJSONToken(w, data) |
| paddy@23 | 155 } |
| paddy@23 | 156 |
| paddy@23 | 157 func handleRefreshTokenRequest(w http.ResponseWriter, r *http.Request, ctx Context) { |
| paddy@23 | 158 // get client authentication |
| paddy@23 | 159 auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams) |
| paddy@23 | 160 |
| paddy@23 | 161 if err != nil { |
| paddy@23 | 162 ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) |
| paddy@23 | 163 return |
| paddy@23 | 164 } |
| paddy@23 | 165 |
| paddy@23 | 166 code := r.Form.Get("refresh_token") |
| paddy@23 | 167 |
| paddy@23 | 168 // "refresh_token" is required |
| paddy@23 | 169 if code == "" { |
| paddy@23 | 170 ctx.RenderJSONError(w, ErrorInvalidRequest, "Missing refresh token.", ctx.Config.DocumentationDomain) |
| paddy@23 | 171 return |
| paddy@23 | 172 } |
| paddy@23 | 173 |
| paddy@23 | 174 // must have a valid client |
| paddy@23 | 175 client, err := getClient(auth, ctx) |
| paddy@23 | 176 if err != nil { |
| paddy@23 | 177 if err == ClientNotFoundError || err == InvalidClientError { |
| paddy@23 | 178 ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) |
| paddy@23 | 179 return |
| paddy@23 | 180 } |
| paddy@23 | 181 ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) |
| paddy@23 | 182 return |
| paddy@23 | 183 } |
| paddy@23 | 184 |
| paddy@23 | 185 // must be a valid refresh code |
| paddy@23 | 186 refreshData, err := ctx.Tokens.GetRefresh(code) |
| paddy@23 | 187 if err != nil { |
| paddy@23 | 188 if err == TokenNotFoundError { |
| paddy@23 | 189 ctx.RenderJSONError(w, ErrorInvalidGrant, "Refresh token not valid.", ctx.Config.DocumentationDomain) |
| paddy@23 | 190 return |
| paddy@23 | 191 } |
| paddy@23 | 192 ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) |
| paddy@23 | 193 return |
| paddy@23 | 194 } |
| paddy@23 | 195 |
| paddy@23 | 196 // client must be the same as the previous token |
| paddy@23 | 197 if !refreshData.Client.ID.Equal(client.ID) { |
| paddy@23 | 198 ctx.RenderJSONError(w, ErrorInvalidGrant, "Refresh token issued to another client.", ctx.Config.DocumentationDomain) |
| paddy@23 | 199 return |
| paddy@23 | 200 } |
| paddy@23 | 201 |
| paddy@23 | 202 scope := r.Form.Get("scope") |
| paddy@23 | 203 if scope == "" { |
| paddy@23 | 204 scope = refreshData.Scope |
| paddy@23 | 205 } |
| paddy@23 | 206 |
| paddy@23 | 207 data := AccessData{ |
| paddy@23 | 208 AuthRequest: AuthRequest{ |
| paddy@23 | 209 Client: client, |
| paddy@23 | 210 Scope: scope, |
| paddy@23 | 211 }, |
| paddy@23 | 212 Scope: scope, |
| paddy@23 | 213 PreviousAccessData: &refreshData, |
| paddy@23 | 214 } |
| paddy@23 | 215 err = fillTokens(&data, true, ctx) |
| paddy@23 | 216 if err != nil { |
| paddy@23 | 217 ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) |
| paddy@23 | 218 return |
| paddy@23 | 219 } |
| paddy@23 | 220 ctx.RenderJSONToken(w, data) |
| paddy@23 | 221 } |
| paddy@23 | 222 |
| paddy@23 | 223 func handlePasswordRequest(w http.ResponseWriter, r *http.Request, ctx Context) { |
| paddy@23 | 224 // get client authentication |
| paddy@23 | 225 auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams) |
| paddy@23 | 226 if err != nil { |
| paddy@23 | 227 ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) |
| paddy@23 | 228 return |
| paddy@23 | 229 } |
| paddy@23 | 230 |
| paddy@23 | 231 username := r.Form.Get("username") |
| paddy@23 | 232 password := r.Form.Get("password") |
| paddy@23 | 233 scope := r.Form.Get("scope") |
| paddy@23 | 234 |
| paddy@23 | 235 // "username" and "password" is required |
| paddy@23 | 236 if username == "" || password == "" { |
| paddy@23 | 237 ctx.RenderJSONError(w, ErrorInvalidRequest, "Missing credentials.", ctx.Config.DocumentationDomain) |
| paddy@23 | 238 return |
| paddy@23 | 239 } |
| paddy@23 | 240 |
| paddy@23 | 241 // must have a valid client |
| paddy@23 | 242 client, err := getClient(auth, ctx) |
| paddy@23 | 243 if err != nil { |
| paddy@23 | 244 if err == ClientNotFoundError || err == InvalidClientError { |
| paddy@23 | 245 ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) |
| paddy@23 | 246 return |
| paddy@23 | 247 } |
| paddy@23 | 248 ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) |
| paddy@23 | 249 return |
| paddy@23 | 250 } |
| paddy@23 | 251 |
| paddy@23 | 252 _, err = ctx.Profiles.GetProfile(username, password) |
| paddy@23 | 253 if err != nil { |
| paddy@23 | 254 if err == ErrProfileNotFound { |
| paddy@23 | 255 ctx.RenderJSONError(w, ErrorInvalidGrant, "Invalid credentials.", ctx.Config.DocumentationDomain) |
| paddy@23 | 256 return |
| paddy@23 | 257 } |
| paddy@23 | 258 ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) |
| paddy@23 | 259 return |
| paddy@23 | 260 } |
| paddy@23 | 261 |
| paddy@23 | 262 data := AccessData{ |
| paddy@23 | 263 AuthRequest: AuthRequest{ |
| paddy@23 | 264 Client: client, |
| paddy@23 | 265 Scope: scope, |
| paddy@23 | 266 }, |
| paddy@23 | 267 Scope: scope, |
| paddy@23 | 268 } |
| paddy@23 | 269 |
| paddy@23 | 270 err = fillTokens(&data, true, ctx) |
| paddy@23 | 271 if err != nil { |
| paddy@23 | 272 ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) |
| paddy@23 | 273 return |
| paddy@23 | 274 } |
| paddy@23 | 275 ctx.RenderJSONToken(w, data) |
| paddy@23 | 276 } |
| paddy@23 | 277 |
| paddy@23 | 278 func handleClientCredentialsRequest(w http.ResponseWriter, r *http.Request, ctx Context) { |
| paddy@23 | 279 // get client authentication |
| paddy@23 | 280 auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams) |
| paddy@23 | 281 if err != nil { |
| paddy@23 | 282 ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) |
| paddy@23 | 283 return |
| paddy@23 | 284 } |
| paddy@23 | 285 |
| paddy@23 | 286 scope := r.Form.Get("scope") |
| paddy@23 | 287 |
| paddy@23 | 288 // must have a valid client |
| paddy@23 | 289 client, err := getClient(auth, ctx) |
| paddy@23 | 290 if err != nil { |
| paddy@23 | 291 if err == ClientNotFoundError || err == InvalidClientError { |
| paddy@23 | 292 ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) |
| paddy@23 | 293 return |
| paddy@23 | 294 } |
| paddy@23 | 295 ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) |
| paddy@23 | 296 return |
| paddy@23 | 297 } |
| paddy@23 | 298 |
| paddy@23 | 299 data := AccessData{ |
| paddy@23 | 300 AuthRequest: AuthRequest{ |
| paddy@23 | 301 Client: client, |
| paddy@23 | 302 Scope: scope, |
| paddy@23 | 303 }, |
| paddy@23 | 304 Scope: scope, |
| paddy@23 | 305 } |
| paddy@23 | 306 |
| paddy@23 | 307 err = fillTokens(&data, true, ctx) |
| paddy@23 | 308 if err != nil { |
| paddy@23 | 309 ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) |
| paddy@23 | 310 return |
| paddy@23 | 311 } |
| paddy@23 | 312 ctx.RenderJSONToken(w, data) |
| paddy@23 | 313 } |
| paddy@23 | 314 |
| paddy@23 | 315 func fillTokens(data *AccessData, includeRefresh bool, ctx Context) error { |
| paddy@23 | 316 var err error |
| paddy@23 | 317 |
| paddy@23 | 318 // generate access token |
| paddy@23 | 319 data.AccessToken = newToken() |
| paddy@23 | 320 if includeRefresh { |
| paddy@23 | 321 data.RefreshToken = newToken() |
| paddy@23 | 322 } |
| paddy@23 | 323 |
| paddy@23 | 324 // save access token |
| paddy@23 | 325 err = ctx.Tokens.SaveAccess(*data) |
| paddy@23 | 326 if err != nil { |
| paddy@23 | 327 if ctx.Log != nil { |
| paddy@23 | 328 ctx.Log.Printf("Error writing access token: %s\n", err) |
| paddy@23 | 329 } |
| paddy@23 | 330 return InternalServerError |
| paddy@23 | 331 } |
| paddy@23 | 332 |
| paddy@23 | 333 // remove authorization token |
| paddy@23 | 334 if data.PreviousAuthorizeData != nil { |
| paddy@23 | 335 err = ctx.Tokens.RemoveAuthorization(data.PreviousAuthorizeData.Code) |
| paddy@23 | 336 if err != nil && ctx.Log != nil { |
| paddy@23 | 337 ctx.Log.Printf("Error removing previous auth data (%s): %s\n", data.PreviousAuthorizeData.Code, err) |
| paddy@23 | 338 } |
| paddy@23 | 339 } |
| paddy@23 | 340 |
| paddy@23 | 341 // remove previous access token |
| paddy@23 | 342 if data.PreviousAccessData != nil { |
| paddy@23 | 343 if data.PreviousAccessData.RefreshToken != "" { |
| paddy@23 | 344 err = ctx.Tokens.RemoveRefresh(data.PreviousAccessData.RefreshToken) |
| paddy@23 | 345 if err != nil && ctx.Log != nil { |
| paddy@23 | 346 ctx.Log.Printf("Error removing previous refresh token (%s): %s\n", data.PreviousAccessData.RefreshToken, err) |
| paddy@23 | 347 } |
| paddy@23 | 348 } |
| paddy@23 | 349 err = ctx.Tokens.RemoveAccess(data.PreviousAccessData.AccessToken) |
| paddy@23 | 350 if err != nil && ctx.Log != nil { |
| paddy@23 | 351 ctx.Log.Printf("Error removing previous access token (%s): %s\n", data.PreviousAccessData.AccessToken, err) |
| paddy@23 | 352 } |
| paddy@23 | 353 } |
| paddy@23 | 354 |
| paddy@23 | 355 data.TokenType = ctx.Config.TokenType |
| paddy@23 | 356 data.ExpiresIn = ctx.Config.AccessExpiration |
| paddy@23 | 357 data.CreatedAt = time.Now() |
| paddy@23 | 358 return nil |
| paddy@23 | 359 } |
| paddy@23 | 360 |
| paddy@23 | 361 func (data AccessData) GetRedirect(fragment bool) (string, error) { |
| paddy@23 | 362 u, err := url.Parse(data.RedirectURI) |
| paddy@23 | 363 if err != nil { |
| paddy@23 | 364 return "", err |
| paddy@23 | 365 } |
| paddy@23 | 366 |
| paddy@23 | 367 // add parameters |
| paddy@23 | 368 q := u.Query() |
| paddy@23 | 369 q.Set("access_token", data.AccessToken) |
| paddy@23 | 370 q.Set("token_type", data.TokenType) |
| paddy@23 | 371 q.Set("expires_in", strconv.FormatInt(int64(data.ExpiresIn), 10)) |
| paddy@23 | 372 if data.RefreshToken != "" { |
| paddy@23 | 373 q.Set("refresh_token", data.RefreshToken) |
| paddy@23 | 374 } |
| paddy@23 | 375 if data.Scope != "" { |
| paddy@23 | 376 q.Set("scope", data.Scope) |
| paddy@23 | 377 } |
| paddy@23 | 378 if len(data.ProfileID) > 0 { |
| paddy@23 | 379 q.Set("profile", data.ProfileID.String()) |
| paddy@23 | 380 } |
| paddy@23 | 381 if fragment { |
| paddy@23 | 382 u.RawQuery = "" |
| paddy@23 | 383 u.Fragment = q.Encode() |
| paddy@23 | 384 } else { |
| paddy@23 | 385 u.RawQuery = q.Encode() |
| paddy@23 | 386 } |
| paddy@23 | 387 |
| paddy@23 | 388 return u.String(), nil |
| paddy@23 | 389 } |
| paddy@23 | 390 |
| paddy@23 | 391 // getClient looks up and authenticates the basic auth using the given |
| paddy@23 | 392 // storage. Sets an error on the response if auth fails or a server error occurs. |
| paddy@23 | 393 func getClient(auth BasicAuth, ctx Context) (Client, error) { |
| paddy@23 | 394 id, err := uuid.Parse(auth.Username) |
| paddy@23 | 395 if err != nil { |
| paddy@23 | 396 return Client{}, err |
| paddy@23 | 397 } |
| paddy@23 | 398 client, err := ctx.Clients.GetClient(id) |
| paddy@23 | 399 if err != nil { |
| paddy@23 | 400 if err == ClientNotFoundError { |
| paddy@23 | 401 return Client{}, err |
| paddy@23 | 402 } |
| paddy@23 | 403 if ctx.Log != nil { |
| paddy@23 | 404 ctx.Log.Printf("Error retrieving client %s: %s", id, err) |
| paddy@23 | 405 } |
| paddy@23 | 406 return Client{}, InternalServerError |
| paddy@23 | 407 } |
| paddy@23 | 408 if client.Secret != auth.Password { |
| paddy@23 | 409 return Client{}, InvalidClientError |
| paddy@23 | 410 } |
| paddy@23 | 411 if client.RedirectURI == "" { |
| paddy@23 | 412 return Client{}, InvalidClientError |
| paddy@23 | 413 } |
| paddy@23 | 414 return client, nil |
| paddy@23 | 415 } |