auth
auth/access.go
Continue our descent to horribleness. Remove all the nonsense about "extensibility" and "clean separation of concerns", instead hardcoding connections to decisions. Remove all those "test" things that stopped passing.
1.1 --- a/access.go Fri Jul 18 07:13:22 2014 -0400 1.2 +++ b/access.go Fri Aug 01 23:08:38 2014 -0400 1.3 @@ -2,8 +2,10 @@ 1.4 1.5 import ( 1.6 "net/http" 1.7 + "net/url" 1.8 "time" 1.9 1.10 + "strconv" 1.11 "secondbit.org/uuid" 1.12 ) 1.13 1.14 @@ -15,58 +17,19 @@ 1.15 RefreshTokenGrant = "refresh_token" 1.16 PasswordGrant = "password" 1.17 ClientCredentialsGrant = "client_credentials" 1.18 - AssertionGrant = "assertion" 1.19 - ImplicitGrant = "__implicit" 1.20 ) 1.21 1.22 -// AccessRequest is a request for access tokens 1.23 -type AccessRequest struct { 1.24 - Code string 1.25 - Client Client 1.26 - AuthorizeData AuthorizeData 1.27 - AccessData AccessData 1.28 - RedirectURI string 1.29 - Scope string 1.30 - Username string 1.31 - Password string 1.32 - AssertionType string 1.33 - Assertion string 1.34 - 1.35 - // Token expiration in seconds. Change if different from default 1.36 - Expiration int32 1.37 - 1.38 - // Set if a refresh token should be generated 1.39 - GenerateRefresh bool 1.40 -} 1.41 - 1.42 // AccessData represents an access grant (tokens, expiration, client, etc) 1.43 type AccessData struct { 1.44 - // Client information 1.45 - Client Client 1.46 - 1.47 - // Authorize data, for authorization code 1.48 - AuthorizeData *AuthorizeData 1.49 - 1.50 - // Previous access data, for refresh token 1.51 - AccessData *AccessData 1.52 - 1.53 - // Access token 1.54 - AccessToken string 1.55 - 1.56 - // Refresh Token. Can be blank 1.57 - RefreshToken string 1.58 - 1.59 - // Token expiration in seconds 1.60 - ExpiresIn int32 1.61 - 1.62 - // Requested scope 1.63 - Scope string 1.64 - 1.65 - // Redirect URI from request 1.66 - RedirectURI string 1.67 - 1.68 - // Date created 1.69 - CreatedAt time.Time 1.70 + PreviousAuthorizeData *AuthorizeData 1.71 + PreviousAccessData *AccessData // previous access data, when refreshing 1.72 + AccessToken string 1.73 + RefreshToken string 1.74 + ExpiresIn int32 1.75 + CreatedAt time.Time 1.76 + TokenType string 1.77 + ProfileID uuid.ID 1.78 + AuthRequest 1.79 } 1.80 1.81 // IsExpired returns true if access expired 1.82 @@ -79,27 +42,16 @@ 1.83 return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second) 1.84 } 1.85 1.86 -// AccessTokenGen generates access tokens 1.87 -type AccessTokenGen interface { 1.88 - GenerateAccessToken(data *AccessData, generaterefresh bool) (accesstoken string, refreshtoken string, err error) 1.89 -} 1.90 - 1.91 // HandleOAuth2AccessRequest is the http.HandlerFunc for handling access token requests. 1.92 func HandleOAuth2AccessRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 1.93 // Only allow GET or POST 1.94 if r.Method != "POST" { 1.95 - if r.Method == "GET" && !ctx.Config.AllowGetAccessRequest { 1.96 + if r.Method != "GET" || !ctx.Config.AllowGetAccessRequest { 1.97 // TODO: return error 1.98 return 1.99 } 1.100 } 1.101 1.102 - err := r.ParseForm() 1.103 - if err != nil { 1.104 - // TODO: return error 1.105 - return 1.106 - } 1.107 - 1.108 grantType := GrantType(r.Form.Get("grant_type")) 1.109 if ctx.Config.AllowedAccessTypes.Exists(grantType) { 1.110 switch grantType { 1.111 @@ -111,8 +63,6 @@ 1.112 handlePasswordRequest(w, r, ctx) 1.113 case ClientCredentialsGrant: 1.114 handleClientCredentialsRequest(w, r, ctx) 1.115 - case AssertionGrant: 1.116 - handleAssertionRequest(w, r, ctx) 1.117 default: 1.118 // TODO: return error 1.119 return 1.120 @@ -128,63 +78,69 @@ 1.121 return 1.122 } 1.123 1.124 - // generate access token 1.125 - ret := AccessRequest{ 1.126 - Code: r.Form.Get("code"), 1.127 - RedirectURI: r.Form.Get("redirect_uri"), 1.128 - GenerateRefresh: true, 1.129 - Expiration: ctx.Config.AccessExpiration, 1.130 - } 1.131 - 1.132 + code := r.Form.Get("code") 1.133 // "code" is required 1.134 - if ret.Code == "" { 1.135 + if code == "" { 1.136 // TODO: return error 1.137 return 1.138 } 1.139 1.140 // must have a valid client 1.141 - ret.Client, err = getClient(auth, ctx) 1.142 + client, err := getClient(auth, ctx) 1.143 if err != nil { 1.144 // TODO: return error 1.145 return 1.146 } 1.147 1.148 // must be a valid authorization code 1.149 - ret.AuthorizeData, err = loadAuthorize(ret.Code, ctx) 1.150 + authData, err := ctx.Tokens.GetAuthorization(code) 1.151 if err != nil { 1.152 // TODO: return error 1.153 return 1.154 } 1.155 - if ret.AuthorizeData.Client.RedirectURI == "" { 1.156 + if authData.Client.RedirectURI == "" { 1.157 // TODO: return error 1.158 return 1.159 } 1.160 - if ret.AuthorizeData.IsExpired() { 1.161 + if authData.IsExpired() { 1.162 return // TODO: return error 1.163 } 1.164 1.165 // code must be from the client 1.166 - if !ret.AuthorizeData.Client.ID.Equal(ret.Client.ID) { 1.167 + if !authData.Client.ID.Equal(client.ID) { 1.168 // TODO: return error 1.169 return 1.170 } 1.171 1.172 // check redirect uri 1.173 - if ret.RedirectURI == "" { 1.174 - ret.RedirectURI = ret.Client.RedirectURI 1.175 + redirectURI := r.Form.Get("redirect_uri") 1.176 + if redirectURI == "" { 1.177 + redirectURI = client.RedirectURI 1.178 } 1.179 - if err = ValidateURI(ret.Client.RedirectURI, ret.RedirectURI); err != nil { 1.180 + if err = validateURI(client.RedirectURI, redirectURI); err != nil { 1.181 // TODO: return error 1.182 return 1.183 } 1.184 - if ret.AuthorizeData.RedirectURI != ret.RedirectURI { 1.185 + if authData.RedirectURI != redirectURI { 1.186 // TODO: return error 1.187 return 1.188 } 1.189 1.190 - // set rest of data 1.191 - ret.Scope = ret.AuthorizeData.Scope 1.192 - // TODO: write ret 1.193 + data := AccessData{ 1.194 + AuthRequest: AuthRequest{ 1.195 + Client: client, 1.196 + RedirectURI: redirectURI, 1.197 + Scope: authData.Scope, 1.198 + }, 1.199 + PreviousAuthorizeData: &authData, 1.200 + } 1.201 + 1.202 + err = fillTokens(&data, true, ctx) 1.203 + if err != nil { 1.204 + // TODO: return error 1.205 + return 1.206 + } 1.207 + // TODO: write data 1.208 } 1.209 1.210 func handleRefreshTokenRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 1.211 @@ -195,52 +151,62 @@ 1.212 return 1.213 } 1.214 1.215 - // generate access token 1.216 - ret := AccessRequest{ 1.217 - Code: r.Form.Get("refresh_token"), 1.218 - Scope: r.Form.Get("scope"), 1.219 - GenerateRefresh: true, 1.220 - Expiration: ctx.Config.AccessExpiration, 1.221 - } 1.222 + code := r.Form.Get("refresh_token") 1.223 1.224 // "refresh_token" is required 1.225 - if ret.Code == "" { 1.226 + if code == "" { 1.227 // TODO: return error 1.228 return 1.229 } 1.230 1.231 // must have a valid client 1.232 - ret.Client, err = getClient(auth, ctx) 1.233 + client, err := getClient(auth, ctx) 1.234 if err != nil { 1.235 // TODO: return error 1.236 return 1.237 } 1.238 1.239 // must be a valid refresh code 1.240 - ret.AccessData, err = loadRefresh(ret.Code, ctx) 1.241 + refreshData, err := ctx.Tokens.GetRefresh(code) 1.242 if err != nil { 1.243 // TODO: return error 1.244 return 1.245 } 1.246 - if ret.AccessData.Client.RedirectURI == "" { 1.247 + if refreshData.Client.RedirectURI == "" { 1.248 // TODO: return error 1.249 return 1.250 } 1.251 1.252 // client must be the same as the previous token 1.253 - if !ret.AccessData.Client.ID.Equal(ret.Client.ID) { 1.254 + if !refreshData.Client.ID.Equal(client.ID) { 1.255 // TODO: return error 1.256 return 1.257 - 1.258 } 1.259 1.260 // set rest of data 1.261 - ret.RedirectURI = ret.AccessData.RedirectURI 1.262 - if ret.Scope == "" { 1.263 - ret.Scope = ret.AccessData.Scope 1.264 + redirectURI := r.Form.Get("redirect_uri") 1.265 + if redirectURI == "" { 1.266 + redirectURI = refreshData.RedirectURI 1.267 + } 1.268 + scope := r.Form.Get("scope") 1.269 + if scope == "" { 1.270 + scope = refreshData.Scope 1.271 } 1.272 1.273 - // TODO: write ret 1.274 + data := AccessData{ 1.275 + AuthRequest: AuthRequest{ 1.276 + Client: client, 1.277 + RedirectURI: redirectURI, 1.278 + Scope: scope, 1.279 + }, 1.280 + PreviousAccessData: &refreshData, 1.281 + } 1.282 + err = fillTokens(&data, true, ctx) 1.283 + if err != nil { 1.284 + // TODO: return error 1.285 + return 1.286 + } 1.287 + // TODO: write data 1.288 } 1.289 1.290 func handlePasswordRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 1.291 @@ -251,32 +217,50 @@ 1.292 return 1.293 } 1.294 1.295 - // generate access token 1.296 - ret := AccessRequest{ 1.297 - Username: r.Form.Get("username"), 1.298 - Password: r.Form.Get("password"), 1.299 - Scope: r.Form.Get("scope"), 1.300 - GenerateRefresh: true, 1.301 - Expiration: ctx.Config.AccessExpiration, 1.302 - } 1.303 + username := r.Form.Get("username") 1.304 + password := r.Form.Get("password") 1.305 + scope := r.Form.Get("scope") 1.306 1.307 // "username" and "password" is required 1.308 - if ret.Username == "" || ret.Password == "" { 1.309 + if username == "" || password == "" { 1.310 // TODO: return error 1.311 return 1.312 } 1.313 1.314 // must have a valid client 1.315 - ret.Client, err = getClient(auth, ctx) 1.316 + client, err := getClient(auth, ctx) 1.317 if err != nil { 1.318 // TODO: return error 1.319 return 1.320 } 1.321 1.322 // set redirect uri 1.323 - ret.RedirectURI = ret.Client.RedirectURI 1.324 + redirectURI := r.Form.Get("redirect_uri") 1.325 + if redirectURI == "" { 1.326 + redirectURI = client.RedirectURI 1.327 + } 1.328 1.329 - // TODO: write ret 1.330 + _, err = ctx.Profiles.GetProfile(username, password) 1.331 + if err != nil { 1.332 + // TODO: return error 1.333 + return 1.334 + } 1.335 + 1.336 + data := AccessData{ 1.337 + AuthRequest: AuthRequest{ 1.338 + Client: client, 1.339 + RedirectURI: redirectURI, 1.340 + Scope: scope, 1.341 + }, 1.342 + } 1.343 + 1.344 + err = fillTokens(&data, true, ctx) 1.345 + if err != nil { 1.346 + // TODO: return error 1.347 + return 1.348 + } 1.349 + 1.350 + // TODO: write data 1.351 } 1.352 1.353 func handleClientCredentialsRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 1.354 @@ -287,130 +271,111 @@ 1.355 return 1.356 } 1.357 1.358 - // generate access token 1.359 - ret := AccessRequest{ 1.360 - Scope: r.Form.Get("scope"), 1.361 - GenerateRefresh: true, 1.362 - Expiration: ctx.Config.AccessExpiration, 1.363 - } 1.364 + scope := r.Form.Get("scope") 1.365 1.366 // must have a valid client 1.367 - ret.Client, err = getClient(auth, ctx) 1.368 + client, err := getClient(auth, ctx) 1.369 if err != nil { 1.370 // TODO: return error 1.371 return 1.372 } 1.373 1.374 // set redirect uri 1.375 - ret.RedirectURI = ret.Client.RedirectURI 1.376 + redirectURI := r.Form.Get("redirect_uri") 1.377 + if redirectURI == "" { 1.378 + redirectURI = client.RedirectURI 1.379 + } 1.380 1.381 - // TODO: write ret 1.382 -} 1.383 + data := AccessData{ 1.384 + AuthRequest: AuthRequest{ 1.385 + Client: client, 1.386 + RedirectURI: redirectURI, 1.387 + Scope: scope, 1.388 + }, 1.389 + } 1.390 1.391 -func handleAssertionRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 1.392 - // get client authentication 1.393 - auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams) 1.394 + err = fillTokens(&data, true, ctx) 1.395 if err != nil { 1.396 // TODO: return error 1.397 return 1.398 } 1.399 1.400 - // generate access token 1.401 - ret := &AccessRequest{ 1.402 - Scope: r.Form.Get("scope"), 1.403 - AssertionType: r.Form.Get("assertion_type"), 1.404 - Assertion: r.Form.Get("assertion"), 1.405 - GenerateRefresh: false, // assertion should NOT generate a refresh token, per the RFC 1.406 - Expiration: ctx.Config.AccessExpiration, 1.407 - } 1.408 - 1.409 - // "assertion_type" and "assertion" is required 1.410 - if ret.AssertionType == "" || ret.Assertion == "" { 1.411 - // TODO: return error 1.412 - return 1.413 - } 1.414 - 1.415 - // must have a valid client 1.416 - ret.Client, err = getClient(auth, ctx) 1.417 - if err != nil { 1.418 - //TODO: return error 1.419 - return 1.420 - } 1.421 - 1.422 - // set redirect uri 1.423 - ret.RedirectURI = ret.Client.RedirectURI 1.424 - 1.425 - // TODO: write ret 1.426 + // TODO: write data 1.427 } 1.428 1.429 -func FinishAccessRequest(w http.ResponseWriter, r *http.Request, ar AccessRequest, ctx Context) { 1.430 - // TODO: check if authorized? 1.431 - redirectURI := r.Form.Get("redirect_uri") 1.432 - // Get redirect uri from AccessRequest if it's there (e.g., refresh token request) 1.433 - if ar.RedirectURI != "" { 1.434 - redirectURI = ar.RedirectURI 1.435 - } 1.436 - ret := AccessData{ 1.437 - Client: ar.Client, 1.438 - AuthorizeData: &ar.AuthorizeData, 1.439 - AccessData: &ar.AccessData, 1.440 - RedirectURI: redirectURI, 1.441 - CreatedAt: time.Now(), 1.442 - ExpiresIn: ar.Expiration, 1.443 - Scope: ar.Scope, 1.444 - } 1.445 - 1.446 +func fillTokens(data *AccessData, includeRefresh bool, ctx Context) error { 1.447 var err error 1.448 1.449 // generate access token 1.450 - ret.AccessToken = newToken() 1.451 - if ar.GenerateRefresh { 1.452 - ret.RefreshToken = newToken() 1.453 + data.AccessToken = newToken() 1.454 + if includeRefresh { 1.455 + data.RefreshToken = newToken() 1.456 } 1.457 1.458 // save access token 1.459 - err = saveAccess(ret, ctx) 1.460 + err = ctx.Tokens.SaveAccess(*data) 1.461 if err != nil { 1.462 - // TODO: return error 1.463 - return 1.464 + // TODO: abstract out error 1.465 + return err 1.466 } 1.467 1.468 // remove authorization token 1.469 - if ret.AuthorizeData != nil { 1.470 - err = removeAuthorize(ret.AuthorizeData.Code, ctx) 1.471 + if data.PreviousAuthorizeData != nil { 1.472 + err = ctx.Tokens.RemoveAuthorization(data.PreviousAuthorizeData.Code) 1.473 if err != nil { 1.474 // TODO: log error 1.475 } 1.476 } 1.477 1.478 // remove previous access token 1.479 - if ret.AccessData != nil { 1.480 - if ret.AccessData.RefreshToken != "" { 1.481 - err = removeRefresh(ret.AccessData.RefreshToken, ctx) 1.482 + if data.PreviousAccessData != nil { 1.483 + if data.PreviousAccessData.RefreshToken != "" { 1.484 + err = ctx.Tokens.RemoveRefresh(data.PreviousAccessData.RefreshToken) 1.485 if err != nil { 1.486 // TODO: log error 1.487 } 1.488 } 1.489 - err = removeAccess(ret.AccessData.AccessToken, ctx) 1.490 + err = ctx.Tokens.RemoveAccess(data.PreviousAccessData.AccessToken) 1.491 if err != nil { 1.492 // TODO: log error 1.493 } 1.494 } 1.495 1.496 - // output data 1.497 - //w.Output["access_token"] = ret.AccessToken 1.498 - //w.Output["token_type"] = ctx.Config.TokenType 1.499 - //w.Output["expires_in"] = ret.ExpiresIn 1.500 - //if ret.RefreshToken != "" { 1.501 - // w.Output["refresh_token"] = ret.RefreshToken 1.502 - //} 1.503 - //if ar.Scope != "" { 1.504 - // w.Output["scope"] = ar.Scope 1.505 - //} 1.506 - // TODO: write ret 1.507 + data.TokenType = ctx.Config.TokenType 1.508 + data.ExpiresIn = ctx.Config.AccessExpiration 1.509 + data.CreatedAt = time.Now() 1.510 + return nil 1.511 } 1.512 1.513 -// Helper Functions 1.514 +func (data AccessData) GetRedirect(fragment bool) (string, error) { 1.515 + u, err := url.Parse(data.RedirectURI) 1.516 + if err != nil { 1.517 + return "", err 1.518 + } 1.519 + 1.520 + // add parameters 1.521 + q := u.Query() 1.522 + q.Set("access_token", data.AccessToken) 1.523 + q.Set("token_type", data.TokenType) 1.524 + q.Set("expires_in", strconv.FormatInt(int64(data.ExpiresIn), 10)) 1.525 + if data.RefreshToken != "" { 1.526 + q.Set("refresh_token", data.RefreshToken) 1.527 + } 1.528 + if data.Scope != "" { 1.529 + q.Set("scope", data.Scope) 1.530 + } 1.531 + if len(data.ProfileID) > 0 { 1.532 + q.Set("profile", data.ProfileID.String()) 1.533 + } 1.534 + if fragment { 1.535 + u.RawQuery = "" 1.536 + u.Fragment = q.Encode() 1.537 + } else { 1.538 + u.RawQuery = q.Encode() 1.539 + } 1.540 + 1.541 + return u.String(), nil 1.542 +} 1.543 1.544 // getClient looks up and authenticates the basic auth using the given 1.545 // storage. Sets an error on the response if auth fails or a server error occurs. 1.546 @@ -419,7 +384,7 @@ 1.547 if err != nil { 1.548 return Client{}, err 1.549 } 1.550 - client, err := GetClient(id, ctx) 1.551 + client, err := ctx.Clients.GetClient(id) 1.552 if err != nil { 1.553 // TODO: abstract out errors 1.554 return Client{}, err 1.555 @@ -434,23 +399,3 @@ 1.556 } 1.557 return client, nil 1.558 } 1.559 - 1.560 -func loadRefresh(code string, ctx Context) (AccessData, error) { 1.561 - return AccessData{}, nil 1.562 -} 1.563 - 1.564 -func loadAccess(code string, ctx Context) (AccessData, error) { 1.565 - return AccessData{}, nil 1.566 -} 1.567 - 1.568 -func saveAccess(data AccessData, ctx Context) error { 1.569 - return nil 1.570 -} 1.571 - 1.572 -func removeAccess(token string, ctx Context) error { 1.573 - return nil 1.574 -} 1.575 - 1.576 -func removeRefresh(token string, ctx Context) error { 1.577 - return nil 1.578 -}