auth
23:1aa3a85ff853 Browse Files
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.
access.go access.go.old authorize.go authorize.go.old config.go config.go.old context.go context.go.old errors.go errors.go.old session.go session.go.old util.go util.go.old
1.1 --- a/access.go Sat Aug 16 20:49:19 2014 -0400 1.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 1.3 @@ -1,415 +0,0 @@ 1.4 -package auth 1.5 - 1.6 -import ( 1.7 - "net/http" 1.8 - "net/url" 1.9 - "time" 1.10 - 1.11 - "strconv" 1.12 - "secondbit.org/uuid" 1.13 -) 1.14 - 1.15 -// GrantType is the type for OAuth param `grant_type` 1.16 -type GrantType string 1.17 - 1.18 -const ( 1.19 - AuthorizationCodeGrant GrantType = "authorization_code" 1.20 - RefreshTokenGrant = "refresh_token" 1.21 - PasswordGrant = "password" 1.22 - ClientCredentialsGrant = "client_credentials" 1.23 -) 1.24 - 1.25 -// AccessData represents an access grant (tokens, expiration, client, etc) 1.26 -type AccessData struct { 1.27 - PreviousAuthorizeData *AuthorizeData `json:"-"` 1.28 - PreviousAccessData *AccessData `json:"-"` // previous access data, when refreshing 1.29 - AccessToken string `json:"access_token"` 1.30 - RefreshToken string `json:"refresh_token,omitempty"` 1.31 - ExpiresIn int32 `json:"expires_in"` 1.32 - CreatedAt time.Time `json:"-"` 1.33 - TokenType string `json:"token_type"` 1.34 - Scope string `json:"scope,omitempty"` 1.35 - ProfileID uuid.ID `json:"-"` 1.36 - AuthRequest `json:"-"` 1.37 -} 1.38 - 1.39 -// IsExpired returns true if access expired 1.40 -func (d *AccessData) IsExpired() bool { 1.41 - return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second).Before(time.Now()) 1.42 -} 1.43 - 1.44 -// ExpireAt returns the expiration date 1.45 -func (d *AccessData) ExpireAt() time.Time { 1.46 - return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second) 1.47 -} 1.48 - 1.49 -// HandleOAuth2AccessRequest is the http.HandlerFunc for handling access token requests. 1.50 -func HandleOAuth2AccessRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 1.51 - // Only allow GET or POST 1.52 - if r.Method != "POST" { 1.53 - if r.Method != "GET" || !ctx.Config.AllowGetAccessRequest { 1.54 - ctx.RenderJSONError(w, ErrorInvalidRequest, "Invalid request method.", ctx.Config.DocumentationDomain) 1.55 - return 1.56 - } 1.57 - } 1.58 - 1.59 - grantType := GrantType(r.Form.Get("grant_type")) 1.60 - if ctx.Config.AllowedAccessTypes.Exists(grantType) { 1.61 - switch grantType { 1.62 - case AuthorizationCodeGrant: 1.63 - handleAuthorizationCodeRequest(w, r, ctx) 1.64 - case RefreshTokenGrant: 1.65 - handleRefreshTokenRequest(w, r, ctx) 1.66 - case PasswordGrant: 1.67 - handlePasswordRequest(w, r, ctx) 1.68 - case ClientCredentialsGrant: 1.69 - handleClientCredentialsRequest(w, r, ctx) 1.70 - default: 1.71 - ctx.RenderJSONError(w, ErrorUnsupportedGrantType, "Unsupported grant type.", ctx.Config.DocumentationDomain) 1.72 - return 1.73 - } 1.74 - } 1.75 -} 1.76 - 1.77 -func handleAuthorizationCodeRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 1.78 - // get client authentication 1.79 - auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams) 1.80 - if err != nil { 1.81 - ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) 1.82 - return 1.83 - } 1.84 - 1.85 - code := r.Form.Get("code") 1.86 - // "code" is required 1.87 - if code == "" { 1.88 - ctx.RenderJSONError(w, ErrorInvalidRequest, "Code must be supplied.", ctx.Config.DocumentationDomain) 1.89 - return 1.90 - } 1.91 - 1.92 - // must have a valid client 1.93 - client, err := getClient(auth, ctx) 1.94 - if err != nil { 1.95 - if err == ClientNotFoundError || err == InvalidClientError { 1.96 - ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) 1.97 - return 1.98 - } 1.99 - ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) 1.100 - return 1.101 - } 1.102 - 1.103 - // must be a valid authorization code 1.104 - authData, err := ctx.Tokens.GetAuthorization(code) 1.105 - if err != nil { 1.106 - if err == AuthorizationNotFoundError { 1.107 - ctx.RenderJSONError(w, ErrorInvalidGrant, "Invalid authorization.", ctx.Config.DocumentationDomain) 1.108 - return 1.109 - } 1.110 - ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) 1.111 - return 1.112 - } 1.113 - if authData.RedirectURI == "" { 1.114 - ctx.RenderJSONError(w, ErrorInvalidGrant, "Invalid redirect on grant.", ctx.Config.DocumentationDomain) 1.115 - return 1.116 - } 1.117 - if authData.IsExpired() { 1.118 - ctx.RenderJSONError(w, ErrorInvalidGrant, "Authorization is expired.", ctx.Config.DocumentationDomain) 1.119 - return 1.120 - } 1.121 - 1.122 - // code must be from the client 1.123 - if !authData.Client.ID.Equal(client.ID) { 1.124 - ctx.RenderJSONError(w, ErrorInvalidGrant, "Grant issued to another client.", ctx.Config.DocumentationDomain) 1.125 - return 1.126 - } 1.127 - 1.128 - // check redirect uri 1.129 - redirectURI := r.Form.Get("redirect_uri") 1.130 - if redirectURI == "" { 1.131 - redirectURI = client.RedirectURI 1.132 - } 1.133 - if err = validateURI(client.RedirectURI, redirectURI); err != nil { 1.134 - ctx.RenderJSONError(w, ErrorInvalidGrant, "Redirect URI doesn't match client.", ctx.Config.DocumentationDomain) 1.135 - return 1.136 - } 1.137 - if authData.RedirectURI != redirectURI { 1.138 - ctx.RenderJSONError(w, ErrorInvalidGrant, "Redirect URI doesn't match auth redirect.", ctx.Config.DocumentationDomain) 1.139 - return 1.140 - } 1.141 - 1.142 - data := AccessData{ 1.143 - AuthRequest: AuthRequest{ 1.144 - Client: client, 1.145 - RedirectURI: redirectURI, 1.146 - Scope: authData.Scope, 1.147 - }, 1.148 - Scope: authData.Scope, 1.149 - PreviousAuthorizeData: &authData, 1.150 - } 1.151 - 1.152 - err = fillTokens(&data, true, ctx) 1.153 - if err != nil { 1.154 - ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) 1.155 - return 1.156 - } 1.157 - ctx.RenderJSONToken(w, data) 1.158 -} 1.159 - 1.160 -func handleRefreshTokenRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 1.161 - // get client authentication 1.162 - auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams) 1.163 - 1.164 - if err != nil { 1.165 - ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) 1.166 - return 1.167 - } 1.168 - 1.169 - code := r.Form.Get("refresh_token") 1.170 - 1.171 - // "refresh_token" is required 1.172 - if code == "" { 1.173 - ctx.RenderJSONError(w, ErrorInvalidRequest, "Missing refresh token.", ctx.Config.DocumentationDomain) 1.174 - return 1.175 - } 1.176 - 1.177 - // must have a valid client 1.178 - client, err := getClient(auth, ctx) 1.179 - if err != nil { 1.180 - if err == ClientNotFoundError || err == InvalidClientError { 1.181 - ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) 1.182 - return 1.183 - } 1.184 - ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) 1.185 - return 1.186 - } 1.187 - 1.188 - // must be a valid refresh code 1.189 - refreshData, err := ctx.Tokens.GetRefresh(code) 1.190 - if err != nil { 1.191 - if err == TokenNotFoundError { 1.192 - ctx.RenderJSONError(w, ErrorInvalidGrant, "Refresh token not valid.", ctx.Config.DocumentationDomain) 1.193 - return 1.194 - } 1.195 - ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) 1.196 - return 1.197 - } 1.198 - 1.199 - // client must be the same as the previous token 1.200 - if !refreshData.Client.ID.Equal(client.ID) { 1.201 - ctx.RenderJSONError(w, ErrorInvalidGrant, "Refresh token issued to another client.", ctx.Config.DocumentationDomain) 1.202 - return 1.203 - } 1.204 - 1.205 - scope := r.Form.Get("scope") 1.206 - if scope == "" { 1.207 - scope = refreshData.Scope 1.208 - } 1.209 - 1.210 - data := AccessData{ 1.211 - AuthRequest: AuthRequest{ 1.212 - Client: client, 1.213 - Scope: scope, 1.214 - }, 1.215 - Scope: scope, 1.216 - PreviousAccessData: &refreshData, 1.217 - } 1.218 - err = fillTokens(&data, true, ctx) 1.219 - if err != nil { 1.220 - ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) 1.221 - return 1.222 - } 1.223 - ctx.RenderJSONToken(w, data) 1.224 -} 1.225 - 1.226 -func handlePasswordRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 1.227 - // get client authentication 1.228 - auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams) 1.229 - if err != nil { 1.230 - ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) 1.231 - return 1.232 - } 1.233 - 1.234 - username := r.Form.Get("username") 1.235 - password := r.Form.Get("password") 1.236 - scope := r.Form.Get("scope") 1.237 - 1.238 - // "username" and "password" is required 1.239 - if username == "" || password == "" { 1.240 - ctx.RenderJSONError(w, ErrorInvalidRequest, "Missing credentials.", ctx.Config.DocumentationDomain) 1.241 - return 1.242 - } 1.243 - 1.244 - // must have a valid client 1.245 - client, err := getClient(auth, ctx) 1.246 - if err != nil { 1.247 - if err == ClientNotFoundError || err == InvalidClientError { 1.248 - ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) 1.249 - return 1.250 - } 1.251 - ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) 1.252 - return 1.253 - } 1.254 - 1.255 - _, err = ctx.Profiles.GetProfile(username, password) 1.256 - if err != nil { 1.257 - if err == ErrProfileNotFound { 1.258 - ctx.RenderJSONError(w, ErrorInvalidGrant, "Invalid credentials.", ctx.Config.DocumentationDomain) 1.259 - return 1.260 - } 1.261 - ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) 1.262 - return 1.263 - } 1.264 - 1.265 - data := AccessData{ 1.266 - AuthRequest: AuthRequest{ 1.267 - Client: client, 1.268 - Scope: scope, 1.269 - }, 1.270 - Scope: scope, 1.271 - } 1.272 - 1.273 - err = fillTokens(&data, true, ctx) 1.274 - if err != nil { 1.275 - ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) 1.276 - return 1.277 - } 1.278 - ctx.RenderJSONToken(w, data) 1.279 -} 1.280 - 1.281 -func handleClientCredentialsRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 1.282 - // get client authentication 1.283 - auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams) 1.284 - if err != nil { 1.285 - ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) 1.286 - return 1.287 - } 1.288 - 1.289 - scope := r.Form.Get("scope") 1.290 - 1.291 - // must have a valid client 1.292 - client, err := getClient(auth, ctx) 1.293 - if err != nil { 1.294 - if err == ClientNotFoundError || err == InvalidClientError { 1.295 - ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) 1.296 - return 1.297 - } 1.298 - ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) 1.299 - return 1.300 - } 1.301 - 1.302 - data := AccessData{ 1.303 - AuthRequest: AuthRequest{ 1.304 - Client: client, 1.305 - Scope: scope, 1.306 - }, 1.307 - Scope: scope, 1.308 - } 1.309 - 1.310 - err = fillTokens(&data, true, ctx) 1.311 - if err != nil { 1.312 - ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) 1.313 - return 1.314 - } 1.315 - ctx.RenderJSONToken(w, data) 1.316 -} 1.317 - 1.318 -func fillTokens(data *AccessData, includeRefresh bool, ctx Context) error { 1.319 - var err error 1.320 - 1.321 - // generate access token 1.322 - data.AccessToken = newToken() 1.323 - if includeRefresh { 1.324 - data.RefreshToken = newToken() 1.325 - } 1.326 - 1.327 - // save access token 1.328 - err = ctx.Tokens.SaveAccess(*data) 1.329 - if err != nil { 1.330 - if ctx.Log != nil { 1.331 - ctx.Log.Printf("Error writing access token: %s\n", err) 1.332 - } 1.333 - return InternalServerError 1.334 - } 1.335 - 1.336 - // remove authorization token 1.337 - if data.PreviousAuthorizeData != nil { 1.338 - err = ctx.Tokens.RemoveAuthorization(data.PreviousAuthorizeData.Code) 1.339 - if err != nil && ctx.Log != nil { 1.340 - ctx.Log.Printf("Error removing previous auth data (%s): %s\n", data.PreviousAuthorizeData.Code, err) 1.341 - } 1.342 - } 1.343 - 1.344 - // remove previous access token 1.345 - if data.PreviousAccessData != nil { 1.346 - if data.PreviousAccessData.RefreshToken != "" { 1.347 - err = ctx.Tokens.RemoveRefresh(data.PreviousAccessData.RefreshToken) 1.348 - if err != nil && ctx.Log != nil { 1.349 - ctx.Log.Printf("Error removing previous refresh token (%s): %s\n", data.PreviousAccessData.RefreshToken, err) 1.350 - } 1.351 - } 1.352 - err = ctx.Tokens.RemoveAccess(data.PreviousAccessData.AccessToken) 1.353 - if err != nil && ctx.Log != nil { 1.354 - ctx.Log.Printf("Error removing previous access token (%s): %s\n", data.PreviousAccessData.AccessToken, err) 1.355 - } 1.356 - } 1.357 - 1.358 - data.TokenType = ctx.Config.TokenType 1.359 - data.ExpiresIn = ctx.Config.AccessExpiration 1.360 - data.CreatedAt = time.Now() 1.361 - return nil 1.362 -} 1.363 - 1.364 -func (data AccessData) GetRedirect(fragment bool) (string, error) { 1.365 - u, err := url.Parse(data.RedirectURI) 1.366 - if err != nil { 1.367 - return "", err 1.368 - } 1.369 - 1.370 - // add parameters 1.371 - q := u.Query() 1.372 - q.Set("access_token", data.AccessToken) 1.373 - q.Set("token_type", data.TokenType) 1.374 - q.Set("expires_in", strconv.FormatInt(int64(data.ExpiresIn), 10)) 1.375 - if data.RefreshToken != "" { 1.376 - q.Set("refresh_token", data.RefreshToken) 1.377 - } 1.378 - if data.Scope != "" { 1.379 - q.Set("scope", data.Scope) 1.380 - } 1.381 - if len(data.ProfileID) > 0 { 1.382 - q.Set("profile", data.ProfileID.String()) 1.383 - } 1.384 - if fragment { 1.385 - u.RawQuery = "" 1.386 - u.Fragment = q.Encode() 1.387 - } else { 1.388 - u.RawQuery = q.Encode() 1.389 - } 1.390 - 1.391 - return u.String(), nil 1.392 -} 1.393 - 1.394 -// getClient looks up and authenticates the basic auth using the given 1.395 -// storage. Sets an error on the response if auth fails or a server error occurs. 1.396 -func getClient(auth BasicAuth, ctx Context) (Client, error) { 1.397 - id, err := uuid.Parse(auth.Username) 1.398 - if err != nil { 1.399 - return Client{}, err 1.400 - } 1.401 - client, err := ctx.Clients.GetClient(id) 1.402 - if err != nil { 1.403 - if err == ClientNotFoundError { 1.404 - return Client{}, err 1.405 - } 1.406 - if ctx.Log != nil { 1.407 - ctx.Log.Printf("Error retrieving client %s: %s", id, err) 1.408 - } 1.409 - return Client{}, InternalServerError 1.410 - } 1.411 - if client.Secret != auth.Password { 1.412 - return Client{}, InvalidClientError 1.413 - } 1.414 - if client.RedirectURI == "" { 1.415 - return Client{}, InvalidClientError 1.416 - } 1.417 - return client, nil 1.418 -}
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 2.2 +++ b/access.go.old Mon Sep 01 09:13:52 2014 -0400 2.3 @@ -0,0 +1,415 @@ 2.4 +package auth 2.5 + 2.6 +import ( 2.7 + "net/http" 2.8 + "net/url" 2.9 + "time" 2.10 + 2.11 + "strconv" 2.12 + "secondbit.org/uuid" 2.13 +) 2.14 + 2.15 +// GrantType is the type for OAuth param `grant_type` 2.16 +type GrantType string 2.17 + 2.18 +const ( 2.19 + AuthorizationCodeGrant GrantType = "authorization_code" 2.20 + RefreshTokenGrant = "refresh_token" 2.21 + PasswordGrant = "password" 2.22 + ClientCredentialsGrant = "client_credentials" 2.23 +) 2.24 + 2.25 +// AccessData represents an access grant (tokens, expiration, client, etc) 2.26 +type AccessData struct { 2.27 + PreviousAuthorizeData *AuthorizeData `json:"-"` 2.28 + PreviousAccessData *AccessData `json:"-"` // previous access data, when refreshing 2.29 + AccessToken string `json:"access_token"` 2.30 + RefreshToken string `json:"refresh_token,omitempty"` 2.31 + ExpiresIn int32 `json:"expires_in"` 2.32 + CreatedAt time.Time `json:"-"` 2.33 + TokenType string `json:"token_type"` 2.34 + Scope string `json:"scope,omitempty"` 2.35 + ProfileID uuid.ID `json:"-"` 2.36 + AuthRequest `json:"-"` 2.37 +} 2.38 + 2.39 +// IsExpired returns true if access expired 2.40 +func (d *AccessData) IsExpired() bool { 2.41 + return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second).Before(time.Now()) 2.42 +} 2.43 + 2.44 +// ExpireAt returns the expiration date 2.45 +func (d *AccessData) ExpireAt() time.Time { 2.46 + return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second) 2.47 +} 2.48 + 2.49 +// HandleOAuth2AccessRequest is the http.HandlerFunc for handling access token requests. 2.50 +func HandleOAuth2AccessRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 2.51 + // Only allow GET or POST 2.52 + if r.Method != "POST" { 2.53 + if r.Method != "GET" || !ctx.Config.AllowGetAccessRequest { 2.54 + ctx.RenderJSONError(w, ErrorInvalidRequest, "Invalid request method.", ctx.Config.DocumentationDomain) 2.55 + return 2.56 + } 2.57 + } 2.58 + 2.59 + grantType := GrantType(r.Form.Get("grant_type")) 2.60 + if ctx.Config.AllowedAccessTypes.Exists(grantType) { 2.61 + switch grantType { 2.62 + case AuthorizationCodeGrant: 2.63 + handleAuthorizationCodeRequest(w, r, ctx) 2.64 + case RefreshTokenGrant: 2.65 + handleRefreshTokenRequest(w, r, ctx) 2.66 + case PasswordGrant: 2.67 + handlePasswordRequest(w, r, ctx) 2.68 + case ClientCredentialsGrant: 2.69 + handleClientCredentialsRequest(w, r, ctx) 2.70 + default: 2.71 + ctx.RenderJSONError(w, ErrorUnsupportedGrantType, "Unsupported grant type.", ctx.Config.DocumentationDomain) 2.72 + return 2.73 + } 2.74 + } 2.75 +} 2.76 + 2.77 +func handleAuthorizationCodeRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 2.78 + // get client authentication 2.79 + auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams) 2.80 + if err != nil { 2.81 + ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) 2.82 + return 2.83 + } 2.84 + 2.85 + code := r.Form.Get("code") 2.86 + // "code" is required 2.87 + if code == "" { 2.88 + ctx.RenderJSONError(w, ErrorInvalidRequest, "Code must be supplied.", ctx.Config.DocumentationDomain) 2.89 + return 2.90 + } 2.91 + 2.92 + // must have a valid client 2.93 + client, err := getClient(auth, ctx) 2.94 + if err != nil { 2.95 + if err == ClientNotFoundError || err == InvalidClientError { 2.96 + ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) 2.97 + return 2.98 + } 2.99 + ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) 2.100 + return 2.101 + } 2.102 + 2.103 + // must be a valid authorization code 2.104 + authData, err := ctx.Tokens.GetAuthorization(code) 2.105 + if err != nil { 2.106 + if err == AuthorizationNotFoundError { 2.107 + ctx.RenderJSONError(w, ErrorInvalidGrant, "Invalid authorization.", ctx.Config.DocumentationDomain) 2.108 + return 2.109 + } 2.110 + ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) 2.111 + return 2.112 + } 2.113 + if authData.RedirectURI == "" { 2.114 + ctx.RenderJSONError(w, ErrorInvalidGrant, "Invalid redirect on grant.", ctx.Config.DocumentationDomain) 2.115 + return 2.116 + } 2.117 + if authData.IsExpired() { 2.118 + ctx.RenderJSONError(w, ErrorInvalidGrant, "Authorization is expired.", ctx.Config.DocumentationDomain) 2.119 + return 2.120 + } 2.121 + 2.122 + // code must be from the client 2.123 + if !authData.Client.ID.Equal(client.ID) { 2.124 + ctx.RenderJSONError(w, ErrorInvalidGrant, "Grant issued to another client.", ctx.Config.DocumentationDomain) 2.125 + return 2.126 + } 2.127 + 2.128 + // check redirect uri 2.129 + redirectURI := r.Form.Get("redirect_uri") 2.130 + if redirectURI == "" { 2.131 + redirectURI = client.RedirectURI 2.132 + } 2.133 + if err = validateURI(client.RedirectURI, redirectURI); err != nil { 2.134 + ctx.RenderJSONError(w, ErrorInvalidGrant, "Redirect URI doesn't match client.", ctx.Config.DocumentationDomain) 2.135 + return 2.136 + } 2.137 + if authData.RedirectURI != redirectURI { 2.138 + ctx.RenderJSONError(w, ErrorInvalidGrant, "Redirect URI doesn't match auth redirect.", ctx.Config.DocumentationDomain) 2.139 + return 2.140 + } 2.141 + 2.142 + data := AccessData{ 2.143 + AuthRequest: AuthRequest{ 2.144 + Client: client, 2.145 + RedirectURI: redirectURI, 2.146 + Scope: authData.Scope, 2.147 + }, 2.148 + Scope: authData.Scope, 2.149 + PreviousAuthorizeData: &authData, 2.150 + } 2.151 + 2.152 + err = fillTokens(&data, true, ctx) 2.153 + if err != nil { 2.154 + ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) 2.155 + return 2.156 + } 2.157 + ctx.RenderJSONToken(w, data) 2.158 +} 2.159 + 2.160 +func handleRefreshTokenRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 2.161 + // get client authentication 2.162 + auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams) 2.163 + 2.164 + if err != nil { 2.165 + ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) 2.166 + return 2.167 + } 2.168 + 2.169 + code := r.Form.Get("refresh_token") 2.170 + 2.171 + // "refresh_token" is required 2.172 + if code == "" { 2.173 + ctx.RenderJSONError(w, ErrorInvalidRequest, "Missing refresh token.", ctx.Config.DocumentationDomain) 2.174 + return 2.175 + } 2.176 + 2.177 + // must have a valid client 2.178 + client, err := getClient(auth, ctx) 2.179 + if err != nil { 2.180 + if err == ClientNotFoundError || err == InvalidClientError { 2.181 + ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) 2.182 + return 2.183 + } 2.184 + ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) 2.185 + return 2.186 + } 2.187 + 2.188 + // must be a valid refresh code 2.189 + refreshData, err := ctx.Tokens.GetRefresh(code) 2.190 + if err != nil { 2.191 + if err == TokenNotFoundError { 2.192 + ctx.RenderJSONError(w, ErrorInvalidGrant, "Refresh token not valid.", ctx.Config.DocumentationDomain) 2.193 + return 2.194 + } 2.195 + ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) 2.196 + return 2.197 + } 2.198 + 2.199 + // client must be the same as the previous token 2.200 + if !refreshData.Client.ID.Equal(client.ID) { 2.201 + ctx.RenderJSONError(w, ErrorInvalidGrant, "Refresh token issued to another client.", ctx.Config.DocumentationDomain) 2.202 + return 2.203 + } 2.204 + 2.205 + scope := r.Form.Get("scope") 2.206 + if scope == "" { 2.207 + scope = refreshData.Scope 2.208 + } 2.209 + 2.210 + data := AccessData{ 2.211 + AuthRequest: AuthRequest{ 2.212 + Client: client, 2.213 + Scope: scope, 2.214 + }, 2.215 + Scope: scope, 2.216 + PreviousAccessData: &refreshData, 2.217 + } 2.218 + err = fillTokens(&data, true, ctx) 2.219 + if err != nil { 2.220 + ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) 2.221 + return 2.222 + } 2.223 + ctx.RenderJSONToken(w, data) 2.224 +} 2.225 + 2.226 +func handlePasswordRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 2.227 + // get client authentication 2.228 + auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams) 2.229 + if err != nil { 2.230 + ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) 2.231 + return 2.232 + } 2.233 + 2.234 + username := r.Form.Get("username") 2.235 + password := r.Form.Get("password") 2.236 + scope := r.Form.Get("scope") 2.237 + 2.238 + // "username" and "password" is required 2.239 + if username == "" || password == "" { 2.240 + ctx.RenderJSONError(w, ErrorInvalidRequest, "Missing credentials.", ctx.Config.DocumentationDomain) 2.241 + return 2.242 + } 2.243 + 2.244 + // must have a valid client 2.245 + client, err := getClient(auth, ctx) 2.246 + if err != nil { 2.247 + if err == ClientNotFoundError || err == InvalidClientError { 2.248 + ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) 2.249 + return 2.250 + } 2.251 + ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) 2.252 + return 2.253 + } 2.254 + 2.255 + _, err = ctx.Profiles.GetProfile(username, password) 2.256 + if err != nil { 2.257 + if err == ErrProfileNotFound { 2.258 + ctx.RenderJSONError(w, ErrorInvalidGrant, "Invalid credentials.", ctx.Config.DocumentationDomain) 2.259 + return 2.260 + } 2.261 + ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) 2.262 + return 2.263 + } 2.264 + 2.265 + data := AccessData{ 2.266 + AuthRequest: AuthRequest{ 2.267 + Client: client, 2.268 + Scope: scope, 2.269 + }, 2.270 + Scope: scope, 2.271 + } 2.272 + 2.273 + err = fillTokens(&data, true, ctx) 2.274 + if err != nil { 2.275 + ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) 2.276 + return 2.277 + } 2.278 + ctx.RenderJSONToken(w, data) 2.279 +} 2.280 + 2.281 +func handleClientCredentialsRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 2.282 + // get client authentication 2.283 + auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams) 2.284 + if err != nil { 2.285 + ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) 2.286 + return 2.287 + } 2.288 + 2.289 + scope := r.Form.Get("scope") 2.290 + 2.291 + // must have a valid client 2.292 + client, err := getClient(auth, ctx) 2.293 + if err != nil { 2.294 + if err == ClientNotFoundError || err == InvalidClientError { 2.295 + ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) 2.296 + return 2.297 + } 2.298 + ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) 2.299 + return 2.300 + } 2.301 + 2.302 + data := AccessData{ 2.303 + AuthRequest: AuthRequest{ 2.304 + Client: client, 2.305 + Scope: scope, 2.306 + }, 2.307 + Scope: scope, 2.308 + } 2.309 + 2.310 + err = fillTokens(&data, true, ctx) 2.311 + if err != nil { 2.312 + ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) 2.313 + return 2.314 + } 2.315 + ctx.RenderJSONToken(w, data) 2.316 +} 2.317 + 2.318 +func fillTokens(data *AccessData, includeRefresh bool, ctx Context) error { 2.319 + var err error 2.320 + 2.321 + // generate access token 2.322 + data.AccessToken = newToken() 2.323 + if includeRefresh { 2.324 + data.RefreshToken = newToken() 2.325 + } 2.326 + 2.327 + // save access token 2.328 + err = ctx.Tokens.SaveAccess(*data) 2.329 + if err != nil { 2.330 + if ctx.Log != nil { 2.331 + ctx.Log.Printf("Error writing access token: %s\n", err) 2.332 + } 2.333 + return InternalServerError 2.334 + } 2.335 + 2.336 + // remove authorization token 2.337 + if data.PreviousAuthorizeData != nil { 2.338 + err = ctx.Tokens.RemoveAuthorization(data.PreviousAuthorizeData.Code) 2.339 + if err != nil && ctx.Log != nil { 2.340 + ctx.Log.Printf("Error removing previous auth data (%s): %s\n", data.PreviousAuthorizeData.Code, err) 2.341 + } 2.342 + } 2.343 + 2.344 + // remove previous access token 2.345 + if data.PreviousAccessData != nil { 2.346 + if data.PreviousAccessData.RefreshToken != "" { 2.347 + err = ctx.Tokens.RemoveRefresh(data.PreviousAccessData.RefreshToken) 2.348 + if err != nil && ctx.Log != nil { 2.349 + ctx.Log.Printf("Error removing previous refresh token (%s): %s\n", data.PreviousAccessData.RefreshToken, err) 2.350 + } 2.351 + } 2.352 + err = ctx.Tokens.RemoveAccess(data.PreviousAccessData.AccessToken) 2.353 + if err != nil && ctx.Log != nil { 2.354 + ctx.Log.Printf("Error removing previous access token (%s): %s\n", data.PreviousAccessData.AccessToken, err) 2.355 + } 2.356 + } 2.357 + 2.358 + data.TokenType = ctx.Config.TokenType 2.359 + data.ExpiresIn = ctx.Config.AccessExpiration 2.360 + data.CreatedAt = time.Now() 2.361 + return nil 2.362 +} 2.363 + 2.364 +func (data AccessData) GetRedirect(fragment bool) (string, error) { 2.365 + u, err := url.Parse(data.RedirectURI) 2.366 + if err != nil { 2.367 + return "", err 2.368 + } 2.369 + 2.370 + // add parameters 2.371 + q := u.Query() 2.372 + q.Set("access_token", data.AccessToken) 2.373 + q.Set("token_type", data.TokenType) 2.374 + q.Set("expires_in", strconv.FormatInt(int64(data.ExpiresIn), 10)) 2.375 + if data.RefreshToken != "" { 2.376 + q.Set("refresh_token", data.RefreshToken) 2.377 + } 2.378 + if data.Scope != "" { 2.379 + q.Set("scope", data.Scope) 2.380 + } 2.381 + if len(data.ProfileID) > 0 { 2.382 + q.Set("profile", data.ProfileID.String()) 2.383 + } 2.384 + if fragment { 2.385 + u.RawQuery = "" 2.386 + u.Fragment = q.Encode() 2.387 + } else { 2.388 + u.RawQuery = q.Encode() 2.389 + } 2.390 + 2.391 + return u.String(), nil 2.392 +} 2.393 + 2.394 +// getClient looks up and authenticates the basic auth using the given 2.395 +// storage. Sets an error on the response if auth fails or a server error occurs. 2.396 +func getClient(auth BasicAuth, ctx Context) (Client, error) { 2.397 + id, err := uuid.Parse(auth.Username) 2.398 + if err != nil { 2.399 + return Client{}, err 2.400 + } 2.401 + client, err := ctx.Clients.GetClient(id) 2.402 + if err != nil { 2.403 + if err == ClientNotFoundError { 2.404 + return Client{}, err 2.405 + } 2.406 + if ctx.Log != nil { 2.407 + ctx.Log.Printf("Error retrieving client %s: %s", id, err) 2.408 + } 2.409 + return Client{}, InternalServerError 2.410 + } 2.411 + if client.Secret != auth.Password { 2.412 + return Client{}, InvalidClientError 2.413 + } 2.414 + if client.RedirectURI == "" { 2.415 + return Client{}, InvalidClientError 2.416 + } 2.417 + return client, nil 2.418 +}
3.1 --- a/authorize.go Sat Aug 16 20:49:19 2014 -0400 3.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 3.3 @@ -1,272 +0,0 @@ 3.4 -package auth 3.5 - 3.6 -import ( 3.7 - "net/http" 3.8 - "net/url" 3.9 - "time" 3.10 - 3.11 - "strings" 3.12 - "secondbit.org/uuid" 3.13 -) 3.14 - 3.15 -// AuthorizeRequestType is the type for OAuth param `response_type` 3.16 -type AuthorizeRequestType string 3.17 - 3.18 -const ( 3.19 - CodeAuthRT AuthorizeRequestType = "code" 3.20 - TokenAuthRT = "token" 3.21 -) 3.22 - 3.23 -type AuthRequest struct { 3.24 - Client Client 3.25 - Scope string 3.26 - RedirectURI string 3.27 - State string 3.28 -} 3.29 - 3.30 -// Authorization data 3.31 -type AuthorizeData struct { 3.32 - // Authorization code 3.33 - Code string 3.34 - 3.35 - // Token expiration in seconds 3.36 - ExpiresIn int32 3.37 - 3.38 - // Date created 3.39 - CreatedAt time.Time 3.40 - 3.41 - ProfileID uuid.ID 3.42 - 3.43 - AuthRequest 3.44 -} 3.45 - 3.46 -// IsExpired is true if authorization expired 3.47 -func (d *AuthorizeData) IsExpired() bool { 3.48 - return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second).Before(time.Now()) 3.49 -} 3.50 - 3.51 -// ExpireAt returns the expiration date 3.52 -func (d *AuthorizeData) ExpireAt() time.Time { 3.53 - return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second) 3.54 -} 3.55 - 3.56 -// HandleAuthorizeRequest is the main http.HandlerFunc for handling 3.57 -// authorization requests 3.58 -func HandleAuthorizeRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 3.59 - r.ParseForm() 3.60 - // create the authorization request 3.61 - redirectURI := r.Form.Get("redirect_uri") 3.62 - var err error 3.63 - if redirectURI != "" { 3.64 - redirectURI, err = url.QueryUnescape(redirectURI) 3.65 - if err != nil { 3.66 - ctx.RenderError(w, URIFormatError(redirectURI)) 3.67 - return 3.68 - } 3.69 - } 3.70 - 3.71 - state := r.Form.Get("state") 3.72 - scope := r.Form.Get("scope") 3.73 - 3.74 - // must have a valid client 3.75 - id, err := uuid.Parse(r.Form.Get("client_id")) 3.76 - if err != nil { 3.77 - ctx.RenderError(w, InvalidClientIDError(r.Form.Get("client_id"))) 3.78 - return 3.79 - } 3.80 - client, err := GetClient(id, ctx) 3.81 - if err != nil { 3.82 - if err == ClientNotFoundError { 3.83 - ctx.RenderError(w, ClientNotFoundError) 3.84 - return 3.85 - } 3.86 - if redirectURI == "" { 3.87 - ctx.RenderError(w, URIMissingError) 3.88 - return 3.89 - } 3.90 - req := AuthRequest{ 3.91 - RedirectURI: redirectURI, 3.92 - Scope: scope, 3.93 - State: state, 3.94 - } 3.95 - redir, err := req.GetErrorRedirect(ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) 3.96 - if err != nil { 3.97 - ctx.RenderError(w, URIFormatError(redirectURI)) 3.98 - return 3.99 - } 3.100 - http.Redirect(w, r, redir, http.StatusFound) 3.101 - return 3.102 - } 3.103 - if client.RedirectURI == "" { 3.104 - ctx.RenderError(w, URIMissingError) 3.105 - return 3.106 - } 3.107 - 3.108 - // check redirect uri 3.109 - if redirectURI == "" { 3.110 - redirectURI = client.RedirectURI 3.111 - } 3.112 - if err = validateURI(client.RedirectURI, redirectURI); err != nil { 3.113 - ctx.RenderError(w, NewURIMismatchError(client.RedirectURI, redirectURI)) 3.114 - return 3.115 - } 3.116 - 3.117 - req := AuthRequest{ 3.118 - Client: client, 3.119 - RedirectURI: redirectURI, 3.120 - Scope: scope, 3.121 - State: state, 3.122 - } 3.123 - 3.124 - requestType := AuthorizeRequestType(r.Form.Get("response_type")) 3.125 - if ctx.Config.AllowedAuthorizeTypes.Exists(requestType) { 3.126 - switch requestType { 3.127 - case CodeAuthRT: 3.128 - req.handleCodeRequest(w, r, ctx) 3.129 - return 3.130 - case TokenAuthRT: 3.131 - req.handleTokenRequest(w, r, ctx) 3.132 - return 3.133 - } 3.134 - } 3.135 - redir, err := req.GetErrorRedirect(ErrorInvalidRequest, "Invalid response type.", ctx.Config.DocumentationDomain) 3.136 - if err != nil { 3.137 - ctx.RenderError(w, URIFormatError(req.RedirectURI)) 3.138 - return 3.139 - } 3.140 - http.Redirect(w, r, redir, http.StatusFound) 3.141 -} 3.142 - 3.143 -func (req AuthRequest) handleCodeRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 3.144 - if r.Method != "GET" && r.Method != "POST" { 3.145 - ctx.RenderError(w, InvalidMethodError) 3.146 - return 3.147 - } 3.148 - 3.149 - if err := validateSession(r, ctx); err == ErrorNotAuthenticated { 3.150 - http.Redirect(w, r, "/auth/login?redirect_to="+url.QueryEscape(r.URL.String()), http.StatusFound) 3.151 - return 3.152 - } else if err != nil { 3.153 - ctx.RenderError(w, err) 3.154 - return 3.155 - } 3.156 - 3.157 - if r.Method == "GET" { 3.158 - ctx.RenderConfirmation(w, r, req) 3.159 - return 3.160 - } 3.161 - 3.162 - if r.FormValue("approved") != "true" { 3.163 - redir, err := req.GetErrorRedirect(ErrorAccessDenied, "Request was not authorized.", ctx.Config.DocumentationDomain) 3.164 - if err != nil { 3.165 - ctx.RenderError(w, URIFormatError(req.RedirectURI)) 3.166 - return 3.167 - } 3.168 - http.Redirect(w, r, redir, http.StatusFound) 3.169 - return 3.170 - } 3.171 - 3.172 - data := AuthorizeData{AuthRequest: req} 3.173 - 3.174 - data.ExpiresIn = ctx.Config.AuthorizationExpiration 3.175 - data.Code = newToken() 3.176 - data.CreatedAt = time.Now() 3.177 - 3.178 - err := ctx.Tokens.SaveAuthorization(data) 3.179 - if err != nil { 3.180 - redir, err := req.GetErrorRedirect(ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) 3.181 - if err != nil { 3.182 - ctx.RenderError(w, URIFormatError(req.RedirectURI)) 3.183 - return 3.184 - } 3.185 - http.Redirect(w, r, redir, http.StatusFound) 3.186 - return 3.187 - } 3.188 - 3.189 - redir, err := data.GetRedirect() 3.190 - if err != nil { 3.191 - ctx.RenderError(w, URIFormatError(req.RedirectURI)) 3.192 - return 3.193 - } 3.194 - http.Redirect(w, r, redir, http.StatusFound) 3.195 -} 3.196 - 3.197 -func (req AuthRequest) handleTokenRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 3.198 - if r.Method != "GET" && r.Method != "POST" { 3.199 - ctx.RenderError(w, InvalidMethodError) 3.200 - return 3.201 - } 3.202 - 3.203 - if err := validateSession(r, ctx); err == ErrorNotAuthenticated { 3.204 - http.Redirect(w, r, "/auth/login?redirect_to="+url.QueryEscape(r.URL.String()), http.StatusFound) 3.205 - return 3.206 - } else if err != nil { 3.207 - ctx.RenderError(w, err) 3.208 - return 3.209 - } 3.210 - 3.211 - if r.Method == "GET" { 3.212 - ctx.RenderConfirmation(w, r, req) 3.213 - return 3.214 - } 3.215 - 3.216 - if r.FormValue("approved") != "true" { 3.217 - redir, err := req.GetErrorRedirect(ErrorAccessDenied, "Request was not authorized.", ctx.Config.DocumentationDomain) 3.218 - if err != nil { 3.219 - ctx.RenderError(w, URIFormatError(req.RedirectURI)) 3.220 - return 3.221 - } 3.222 - http.Redirect(w, r, redir, http.StatusFound) 3.223 - return 3.224 - } 3.225 - 3.226 - data := AccessData{AuthRequest: req} 3.227 - 3.228 - err := fillTokens(&data, false, ctx) 3.229 - if err != nil { 3.230 - ctx.RenderError(w, InternalServerError) 3.231 - return 3.232 - } 3.233 - 3.234 - redir, err := data.GetRedirect(true) 3.235 - if err != nil { 3.236 - ctx.RenderError(w, URIFormatError(req.RedirectURI)) 3.237 - return 3.238 - } 3.239 - http.Redirect(w, r, redir, http.StatusFound) 3.240 -} 3.241 - 3.242 -func (data AuthorizeData) GetRedirect() (string, error) { 3.243 - u, err := url.Parse(data.RedirectURI) 3.244 - if err != nil { 3.245 - return "", err 3.246 - } 3.247 - 3.248 - // add parameters 3.249 - q := u.Query() 3.250 - q.Set("code", data.Code) 3.251 - q.Set("state", data.State) 3.252 - u.RawQuery = q.Encode() 3.253 - 3.254 - return u.String(), nil 3.255 -} 3.256 - 3.257 -func (req AuthRequest) GetErrorRedirect(code, description, uriBase string) (string, error) { 3.258 - u, err := url.Parse(req.RedirectURI) 3.259 - if err != nil { 3.260 - return "", err 3.261 - } 3.262 - 3.263 - // add parameters 3.264 - q := u.Query() 3.265 - q.Set("error", code) 3.266 - q.Set("error_description", description) 3.267 - q.Set("error_uri", strings.Join([]string{ 3.268 - strings.TrimRight(uriBase, "/"), 3.269 - strings.TrimLeft(code, "/"), 3.270 - }, "/")) 3.271 - q.Set("state", req.State) 3.272 - u.RawQuery = q.Encode() 3.273 - 3.274 - return u.String(), nil 3.275 -}
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 4.2 +++ b/authorize.go.old Mon Sep 01 09:13:52 2014 -0400 4.3 @@ -0,0 +1,272 @@ 4.4 +package auth 4.5 + 4.6 +import ( 4.7 + "net/http" 4.8 + "net/url" 4.9 + "time" 4.10 + 4.11 + "strings" 4.12 + "secondbit.org/uuid" 4.13 +) 4.14 + 4.15 +// AuthorizeRequestType is the type for OAuth param `response_type` 4.16 +type AuthorizeRequestType string 4.17 + 4.18 +const ( 4.19 + CodeAuthRT AuthorizeRequestType = "code" 4.20 + TokenAuthRT = "token" 4.21 +) 4.22 + 4.23 +type AuthRequest struct { 4.24 + Client Client 4.25 + Scope string 4.26 + RedirectURI string 4.27 + State string 4.28 +} 4.29 + 4.30 +// Authorization data 4.31 +type AuthorizeData struct { 4.32 + // Authorization code 4.33 + Code string 4.34 + 4.35 + // Token expiration in seconds 4.36 + ExpiresIn int32 4.37 + 4.38 + // Date created 4.39 + CreatedAt time.Time 4.40 + 4.41 + ProfileID uuid.ID 4.42 + 4.43 + AuthRequest 4.44 +} 4.45 + 4.46 +// IsExpired is true if authorization expired 4.47 +func (d *AuthorizeData) IsExpired() bool { 4.48 + return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second).Before(time.Now()) 4.49 +} 4.50 + 4.51 +// ExpireAt returns the expiration date 4.52 +func (d *AuthorizeData) ExpireAt() time.Time { 4.53 + return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second) 4.54 +} 4.55 + 4.56 +// HandleAuthorizeRequest is the main http.HandlerFunc for handling 4.57 +// authorization requests 4.58 +func HandleAuthorizeRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 4.59 + r.ParseForm() 4.60 + // create the authorization request 4.61 + redirectURI := r.Form.Get("redirect_uri") 4.62 + var err error 4.63 + if redirectURI != "" { 4.64 + redirectURI, err = url.QueryUnescape(redirectURI) 4.65 + if err != nil { 4.66 + ctx.RenderError(w, URIFormatError(redirectURI)) 4.67 + return 4.68 + } 4.69 + } 4.70 + 4.71 + state := r.Form.Get("state") 4.72 + scope := r.Form.Get("scope") 4.73 + 4.74 + // must have a valid client 4.75 + id, err := uuid.Parse(r.Form.Get("client_id")) 4.76 + if err != nil { 4.77 + ctx.RenderError(w, InvalidClientIDError(r.Form.Get("client_id"))) 4.78 + return 4.79 + } 4.80 + client, err := GetClient(id, ctx) 4.81 + if err != nil { 4.82 + if err == ClientNotFoundError { 4.83 + ctx.RenderError(w, ClientNotFoundError) 4.84 + return 4.85 + } 4.86 + if redirectURI == "" { 4.87 + ctx.RenderError(w, URIMissingError) 4.88 + return 4.89 + } 4.90 + req := AuthRequest{ 4.91 + RedirectURI: redirectURI, 4.92 + Scope: scope, 4.93 + State: state, 4.94 + } 4.95 + redir, err := req.GetErrorRedirect(ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) 4.96 + if err != nil { 4.97 + ctx.RenderError(w, URIFormatError(redirectURI)) 4.98 + return 4.99 + } 4.100 + http.Redirect(w, r, redir, http.StatusFound) 4.101 + return 4.102 + } 4.103 + if client.RedirectURI == "" { 4.104 + ctx.RenderError(w, URIMissingError) 4.105 + return 4.106 + } 4.107 + 4.108 + // check redirect uri 4.109 + if redirectURI == "" { 4.110 + redirectURI = client.RedirectURI 4.111 + } 4.112 + if err = validateURI(client.RedirectURI, redirectURI); err != nil { 4.113 + ctx.RenderError(w, NewURIMismatchError(client.RedirectURI, redirectURI)) 4.114 + return 4.115 + } 4.116 + 4.117 + req := AuthRequest{ 4.118 + Client: client, 4.119 + RedirectURI: redirectURI, 4.120 + Scope: scope, 4.121 + State: state, 4.122 + } 4.123 + 4.124 + requestType := AuthorizeRequestType(r.Form.Get("response_type")) 4.125 + if ctx.Config.AllowedAuthorizeTypes.Exists(requestType) { 4.126 + switch requestType { 4.127 + case CodeAuthRT: 4.128 + req.handleCodeRequest(w, r, ctx) 4.129 + return 4.130 + case TokenAuthRT: 4.131 + req.handleTokenRequest(w, r, ctx) 4.132 + return 4.133 + } 4.134 + } 4.135 + redir, err := req.GetErrorRedirect(ErrorInvalidRequest, "Invalid response type.", ctx.Config.DocumentationDomain) 4.136 + if err != nil { 4.137 + ctx.RenderError(w, URIFormatError(req.RedirectURI)) 4.138 + return 4.139 + } 4.140 + http.Redirect(w, r, redir, http.StatusFound) 4.141 +} 4.142 + 4.143 +func (req AuthRequest) handleCodeRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 4.144 + if r.Method != "GET" && r.Method != "POST" { 4.145 + ctx.RenderError(w, InvalidMethodError) 4.146 + return 4.147 + } 4.148 + 4.149 + if err := validateSession(r, ctx); err == ErrorNotAuthenticated { 4.150 + http.Redirect(w, r, "/auth/login?redirect_to="+url.QueryEscape(r.URL.String()), http.StatusFound) 4.151 + return 4.152 + } else if err != nil { 4.153 + ctx.RenderError(w, err) 4.154 + return 4.155 + } 4.156 + 4.157 + if r.Method == "GET" { 4.158 + ctx.RenderConfirmation(w, r, req) 4.159 + return 4.160 + } 4.161 + 4.162 + if r.FormValue("approved") != "true" { 4.163 + redir, err := req.GetErrorRedirect(ErrorAccessDenied, "Request was not authorized.", ctx.Config.DocumentationDomain) 4.164 + if err != nil { 4.165 + ctx.RenderError(w, URIFormatError(req.RedirectURI)) 4.166 + return 4.167 + } 4.168 + http.Redirect(w, r, redir, http.StatusFound) 4.169 + return 4.170 + } 4.171 + 4.172 + data := AuthorizeData{AuthRequest: req} 4.173 + 4.174 + data.ExpiresIn = ctx.Config.AuthorizationExpiration 4.175 + data.Code = newToken() 4.176 + data.CreatedAt = time.Now() 4.177 + 4.178 + err := ctx.Tokens.SaveAuthorization(data) 4.179 + if err != nil { 4.180 + redir, err := req.GetErrorRedirect(ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) 4.181 + if err != nil { 4.182 + ctx.RenderError(w, URIFormatError(req.RedirectURI)) 4.183 + return 4.184 + } 4.185 + http.Redirect(w, r, redir, http.StatusFound) 4.186 + return 4.187 + } 4.188 + 4.189 + redir, err := data.GetRedirect() 4.190 + if err != nil { 4.191 + ctx.RenderError(w, URIFormatError(req.RedirectURI)) 4.192 + return 4.193 + } 4.194 + http.Redirect(w, r, redir, http.StatusFound) 4.195 +} 4.196 + 4.197 +func (req AuthRequest) handleTokenRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 4.198 + if r.Method != "GET" && r.Method != "POST" { 4.199 + ctx.RenderError(w, InvalidMethodError) 4.200 + return 4.201 + } 4.202 + 4.203 + if err := validateSession(r, ctx); err == ErrorNotAuthenticated { 4.204 + http.Redirect(w, r, "/auth/login?redirect_to="+url.QueryEscape(r.URL.String()), http.StatusFound) 4.205 + return 4.206 + } else if err != nil { 4.207 + ctx.RenderError(w, err) 4.208 + return 4.209 + } 4.210 + 4.211 + if r.Method == "GET" { 4.212 + ctx.RenderConfirmation(w, r, req) 4.213 + return 4.214 + } 4.215 + 4.216 + if r.FormValue("approved") != "true" { 4.217 + redir, err := req.GetErrorRedirect(ErrorAccessDenied, "Request was not authorized.", ctx.Config.DocumentationDomain) 4.218 + if err != nil { 4.219 + ctx.RenderError(w, URIFormatError(req.RedirectURI)) 4.220 + return 4.221 + } 4.222 + http.Redirect(w, r, redir, http.StatusFound) 4.223 + return 4.224 + } 4.225 + 4.226 + data := AccessData{AuthRequest: req} 4.227 + 4.228 + err := fillTokens(&data, false, ctx) 4.229 + if err != nil { 4.230 + ctx.RenderError(w, InternalServerError) 4.231 + return 4.232 + } 4.233 + 4.234 + redir, err := data.GetRedirect(true) 4.235 + if err != nil { 4.236 + ctx.RenderError(w, URIFormatError(req.RedirectURI)) 4.237 + return 4.238 + } 4.239 + http.Redirect(w, r, redir, http.StatusFound) 4.240 +} 4.241 + 4.242 +func (data AuthorizeData) GetRedirect() (string, error) { 4.243 + u, err := url.Parse(data.RedirectURI) 4.244 + if err != nil { 4.245 + return "", err 4.246 + } 4.247 + 4.248 + // add parameters 4.249 + q := u.Query() 4.250 + q.Set("code", data.Code) 4.251 + q.Set("state", data.State) 4.252 + u.RawQuery = q.Encode() 4.253 + 4.254 + return u.String(), nil 4.255 +} 4.256 + 4.257 +func (req AuthRequest) GetErrorRedirect(code, description, uriBase string) (string, error) { 4.258 + u, err := url.Parse(req.RedirectURI) 4.259 + if err != nil { 4.260 + return "", err 4.261 + } 4.262 + 4.263 + // add parameters 4.264 + q := u.Query() 4.265 + q.Set("error", code) 4.266 + q.Set("error_description", description) 4.267 + q.Set("error_uri", strings.Join([]string{ 4.268 + strings.TrimRight(uriBase, "/"), 4.269 + strings.TrimLeft(code, "/"), 4.270 + }, "/")) 4.271 + q.Set("state", req.State) 4.272 + u.RawQuery = q.Encode() 4.273 + 4.274 + return u.String(), nil 4.275 +}
5.1 --- a/config.go Sat Aug 16 20:49:19 2014 -0400 5.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 5.3 @@ -1,79 +0,0 @@ 5.4 -package auth 5.5 - 5.6 -import "time" 5.7 - 5.8 -// AllowedAuthorizeType is a collection of allowed auth request types 5.9 -type AllowedAuthorizeType []AuthorizeRequestType 5.10 - 5.11 -// Exists returns true if the auth type exists in the list 5.12 -func (t AllowedAuthorizeType) Exists(rt AuthorizeRequestType) bool { 5.13 - for _, k := range t { 5.14 - if k == rt { 5.15 - return true 5.16 - } 5.17 - } 5.18 - return false 5.19 -} 5.20 - 5.21 -// AllowedAccessType is a collection of allowed access request types 5.22 -type AllowedAccessType []GrantType 5.23 - 5.24 -// Exists returns true if the access type exists in the list 5.25 -func (t AllowedAccessType) Exists(rt GrantType) bool { 5.26 - for _, k := range t { 5.27 - if k == rt { 5.28 - return true 5.29 - } 5.30 - } 5.31 - return false 5.32 -} 5.33 - 5.34 -// ServerConfig contains server configuration information 5.35 -type ServerConfig struct { 5.36 - // Authorization token expiration in seconds (default 5 minutes) 5.37 - AuthorizationExpiration int32 5.38 - 5.39 - // Access token expiration in seconds (default 1 hour) 5.40 - AccessExpiration int32 5.41 - 5.42 - // Token type to return 5.43 - TokenType string 5.44 - 5.45 - // List of allowed authorize types (only CodeAuthRT by default) 5.46 - AllowedAuthorizeTypes AllowedAuthorizeType 5.47 - 5.48 - // List of allowed access types (only AUTHORIZATION_CodeAuthRT by default) 5.49 - AllowedAccessTypes AllowedAccessType 5.50 - 5.51 - // HTTP status code to return for errors - default 200 5.52 - // Only used if response was created from server 5.53 - ErrorStatusCode int 5.54 - 5.55 - // If true allows client secret also in params, else only in 5.56 - // Authorization header - default false 5.57 - AllowClientSecretInParams bool 5.58 - 5.59 - // If true allows access request using GET, else only POST - default false 5.60 - AllowGetAccessRequest bool 5.61 - 5.62 - // The base path of documentation 5.63 - DocumentationDomain string 5.64 - 5.65 - SessionLength time.Duration 5.66 - RequestIPHeader string 5.67 - LoginRedirectDomain string 5.68 -} 5.69 - 5.70 -// NewServerConfig returns a new ServerConfig with default configuration 5.71 -func NewServerConfig() ServerConfig { 5.72 - return ServerConfig{ 5.73 - AuthorizationExpiration: 250, 5.74 - AccessExpiration: 3600, 5.75 - TokenType: "bearer", 5.76 - AllowedAuthorizeTypes: AllowedAuthorizeType{CodeAuthRT}, 5.77 - AllowedAccessTypes: AllowedAccessType{AuthorizationCodeGrant}, 5.78 - ErrorStatusCode: 200, 5.79 - AllowClientSecretInParams: false, 5.80 - AllowGetAccessRequest: false, 5.81 - } 5.82 -}
6.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 6.2 +++ b/config.go.old Mon Sep 01 09:13:52 2014 -0400 6.3 @@ -0,0 +1,79 @@ 6.4 +package auth 6.5 + 6.6 +import "time" 6.7 + 6.8 +// AllowedAuthorizeType is a collection of allowed auth request types 6.9 +type AllowedAuthorizeType []AuthorizeRequestType 6.10 + 6.11 +// Exists returns true if the auth type exists in the list 6.12 +func (t AllowedAuthorizeType) Exists(rt AuthorizeRequestType) bool { 6.13 + for _, k := range t { 6.14 + if k == rt { 6.15 + return true 6.16 + } 6.17 + } 6.18 + return false 6.19 +} 6.20 + 6.21 +// AllowedAccessType is a collection of allowed access request types 6.22 +type AllowedAccessType []GrantType 6.23 + 6.24 +// Exists returns true if the access type exists in the list 6.25 +func (t AllowedAccessType) Exists(rt GrantType) bool { 6.26 + for _, k := range t { 6.27 + if k == rt { 6.28 + return true 6.29 + } 6.30 + } 6.31 + return false 6.32 +} 6.33 + 6.34 +// ServerConfig contains server configuration information 6.35 +type ServerConfig struct { 6.36 + // Authorization token expiration in seconds (default 5 minutes) 6.37 + AuthorizationExpiration int32 6.38 + 6.39 + // Access token expiration in seconds (default 1 hour) 6.40 + AccessExpiration int32 6.41 + 6.42 + // Token type to return 6.43 + TokenType string 6.44 + 6.45 + // List of allowed authorize types (only CodeAuthRT by default) 6.46 + AllowedAuthorizeTypes AllowedAuthorizeType 6.47 + 6.48 + // List of allowed access types (only AUTHORIZATION_CodeAuthRT by default) 6.49 + AllowedAccessTypes AllowedAccessType 6.50 + 6.51 + // HTTP status code to return for errors - default 200 6.52 + // Only used if response was created from server 6.53 + ErrorStatusCode int 6.54 + 6.55 + // If true allows client secret also in params, else only in 6.56 + // Authorization header - default false 6.57 + AllowClientSecretInParams bool 6.58 + 6.59 + // If true allows access request using GET, else only POST - default false 6.60 + AllowGetAccessRequest bool 6.61 + 6.62 + // The base path of documentation 6.63 + DocumentationDomain string 6.64 + 6.65 + SessionLength time.Duration 6.66 + RequestIPHeader string 6.67 + LoginRedirectDomain string 6.68 +} 6.69 + 6.70 +// NewServerConfig returns a new ServerConfig with default configuration 6.71 +func NewServerConfig() ServerConfig { 6.72 + return ServerConfig{ 6.73 + AuthorizationExpiration: 250, 6.74 + AccessExpiration: 3600, 6.75 + TokenType: "bearer", 6.76 + AllowedAuthorizeTypes: AllowedAuthorizeType{CodeAuthRT}, 6.77 + AllowedAccessTypes: AllowedAccessType{AuthorizationCodeGrant}, 6.78 + ErrorStatusCode: 200, 6.79 + AllowClientSecretInParams: false, 6.80 + AllowGetAccessRequest: false, 6.81 + } 6.82 +}
7.1 --- a/context.go Sat Aug 16 20:49:19 2014 -0400 7.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 7.3 @@ -1,108 +0,0 @@ 7.4 -package auth 7.5 - 7.6 -import ( 7.7 - "encoding/json" 7.8 - "html/template" 7.9 - "io" 7.10 - "log" 7.11 - "net/http" 7.12 - 7.13 - "github.com/justinas/nosurf" 7.14 -) 7.15 - 7.16 -type Context struct { 7.17 - Config ServerConfig 7.18 - Clients ClientStore 7.19 - Tokens TokenStore 7.20 - Profiles ProfileStore 7.21 - Sessions SessionStore 7.22 - Log *log.Logger 7.23 - Templates Templates 7.24 -} 7.25 - 7.26 -type Templates struct { 7.27 - Error *template.Template 7.28 - Confirmation *template.Template 7.29 - Login *template.Template 7.30 -} 7.31 - 7.32 -type jsonError struct { 7.33 - Error string `json:"error,omitempty"` 7.34 - Description string `json:"error_description,omitempty"` 7.35 - URI string `json:"error_uri,omitempty"` 7.36 - State string `json:"state,omitempty"` 7.37 -} 7.38 - 7.39 -func (c Context) RenderError(w io.Writer, err error) { 7.40 - if c.Templates.Error == nil { 7.41 - log.Println("Error template is nil, can't render error.") 7.42 - return 7.43 - } 7.44 - renderErr := c.Templates.Error.Execute(w, map[string]interface{}{ 7.45 - "err": err, 7.46 - }) 7.47 - if renderErr != nil { 7.48 - log.Printf("Error executing error template (oh, the irony): %s\n", renderErr) 7.49 - return 7.50 - } 7.51 -} 7.52 - 7.53 -func (c Context) RenderJSONError(w io.Writer, code, description, baseURI string) { 7.54 - d, err := json.Marshal(jsonError{ 7.55 - Error: code, 7.56 - Description: description, 7.57 - URI: baseURI, 7.58 - }) 7.59 - if err != nil { 7.60 - log.Printf("Error marshalling json error (oh, the irony): %s\n", err) 7.61 - return 7.62 - } 7.63 - _, err = w.Write(d) 7.64 - if err != nil { 7.65 - log.Printf("Error writing json error: %s\n", err) 7.66 - return 7.67 - } 7.68 -} 7.69 - 7.70 -func (c Context) RenderConfirmation(w io.Writer, r *http.Request, req AuthRequest) { 7.71 - if c.Templates.Confirmation == nil { 7.72 - log.Println("Confirmation template is nil, can't render confirmation.") 7.73 - return 7.74 - } 7.75 - err := c.Templates.Confirmation.Execute(w, map[string]interface{}{ 7.76 - "scope": req.Scope, 7.77 - "client": req.Client, 7.78 - "csrf_token": nosurf.Token(r), 7.79 - }) 7.80 - if err != nil { 7.81 - log.Printf("Error executing confirmation template: %s\n", err) 7.82 - return 7.83 - } 7.84 -} 7.85 - 7.86 -func (c Context) RenderLogin(w io.Writer, r *http.Request) { 7.87 - if c.Templates.Login == nil { 7.88 - log.Println("Login template is nil, can't render confirmation.") 7.89 - return 7.90 - } 7.91 - err := c.Templates.Login.Execute(w, map[string]interface{}{ 7.92 - "csrf_token": nosurf.Token(r), 7.93 - }) 7.94 - if err != nil { 7.95 - log.Printf("Error executing login template: %s\n", err) 7.96 - return 7.97 - } 7.98 -} 7.99 - 7.100 -func (c Context) RenderJSONToken(w io.Writer, data AccessData) { 7.101 - d, err := json.Marshal(data) 7.102 - if err != nil { 7.103 - log.Printf("Error marshalling json token: %s\n", err) 7.104 - return 7.105 - } 7.106 - _, err = w.Write(d) 7.107 - if err != nil { 7.108 - log.Printf("Error writing json token: %s\n", err) 7.109 - return 7.110 - } 7.111 -}
8.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 8.2 +++ b/context.go.old Mon Sep 01 09:13:52 2014 -0400 8.3 @@ -0,0 +1,108 @@ 8.4 +package auth 8.5 + 8.6 +import ( 8.7 + "encoding/json" 8.8 + "html/template" 8.9 + "io" 8.10 + "log" 8.11 + "net/http" 8.12 + 8.13 + "github.com/justinas/nosurf" 8.14 +) 8.15 + 8.16 +type Context struct { 8.17 + Config ServerConfig 8.18 + Clients ClientStore 8.19 + Tokens TokenStore 8.20 + Profiles ProfileStore 8.21 + Sessions SessionStore 8.22 + Log *log.Logger 8.23 + Templates Templates 8.24 +} 8.25 + 8.26 +type Templates struct { 8.27 + Error *template.Template 8.28 + Confirmation *template.Template 8.29 + Login *template.Template 8.30 +} 8.31 + 8.32 +type jsonError struct { 8.33 + Error string `json:"error,omitempty"` 8.34 + Description string `json:"error_description,omitempty"` 8.35 + URI string `json:"error_uri,omitempty"` 8.36 + State string `json:"state,omitempty"` 8.37 +} 8.38 + 8.39 +func (c Context) RenderError(w io.Writer, err error) { 8.40 + if c.Templates.Error == nil { 8.41 + log.Println("Error template is nil, can't render error.") 8.42 + return 8.43 + } 8.44 + renderErr := c.Templates.Error.Execute(w, map[string]interface{}{ 8.45 + "err": err, 8.46 + }) 8.47 + if renderErr != nil { 8.48 + log.Printf("Error executing error template (oh, the irony): %s\n", renderErr) 8.49 + return 8.50 + } 8.51 +} 8.52 + 8.53 +func (c Context) RenderJSONError(w io.Writer, code, description, baseURI string) { 8.54 + d, err := json.Marshal(jsonError{ 8.55 + Error: code, 8.56 + Description: description, 8.57 + URI: baseURI, 8.58 + }) 8.59 + if err != nil { 8.60 + log.Printf("Error marshalling json error (oh, the irony): %s\n", err) 8.61 + return 8.62 + } 8.63 + _, err = w.Write(d) 8.64 + if err != nil { 8.65 + log.Printf("Error writing json error: %s\n", err) 8.66 + return 8.67 + } 8.68 +} 8.69 + 8.70 +func (c Context) RenderConfirmation(w io.Writer, r *http.Request, req AuthRequest) { 8.71 + if c.Templates.Confirmation == nil { 8.72 + log.Println("Confirmation template is nil, can't render confirmation.") 8.73 + return 8.74 + } 8.75 + err := c.Templates.Confirmation.Execute(w, map[string]interface{}{ 8.76 + "scope": req.Scope, 8.77 + "client": req.Client, 8.78 + "csrf_token": nosurf.Token(r), 8.79 + }) 8.80 + if err != nil { 8.81 + log.Printf("Error executing confirmation template: %s\n", err) 8.82 + return 8.83 + } 8.84 +} 8.85 + 8.86 +func (c Context) RenderLogin(w io.Writer, r *http.Request) { 8.87 + if c.Templates.Login == nil { 8.88 + log.Println("Login template is nil, can't render confirmation.") 8.89 + return 8.90 + } 8.91 + err := c.Templates.Login.Execute(w, map[string]interface{}{ 8.92 + "csrf_token": nosurf.Token(r), 8.93 + }) 8.94 + if err != nil { 8.95 + log.Printf("Error executing login template: %s\n", err) 8.96 + return 8.97 + } 8.98 +} 8.99 + 8.100 +func (c Context) RenderJSONToken(w io.Writer, data AccessData) { 8.101 + d, err := json.Marshal(data) 8.102 + if err != nil { 8.103 + log.Printf("Error marshalling json token: %s\n", err) 8.104 + return 8.105 + } 8.106 + _, err = w.Write(d) 8.107 + if err != nil { 8.108 + log.Printf("Error writing json token: %s\n", err) 8.109 + return 8.110 + } 8.111 +}
9.1 --- a/errors.go Sat Aug 16 20:49:19 2014 -0400 9.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 9.3 @@ -1,55 +0,0 @@ 9.4 -package auth 9.5 - 9.6 -import "errors" 9.7 - 9.8 -const ( 9.9 - ErrorServerError = "server_error" 9.10 - ErrorInvalidRequest = "invalid_request" 9.11 - ErrorAccessDenied = "access_denied" 9.12 - ErrorInvalidClient = "invalid_client" 9.13 - ErrorInvalidGrant = "invalid_grant" 9.14 - ErrorUnauthorizedClient = "unauthorized_client" 9.15 - ErrorUnsupportedGrantType = "unsupported_grant_type" 9.16 - ErrorInvalidScope = "invalid_scope" 9.17 -) 9.18 - 9.19 -var ( 9.20 - ClientNotFoundError = errors.New("Client not found.") 9.21 - URIMissingError = errors.New("Redirect URI missing.") 9.22 - InvalidMethodError = errors.New("Invalid request method.") 9.23 - InternalServerError = errors.New("Internal server error.") 9.24 - ErrorNotAuthenticated = errors.New("Not authenticated.") 9.25 - InvalidClientError = errors.New("Invalid client.") 9.26 - AuthorizationNotFoundError = errors.New("Authorization not found.") 9.27 - ErrProfileNotFound = errors.New("Profile not found.") 9.28 - TokenNotFoundError = errors.New("Token not found.") 9.29 - NilClientError = errors.New("Client was nil.") 9.30 -) 9.31 - 9.32 -type URIFormatError string 9.33 - 9.34 -func (err URIFormatError) Error() string { 9.35 - return "Invalid URI format: " + string(err) 9.36 -} 9.37 - 9.38 -type InvalidClientIDError string 9.39 - 9.40 -func (err InvalidClientIDError) Error() string { 9.41 - return "Invalid client ID: " + string(err) 9.42 -} 9.43 - 9.44 -type URIMismatchError struct { 9.45 - uri string 9.46 - mismatch string 9.47 -} 9.48 - 9.49 -func (err URIMismatchError) Error() string { 9.50 - return "Supplied redirect URI " + err.mismatch + " does not match the redirect in the database (" + err.uri + ")" 9.51 -} 9.52 - 9.53 -func NewURIMismatchError(uri, mismatch string) error { 9.54 - return URIMismatchError{ 9.55 - uri: uri, 9.56 - mismatch: mismatch, 9.57 - } 9.58 -}
10.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 10.2 +++ b/errors.go.old Mon Sep 01 09:13:52 2014 -0400 10.3 @@ -0,0 +1,55 @@ 10.4 +package auth 10.5 + 10.6 +import "errors" 10.7 + 10.8 +const ( 10.9 + ErrorServerError = "server_error" 10.10 + ErrorInvalidRequest = "invalid_request" 10.11 + ErrorAccessDenied = "access_denied" 10.12 + ErrorInvalidClient = "invalid_client" 10.13 + ErrorInvalidGrant = "invalid_grant" 10.14 + ErrorUnauthorizedClient = "unauthorized_client" 10.15 + ErrorUnsupportedGrantType = "unsupported_grant_type" 10.16 + ErrorInvalidScope = "invalid_scope" 10.17 +) 10.18 + 10.19 +var ( 10.20 + ClientNotFoundError = errors.New("Client not found.") 10.21 + URIMissingError = errors.New("Redirect URI missing.") 10.22 + InvalidMethodError = errors.New("Invalid request method.") 10.23 + InternalServerError = errors.New("Internal server error.") 10.24 + ErrorNotAuthenticated = errors.New("Not authenticated.") 10.25 + InvalidClientError = errors.New("Invalid client.") 10.26 + AuthorizationNotFoundError = errors.New("Authorization not found.") 10.27 + ErrProfileNotFound = errors.New("Profile not found.") 10.28 + TokenNotFoundError = errors.New("Token not found.") 10.29 + NilClientError = errors.New("Client was nil.") 10.30 +) 10.31 + 10.32 +type URIFormatError string 10.33 + 10.34 +func (err URIFormatError) Error() string { 10.35 + return "Invalid URI format: " + string(err) 10.36 +} 10.37 + 10.38 +type InvalidClientIDError string 10.39 + 10.40 +func (err InvalidClientIDError) Error() string { 10.41 + return "Invalid client ID: " + string(err) 10.42 +} 10.43 + 10.44 +type URIMismatchError struct { 10.45 + uri string 10.46 + mismatch string 10.47 +} 10.48 + 10.49 +func (err URIMismatchError) Error() string { 10.50 + return "Supplied redirect URI " + err.mismatch + " does not match the redirect in the database (" + err.uri + ")" 10.51 +} 10.52 + 10.53 +func NewURIMismatchError(uri, mismatch string) error { 10.54 + return URIMismatchError{ 10.55 + uri: uri, 10.56 + mismatch: mismatch, 10.57 + } 10.58 +}
11.1 --- a/session.go Sat Aug 16 20:49:19 2014 -0400 11.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 11.3 @@ -1,94 +0,0 @@ 11.4 -package auth 11.5 - 11.6 -import ( 11.7 - "errors" 11.8 - "net/http" 11.9 - "net/url" 11.10 - "time" 11.11 - 11.12 - "strings" 11.13 - "secondbit.org/uuid" 11.14 -) 11.15 - 11.16 -const sessionCookie = "session" 11.17 - 11.18 -var ( 11.19 - ErrSessionNotFound = errors.New("Session not found.") 11.20 -) 11.21 - 11.22 -type Session struct { 11.23 - Token string 11.24 - User uuid.ID 11.25 - Expires time.Time 11.26 - Created time.Time 11.27 - IP string 11.28 -} 11.29 - 11.30 -func validateSession(r *http.Request, c Context) error { 11.31 - cookie, err := r.Cookie(sessionCookie) 11.32 - if err == http.ErrNoCookie { 11.33 - return ErrSessionNotFound 11.34 - } 11.35 - _, err = c.Sessions.GetSession(cookie.Value) 11.36 - return err 11.37 -} 11.38 - 11.39 -func HandleLoginRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 11.40 - if r.Method == "GET" { 11.41 - ctx.RenderLogin(w, r) 11.42 - return 11.43 - } else if r.Method != "POST" { 11.44 - // TODO: return bad method error 11.45 - return 11.46 - } 11.47 - 11.48 - if r.FormValue("username") == "" || r.FormValue("password") == "" { 11.49 - // TODO: return unauthenticated error 11.50 - return 11.51 - } 11.52 - id, err := ctx.Profiles.GetProfile(r.FormValue("username"), r.FormValue("password")) 11.53 - if err != nil { 11.54 - if err == ErrProfileNotFound { 11.55 - // TODO: return unauthenticated error 11.56 - return 11.57 - } 11.58 - // TODO: return internal server error 11.59 - return 11.60 - } 11.61 - session := Session{ 11.62 - Token: newToken(), 11.63 - User: id, 11.64 - Expires: time.Now().Add(ctx.Config.SessionLength), 11.65 - Created: time.Now(), 11.66 - IP: r.Header.Get(ctx.Config.RequestIPHeader), 11.67 - } 11.68 - err = ctx.Sessions.SetSession(session) 11.69 - if err != nil { 11.70 - // TODO: return internal server error 11.71 - return 11.72 - } 11.73 - http.SetCookie(w, &http.Cookie{ 11.74 - Name: sessionCookie, 11.75 - Value: session.Token, 11.76 - Expires: session.Expires, 11.77 - Secure: true, 11.78 - HttpOnly: true, 11.79 - }) 11.80 - 11.81 - redirectString := r.URL.Query().Get("redirect_to") 11.82 - if redirectString != "" { 11.83 - redirectURI, err := url.Parse(redirectString) 11.84 - if err != nil { 11.85 - // TODO: render a bad request error 11.86 - return 11.87 - } 11.88 - if !strings.HasSuffix("."+ctx.Config.LoginRedirectDomain, redirectURI.Host) && redirectURI.Host != ctx.Config.LoginRedirectDomain { 11.89 - // TODO: render a bad request error 11.90 - return 11.91 - } 11.92 - } else { 11.93 - redirectString = "https://" + ctx.Config.LoginRedirectDomain 11.94 - } 11.95 - http.Redirect(w, r, redirectString, http.StatusFound) 11.96 - return 11.97 -}
12.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 12.2 +++ b/session.go.old Mon Sep 01 09:13:52 2014 -0400 12.3 @@ -0,0 +1,101 @@ 12.4 +package auth 12.5 + 12.6 +import ( 12.7 + "errors" 12.8 + "net/http" 12.9 + "net/url" 12.10 + "time" 12.11 + 12.12 + "strings" 12.13 + "secondbit.org/uuid" 12.14 +) 12.15 + 12.16 +const sessionCookie = "session" 12.17 + 12.18 +var ( 12.19 + ErrSessionNotFound = errors.New("Session not found.") 12.20 +) 12.21 + 12.22 +type Session struct { 12.23 + Token string 12.24 + Profile uuid.ID 12.25 + Expires time.Time 12.26 + Created time.Time 12.27 + IP string 12.28 +} 12.29 + 12.30 +type SessionStore interface { 12.31 + GetSession(token string) (Session, error) 12.32 + GetAllSessions(userID uuid.ID, num, page int) ([]Session, error) 12.33 + SaveSession(session Session) error 12.34 + DeleteSession(sessionID uuid.ID) error 12.35 +} 12.36 + 12.37 +func validateSession(r *http.Request, c Context) error { 12.38 + cookie, err := r.Cookie(sessionCookie) 12.39 + if err == http.ErrNoCookie { 12.40 + return ErrSessionNotFound 12.41 + } 12.42 + _, err = c.Sessions.GetSession(cookie.Value) 12.43 + return err 12.44 +} 12.45 + 12.46 +func HandleLoginRequest(w http.ResponseWriter, r *http.Request, ctx Context) { 12.47 + if r.Method == "GET" { 12.48 + ctx.RenderLogin(w, r) 12.49 + return 12.50 + } else if r.Method != "POST" { 12.51 + // TODO: return bad method error 12.52 + return 12.53 + } 12.54 + 12.55 + if r.FormValue("username") == "" || r.FormValue("password") == "" { 12.56 + // TODO: return unauthenticated error 12.57 + return 12.58 + } 12.59 + id, err := ctx.Profiles.GetProfile(r.FormValue("username"), r.FormValue("password")) 12.60 + if err != nil { 12.61 + if err == ErrProfileNotFound { 12.62 + // TODO: return unauthenticated error 12.63 + return 12.64 + } 12.65 + // TODO: return internal server error 12.66 + return 12.67 + } 12.68 + session := Session{ 12.69 + Token: newToken(), 12.70 + Profile: id, 12.71 + Expires: time.Now().Add(ctx.Config.SessionLength), 12.72 + Created: time.Now(), 12.73 + IP: r.Header.Get(ctx.Config.RequestIPHeader), 12.74 + } 12.75 + err = ctx.Sessions.SetSession(session) 12.76 + if err != nil { 12.77 + // TODO: return internal server error 12.78 + return 12.79 + } 12.80 + http.SetCookie(w, &http.Cookie{ 12.81 + Name: sessionCookie, 12.82 + Value: session.Token, 12.83 + Expires: session.Expires, 12.84 + Secure: true, 12.85 + HttpOnly: true, 12.86 + }) 12.87 + 12.88 + redirectString := r.URL.Query().Get("redirect_to") 12.89 + if redirectString != "" { 12.90 + redirectURI, err := url.Parse(redirectString) 12.91 + if err != nil { 12.92 + // TODO: render a bad request error 12.93 + return 12.94 + } 12.95 + if !strings.HasSuffix("."+ctx.Config.LoginRedirectDomain, redirectURI.Host) && redirectURI.Host != ctx.Config.LoginRedirectDomain { 12.96 + // TODO: render a bad request error 12.97 + return 12.98 + } 12.99 + } else { 12.100 + redirectString = "https://" + ctx.Config.LoginRedirectDomain 12.101 + } 12.102 + http.Redirect(w, r, redirectString, http.StatusFound) 12.103 + return 12.104 +}
13.1 --- a/util.go Sat Aug 16 20:49:19 2014 -0400 13.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 13.3 @@ -1,102 +0,0 @@ 13.4 -package auth 13.5 - 13.6 -import ( 13.7 - "encoding/base64" 13.8 - "errors" 13.9 - "fmt" 13.10 - "net/http" 13.11 - "net/url" 13.12 - "strings" 13.13 - 13.14 - "code.google.com/p/go-uuid/uuid" 13.15 -) 13.16 - 13.17 -var ( 13.18 - BasicAuthNotSetError = errors.New("Authorization header not set.") 13.19 - InvalidBasicAuthTypeError = errors.New("Invalid basic auth type.") 13.20 - InvalidBasicAuthMessage = errors.New("Invalid basic auth format.") 13.21 -) 13.22 - 13.23 -// Parse basic authentication header 13.24 -type BasicAuth struct { 13.25 - Username string 13.26 - Password string 13.27 -} 13.28 - 13.29 -// Return authorization header data 13.30 -func CheckBasicAuth(r *http.Request) (BasicAuth, error) { 13.31 - if r.Header.Get("Authorization") == "" { 13.32 - return BasicAuth{}, BasicAuthNotSetError 13.33 - } 13.34 - 13.35 - s := strings.SplitN(r.Header.Get("Authorization"), " ", 2) 13.36 - if len(s) != 2 || s[0] != "Basic" { 13.37 - return BasicAuth{}, InvalidBasicAuthTypeError 13.38 - } 13.39 - 13.40 - b, err := base64.StdEncoding.DecodeString(s[1]) 13.41 - if err != nil { 13.42 - return BasicAuth{}, err 13.43 - } 13.44 - pair := strings.SplitN(string(b), ":", 2) 13.45 - if len(pair) != 2 { 13.46 - return BasicAuth{}, InvalidBasicAuthMessage 13.47 - } 13.48 - 13.49 - return BasicAuth{Username: pair[0], Password: pair[1]}, nil 13.50 -} 13.51 - 13.52 -// getClientAuth checks client basic authentication in params if allowed, 13.53 -// otherwise gets it from the header. 13.54 -func getClientAuth(r *http.Request, allowQueryParams bool) (BasicAuth, error) { 13.55 - 13.56 - if allowQueryParams { 13.57 - // Allow for auth without password 13.58 - if _, hasSecret := r.Form["client_secret"]; hasSecret { 13.59 - auth := BasicAuth{ 13.60 - Username: r.Form.Get("client_id"), 13.61 - Password: r.Form.Get("client_secret"), 13.62 - } 13.63 - if auth.Username != "" { 13.64 - return auth, nil 13.65 - } 13.66 - } 13.67 - } 13.68 - 13.69 - return CheckBasicAuth(r) 13.70 -} 13.71 - 13.72 -func newToken() string { 13.73 - return base64.StdEncoding.EncodeToString([]byte(uuid.New())) 13.74 -} 13.75 - 13.76 -// validateURI validates that redirectURI is contained in baseURI 13.77 -func validateURI(baseURI string, redirectURI string) error { 13.78 - if baseURI == "" || redirectURI == "" { 13.79 - return errors.New("urls cannot be blank.") 13.80 - } 13.81 - 13.82 - // parse base url 13.83 - base, err := url.Parse(baseURI) 13.84 - if err != nil { 13.85 - return err 13.86 - } 13.87 - 13.88 - // parse passed url 13.89 - redirect, err := url.Parse(redirectURI) 13.90 - if err != nil { 13.91 - return err 13.92 - } 13.93 - 13.94 - // must not have fragment 13.95 - if base.Fragment != "" || redirect.Fragment != "" { 13.96 - return errors.New("url must not include fragment.") 13.97 - } 13.98 - 13.99 - // check if urls match 13.100 - if base.Scheme == redirect.Scheme && base.Host == redirect.Host && len(redirect.Path) >= len(base.Path) && strings.HasPrefix(redirect.Path, base.Path) { 13.101 - return nil 13.102 - } 13.103 - 13.104 - return errors.New(fmt.Sprintf("urls don't validate: %s / %s\n", baseURI, redirectURI)) 13.105 -}
14.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 14.2 +++ b/util.go.old Mon Sep 01 09:13:52 2014 -0400 14.3 @@ -0,0 +1,102 @@ 14.4 +package auth 14.5 + 14.6 +import ( 14.7 + "encoding/base64" 14.8 + "errors" 14.9 + "fmt" 14.10 + "net/http" 14.11 + "net/url" 14.12 + "strings" 14.13 + 14.14 + "code.google.com/p/go-uuid/uuid" 14.15 +) 14.16 + 14.17 +var ( 14.18 + BasicAuthNotSetError = errors.New("Authorization header not set.") 14.19 + InvalidBasicAuthTypeError = errors.New("Invalid basic auth type.") 14.20 + InvalidBasicAuthMessage = errors.New("Invalid basic auth format.") 14.21 +) 14.22 + 14.23 +// Parse basic authentication header 14.24 +type BasicAuth struct { 14.25 + Username string 14.26 + Password string 14.27 +} 14.28 + 14.29 +// Return authorization header data 14.30 +func CheckBasicAuth(r *http.Request) (BasicAuth, error) { 14.31 + if r.Header.Get("Authorization") == "" { 14.32 + return BasicAuth{}, BasicAuthNotSetError 14.33 + } 14.34 + 14.35 + s := strings.SplitN(r.Header.Get("Authorization"), " ", 2) 14.36 + if len(s) != 2 || s[0] != "Basic" { 14.37 + return BasicAuth{}, InvalidBasicAuthTypeError 14.38 + } 14.39 + 14.40 + b, err := base64.StdEncoding.DecodeString(s[1]) 14.41 + if err != nil { 14.42 + return BasicAuth{}, err 14.43 + } 14.44 + pair := strings.SplitN(string(b), ":", 2) 14.45 + if len(pair) != 2 { 14.46 + return BasicAuth{}, InvalidBasicAuthMessage 14.47 + } 14.48 + 14.49 + return BasicAuth{Username: pair[0], Password: pair[1]}, nil 14.50 +} 14.51 + 14.52 +// getClientAuth checks client basic authentication in params if allowed, 14.53 +// otherwise gets it from the header. 14.54 +func getClientAuth(r *http.Request, allowQueryParams bool) (BasicAuth, error) { 14.55 + 14.56 + if allowQueryParams { 14.57 + // Allow for auth without password 14.58 + if _, hasSecret := r.Form["client_secret"]; hasSecret { 14.59 + auth := BasicAuth{ 14.60 + Username: r.Form.Get("client_id"), 14.61 + Password: r.Form.Get("client_secret"), 14.62 + } 14.63 + if auth.Username != "" { 14.64 + return auth, nil 14.65 + } 14.66 + } 14.67 + } 14.68 + 14.69 + return CheckBasicAuth(r) 14.70 +} 14.71 + 14.72 +func newToken() string { 14.73 + return base64.StdEncoding.EncodeToString([]byte(uuid.New())) 14.74 +} 14.75 + 14.76 +// validateURI validates that redirectURI is contained in baseURI 14.77 +func validateURI(baseURI string, redirectURI string) error { 14.78 + if baseURI == "" || redirectURI == "" { 14.79 + return errors.New("urls cannot be blank.") 14.80 + } 14.81 + 14.82 + // parse base url 14.83 + base, err := url.Parse(baseURI) 14.84 + if err != nil { 14.85 + return err 14.86 + } 14.87 + 14.88 + // parse passed url 14.89 + redirect, err := url.Parse(redirectURI) 14.90 + if err != nil { 14.91 + return err 14.92 + } 14.93 + 14.94 + // must not have fragment 14.95 + if base.Fragment != "" || redirect.Fragment != "" { 14.96 + return errors.New("url must not include fragment.") 14.97 + } 14.98 + 14.99 + // check if urls match 14.100 + if base.Scheme == redirect.Scheme && base.Host == redirect.Host && len(redirect.Path) >= len(base.Path) && strings.HasPrefix(redirect.Path, base.Path) { 14.101 + return nil 14.102 + } 14.103 + 14.104 + return errors.New(fmt.Sprintf("urls don't validate: %s / %s\n", baseURI, redirectURI)) 14.105 +}