Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(258)

Side by Side Diff: server/auth/auth.go

Issue 2830443003: auth: Refactor how authentication methods are passed to server/auth library. (Closed)
Patch Set: fix test Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « scheduler/appengine/ui/common.go ('k') | server/auth/auth_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2015 The LUCI Authors. All rights reserved. 1 // Copyright 2015 The LUCI Authors. All rights reserved.
2 // Use of this source code is governed under the Apache License, Version 2.0 2 // Use of this source code is governed under the Apache License, Version 2.0
3 // that can be found in the LICENSE file. 3 // that can be found in the LICENSE file.
4 4
5 package auth 5 package auth
6 6
7 import ( 7 import (
8 "fmt" 8 "fmt"
9 "net/http" 9 "net/http"
10 10
11 "golang.org/x/net/context" 11 "golang.org/x/net/context"
12 12
13 "github.com/luci/luci-go/common/errors" 13 "github.com/luci/luci-go/common/errors"
14 "github.com/luci/luci-go/common/logging" 14 "github.com/luci/luci-go/common/logging"
15 15
16 "github.com/luci/luci-go/server/auth/delegation" 16 "github.com/luci/luci-go/server/auth/delegation"
17 "github.com/luci/luci-go/server/auth/identity" 17 "github.com/luci/luci-go/server/auth/identity"
18 "github.com/luci/luci-go/server/auth/signing" 18 "github.com/luci/luci-go/server/auth/signing"
19 "github.com/luci/luci-go/server/router"
19 ) 20 )
20 21
21 var ( 22 var (
22 // ErrNotConfigured is returned by Authenticate if auth library wasn't 23 // ErrNotConfigured is returned by Authenticate if auth library wasn't
23 // properly initialized (see SetConfig). 24 // properly initialized (see SetConfig).
24 ErrNotConfigured = errors.New("auth: the library is not properly configu red") 25 ErrNotConfigured = errors.New("auth: the library is not properly configu red")
25 26
26 // ErrNoUsersAPI is returned by LoginURL and LogoutURL if none of 27 // ErrNoUsersAPI is returned by LoginURL and LogoutURL if none of
27 // the authentication methods support UsersAPI. 28 // the authentication methods support UsersAPI.
28 ErrNoUsersAPI = errors.New("auth: methods do not support login or logout URL") 29 ErrNoUsersAPI = errors.New("auth: methods do not support login or logout URL")
29 30
30 // ErrBadClientID is returned by Authenticate if caller is using 31 // ErrBadClientID is returned by Authenticate if caller is using
31 // non-whitelisted OAuth2 client. More info is in the log. 32 // non-whitelisted OAuth2 client. More info is in the log.
32 ErrBadClientID = errors.New("auth: OAuth client_id is not whitelisted") 33 ErrBadClientID = errors.New("auth: OAuth client_id is not whitelisted")
33 34
34 // ErrIPNotWhitelisted is returned when an account is restricted by an I P 35 // ErrIPNotWhitelisted is returned when an account is restricted by an I P
35 // whitelist and request's remote_addr is not in it. 36 // whitelist and request's remote_addr is not in it.
36 ErrIPNotWhitelisted = errors.New("auth: IP is not whitelisted") 37 ErrIPNotWhitelisted = errors.New("auth: IP is not whitelisted")
37 ) 38 )
38 39
39 // Method implements particular kind of low level authentication mechanism for 40 // Method implements a particular kind of low-level authentication mechanism.
40 // incoming requests. It may also optionally implement UsersAPI (if the method 41 //
41 // support login and logout URLs). Use type sniffing to figure out. 42 // It may also optionally implement UsersAPI (if the method support login and
43 // logout URLs).
44 //
45 // Methods are not usually used directly, but passed to Authenticator{...} that
46 // knows how to apply them.
42 type Method interface { 47 type Method interface {
43 // Authenticate extracts user information from the incoming request. 48 // Authenticate extracts user information from the incoming request.
49 //
44 // It returns: 50 // It returns:
45 // * (*User, nil) on success. 51 // * (*User, nil) on success.
46 // * (nil, nil) if the method is not applicable. 52 // * (nil, nil) if the method is not applicable.
47 // * (nil, error) if the method is applicable, but credentials are inv alid. 53 // * (nil, error) if the method is applicable, but credentials are inv alid.
48 Authenticate(context.Context, *http.Request) (*User, error) 54 Authenticate(context.Context, *http.Request) (*User, error)
49 } 55 }
50 56
51 // UsersAPI may be additionally implemented by Method if it supports login and 57 // UsersAPI may be additionally implemented by Method if it supports login and
52 // logout URLs. 58 // logout URLs.
53 type UsersAPI interface { 59 type UsersAPI interface {
(...skipping 27 matching lines...) Expand all
81 87
82 // Picture is URL of the user avatar. Optional, default "". 88 // Picture is URL of the user avatar. Optional, default "".
83 Picture string 89 Picture string
84 90
85 // ClientID is the ID of the pre-registered OAuth2 client so its identit y can 91 // ClientID is the ID of the pre-registered OAuth2 client so its identit y can
86 // be verified. Used only by authentication methods based on OAuth2. 92 // be verified. Used only by authentication methods based on OAuth2.
87 // See https://developers.google.com/console/help/#generatingoauth2 for more. 93 // See https://developers.google.com/console/help/#generatingoauth2 for more.
88 ClientID string 94 ClientID string
89 } 95 }
90 96
91 // Authenticator perform authentication of incoming requests. It is stateless 97 // Authenticator performs authentication of incoming requests.
92 // object that just describes what methods to try when authenticating current 98 //
93 // request. It is fine to create it on per-request basis. 99 // It is a stateless object configured with a list of methods to try when
94 type Authenticator []Method 100 // authenticating incoming requests. It implements Authenticate method that
101 // performs high-level authentication logic using the provided list of low-level
102 // auth methods.
103 //
104 // Note that most likely you don't need to instantiate this object directly.
105 // Use Authenticate middleware instead. Authenticator is exposed publicly only
106 // to be used in advanced cases, when you need to fine-tune authentication
107 // behavior.
108 type Authenticator struct {
109 » Methods []Method // a list of authentication methods to try
110 }
95 111
96 // Authenticate authenticates incoming requests and returns new context.Context 112 // GetMiddleware returns a middleware that uses this Authenticator for
97 // with State stored into it. Returns error if credentials are provided, but 113 // authentication.
98 // invalid. If no credentials are provided (i.e. the request is anonymous), 114 //
99 // finishes successfully, but in that case State.Identity() will return 115 // It uses a.Authenticate internally and handles errors appropriately.
100 // AnonymousIdentity. 116 func (a *Authenticator) GetMiddleware() router.Middleware {
101 func (a Authenticator) Authenticate(c context.Context, r *http.Request) (context .Context, error) { 117 » return func(c *router.Context, next router.Handler) {
118 » » ctx, err := a.Authenticate(c.Context, c.Request)
119 » » switch {
120 » » case errors.IsTransient(err):
121 » » » replyError(c.Context, c.Writer, 500, "Transient error du ring authentication", err)
122 » » case err != nil:
123 » » » replyError(c.Context, c.Writer, 401, "Authentication err or", err)
124 » » default:
125 » » » c.Context = ctx
126 » » » next(c)
127 » » }
128 » }
129 }
130
131 // Authenticate authenticates the requests and adds State into the context.
132 //
133 // Returns an error if credentials are provided, but invalid. If no credentials
134 // are provided (i.e. the request is anonymous), finishes successfully, but in
135 // that case State.Identity() returns AnonymousIdentity.
136 func (a *Authenticator) Authenticate(c context.Context, r *http.Request) (contex t.Context, error) {
102 report := durationReporter(c, authenticateDuration) 137 report := durationReporter(c, authenticateDuration)
103 138
104 // Make it known to handlers what authentication methods are allowed.
105 // This is important for LoginURL and LogoutURL functions.
106 c = SetAuthenticator(c, a)
107
108 // We will need working DB factory below to check IP whitelist. 139 // We will need working DB factory below to check IP whitelist.
109 cfg := GetConfig(c) 140 cfg := GetConfig(c)
110 » if cfg == nil || cfg.DBProvider == nil { 141 » if cfg == nil || cfg.DBProvider == nil || len(a.Methods) == 0 {
111 report(ErrNotConfigured, "ERROR_NOT_CONFIGURED") 142 report(ErrNotConfigured, "ERROR_NOT_CONFIGURED")
112 return nil, ErrNotConfigured 143 return nil, ErrNotConfigured
113 } 144 }
114 145
115 // Pick first authentication method that applies. 146 // Pick first authentication method that applies.
116 » var s state 147 » s := state{authenticator: a}
117 » for _, m := range a { 148 » for _, m := range a.Methods {
118 var err error 149 var err error
119 s.user, err = m.Authenticate(c, r) 150 s.user, err = m.Authenticate(c, r)
120 if err != nil { 151 if err != nil {
121 report(err, "ERROR_BROKEN_CREDS") // e.g. malformed OAut h token 152 report(err, "ERROR_BROKEN_CREDS") // e.g. malformed OAut h token
122 return nil, err 153 return nil, err
123 } 154 }
124 if s.user != nil { 155 if s.user != nil {
125 if err = s.user.Identity.Validate(); err != nil { 156 if err = s.user.Identity.Validate(); err != nil {
126 report(err, "ERROR_BROKEN_IDENTITY") // a weird looking email address 157 report(err, "ERROR_BROKEN_IDENTITY") // a weird looking email address
127 return nil, err 158 return nil, err
(...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after
230 "peerID": s.peerIdent, 261 "peerID": s.peerIdent,
231 "delegatedID": delegatedIdentity, 262 "delegatedID": delegatedIdentity,
232 }.Debugf(c, "auth: Using delegation") 263 }.Debugf(c, "auth: Using delegation")
233 } 264 }
234 265
235 // Inject auth state. 266 // Inject auth state.
236 report(nil, "SUCCESS") 267 report(nil, "SUCCESS")
237 return WithState(c, &s), nil 268 return WithState(c, &s), nil
238 } 269 }
239 270
240 // usersAPI returns implementation of UsersAPI by examining Methods. Returns nil 271 // usersAPI returns implementation of UsersAPI by examining Methods.
241 // if none of Methods implement UsersAPI. 272 //
242 func (a Authenticator) usersAPI() UsersAPI { 273 // Returns nil if none of Methods implement UsersAPI.
243 » for _, m := range a { 274 func (a *Authenticator) usersAPI() UsersAPI {
275 » for _, m := range a.Methods {
244 if api, ok := m.(UsersAPI); ok { 276 if api, ok := m.(UsersAPI); ok {
245 return api 277 return api
246 } 278 }
247 } 279 }
248 return nil 280 return nil
249 } 281 }
250 282
251 // LoginURL returns a URL that, when visited, prompts the user to sign in, 283 // LoginURL returns a URL that, when visited, prompts the user to sign in,
252 // then redirects the user to the URL specified by dest. 284 // then redirects the user to the URL specified by dest.
253 func (a Authenticator) LoginURL(c context.Context, dest string) (string, error) { 285 //
286 // Returns ErrNoUsersAPI if none of the authentication methods support login
287 // URLs.
288 func (a *Authenticator) LoginURL(c context.Context, dest string) (string, error) {
254 if api := a.usersAPI(); api != nil { 289 if api := a.usersAPI(); api != nil {
255 return api.LoginURL(c, dest) 290 return api.LoginURL(c, dest)
256 } 291 }
257 return "", ErrNoUsersAPI 292 return "", ErrNoUsersAPI
258 } 293 }
259 294
260 // LogoutURL returns a URL that, when visited, signs the user out, 295 // LogoutURL returns a URL that, when visited, signs the user out, then
261 // then redirects the user to the URL specified by dest. 296 // redirects the user to the URL specified by dest.
262 func (a Authenticator) LogoutURL(c context.Context, dest string) (string, error) { 297 //
298 // Returns ErrNoUsersAPI if none of the authentication methods support login
299 // URLs.
300 func (a *Authenticator) LogoutURL(c context.Context, dest string) (string, error ) {
263 if api := a.usersAPI(); api != nil { 301 if api := a.usersAPI(); api != nil {
264 return api.LogoutURL(c, dest) 302 return api.LogoutURL(c, dest)
265 } 303 }
266 return "", ErrNoUsersAPI 304 return "", ErrNoUsersAPI
267 } 305 }
268 306
269 //// 307 ////
270 308
309 // replyError logs the error and writes it to ResponseWriter.
310 func replyError(c context.Context, rw http.ResponseWriter, code int, msg string, err error) {
311 logging.WithError(err).Errorf(c, "HTTP %d: %s", code, msg)
312 http.Error(rw, msg, code)
313 }
314
271 // getOwnServiceIdentity returns 'service:<appID>' identity of the current 315 // getOwnServiceIdentity returns 'service:<appID>' identity of the current
272 // service. 316 // service.
273 func getOwnServiceIdentity(c context.Context, signer signing.Signer) (identity.I dentity, error) { 317 func getOwnServiceIdentity(c context.Context, signer signing.Signer) (identity.I dentity, error) {
274 if signer == nil { 318 if signer == nil {
275 return "", ErrNotConfigured 319 return "", ErrNotConfigured
276 } 320 }
277 serviceInfo, err := signer.ServiceInfo(c) 321 serviceInfo, err := signer.ServiceInfo(c)
278 if err != nil { 322 if err != nil {
279 return "", err 323 return "", err
280 } 324 }
281 return identity.MakeIdentity("service:" + serviceInfo.AppID) 325 return identity.MakeIdentity("service:" + serviceInfo.AppID)
282 } 326 }
OLDNEW
« no previous file with comments | « scheduler/appengine/ui/common.go ('k') | server/auth/auth_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698