auth

Paddy 2014-08-01 Parent:7b9e0fc20256 Child:65c49af1ed3f

2:0aa843a306cd Go to Latest

auth/access.go

Pass context when checking session. When validating a session pass the context.

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