| OLD | NEW |
| 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 Loading... |
| 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 Loading... |
| 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 } |
| OLD | NEW |