| 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 openid | 5 package openid |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "fmt" | 8 "fmt" |
| 9 "net/http" | 9 "net/http" |
| 10 "net/url" | 10 "net/url" |
| 11 "time" | 11 "time" |
| 12 | 12 |
| 13 "github.com/julienschmidt/httprouter" | |
| 14 "golang.org/x/net/context" | 13 "golang.org/x/net/context" |
| 15 | 14 |
| 16 "github.com/luci/luci-go/common/clock" | 15 "github.com/luci/luci-go/common/clock" |
| 17 "github.com/luci/luci-go/common/errors" | 16 "github.com/luci/luci-go/common/errors" |
| 18 "github.com/luci/luci-go/common/logging" | 17 "github.com/luci/luci-go/common/logging" |
| 19 "github.com/luci/luci-go/server/auth" | 18 "github.com/luci/luci-go/server/auth" |
| 20 » "github.com/luci/luci-go/server/middleware" | 19 » "github.com/luci/luci-go/server/router" |
| 21 ) | 20 ) |
| 22 | 21 |
| 23 // These are installed into a HTTP router by AuthMethod.InstallHandlers(...). | 22 // These are installed into a HTTP router by AuthMethod.InstallHandlers(...). |
| 24 const ( | 23 const ( |
| 25 loginURL = "/auth/openid/login" | 24 loginURL = "/auth/openid/login" |
| 26 logoutURL = "/auth/openid/logout" | 25 logoutURL = "/auth/openid/logout" |
| 27 callbackURL = "/auth/openid/callback" | 26 callbackURL = "/auth/openid/callback" |
| 28 ) | 27 ) |
| 29 | 28 |
| 30 // AuthMethod implements auth.Method and auth.UsersAPI and can be used as | 29 // AuthMethod implements auth.Method and auth.UsersAPI and can be used as |
| (...skipping 14 matching lines...) Expand all Loading... |
| 45 Insecure bool | 44 Insecure bool |
| 46 | 45 |
| 47 // IncompatibleCookies is a list of cookies to remove when setting or cl
earing | 46 // IncompatibleCookies is a list of cookies to remove when setting or cl
earing |
| 48 // session cookie. It is useful to get rid of GAE cookies when OpenID co
okies | 47 // session cookie. It is useful to get rid of GAE cookies when OpenID co
okies |
| 49 // are being used. Having both is very confusing. | 48 // are being used. Having both is very confusing. |
| 50 IncompatibleCookies []string | 49 IncompatibleCookies []string |
| 51 } | 50 } |
| 52 | 51 |
| 53 // InstallHandlers installs HTTP handlers used in OpenID protocol. Must be | 52 // InstallHandlers installs HTTP handlers used in OpenID protocol. Must be |
| 54 // installed in server HTTP router for OpenID authentication flow to work. | 53 // installed in server HTTP router for OpenID authentication flow to work. |
| 55 func (m *AuthMethod) InstallHandlers(r *httprouter.Router, base middleware.Base)
{ | 54 func (m *AuthMethod) InstallHandlers(r *router.Router, base router.MiddlewareCha
in) { |
| 56 » r.GET(loginURL, base(m.loginHandler)) | 55 » r.GET(loginURL, base, m.loginHandler) |
| 57 » r.GET(logoutURL, base(m.logoutHandler)) | 56 » r.GET(logoutURL, base, m.logoutHandler) |
| 58 » r.GET(callbackURL, base(m.callbackHandler)) | 57 » r.GET(callbackURL, base, m.callbackHandler) |
| 59 } | 58 } |
| 60 | 59 |
| 61 // Warmup prepares local caches. It's optional. | 60 // Warmup prepares local caches. It's optional. |
| 62 func (m *AuthMethod) Warmup(c context.Context) error { | 61 func (m *AuthMethod) Warmup(c context.Context) error { |
| 63 cfg, err := fetchCachedSettings(c) | 62 cfg, err := fetchCachedSettings(c) |
| 64 if err != nil { | 63 if err != nil { |
| 65 return err | 64 return err |
| 66 } | 65 } |
| 67 _, err = fetchDiscoveryDoc(c, cfg.DiscoveryURL) | 66 _, err = fetchDiscoveryDoc(c, cfg.DiscoveryURL) |
| 68 return err | 67 return err |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 111 func (m *AuthMethod) LogoutURL(c context.Context, dest string) (string, error) { | 110 func (m *AuthMethod) LogoutURL(c context.Context, dest string) (string, error) { |
| 112 if m.SessionStore == nil { | 111 if m.SessionStore == nil { |
| 113 return "", ErrNotConfigured | 112 return "", ErrNotConfigured |
| 114 } | 113 } |
| 115 return makeRedirectURL(logoutURL, dest) | 114 return makeRedirectURL(logoutURL, dest) |
| 116 } | 115 } |
| 117 | 116 |
| 118 //// | 117 //// |
| 119 | 118 |
| 120 // loginHandler initiates login flow by redirecting user to OpenID login page. | 119 // loginHandler initiates login flow by redirecting user to OpenID login page. |
| 121 func (m *AuthMethod) loginHandler(c context.Context, rw http.ResponseWriter, r *
http.Request, p httprouter.Params) { | 120 func (m *AuthMethod) loginHandler(ctx *router.Context) { |
| 121 » c, rw, r := ctx.Context, ctx.Writer, ctx.Request |
| 122 |
| 122 dest, err := normalizeURL(r.URL.Query().Get("r")) | 123 dest, err := normalizeURL(r.URL.Query().Get("r")) |
| 123 if err != nil { | 124 if err != nil { |
| 124 replyError(c, rw, err, "Bad redirect URI (%q) - %s", dest, err) | 125 replyError(c, rw, err, "Bad redirect URI (%q) - %s", dest, err) |
| 125 return | 126 return |
| 126 } | 127 } |
| 127 | 128 |
| 128 cfg, err := fetchCachedSettings(c) | 129 cfg, err := fetchCachedSettings(c) |
| 129 if err != nil { | 130 if err != nil { |
| 130 replyError(c, rw, err, "Can't load OpenID settings - %s", err) | 131 replyError(c, rw, err, "Can't load OpenID settings - %s", err) |
| 131 return | 132 return |
| 132 } | 133 } |
| 133 | 134 |
| 134 // `state` will be propagated by OpenID backend and will eventually show
up | 135 // `state` will be propagated by OpenID backend and will eventually show
up |
| 135 // in callback URI handler. See callbackHandler. | 136 // in callback URI handler. See callbackHandler. |
| 136 state := map[string]string{ | 137 state := map[string]string{ |
| 137 "dest_url": dest, | 138 "dest_url": dest, |
| 138 "host_url": r.Host, | 139 "host_url": r.Host, |
| 139 } | 140 } |
| 140 authURI, err := authenticationURI(c, cfg, state) | 141 authURI, err := authenticationURI(c, cfg, state) |
| 141 if err != nil { | 142 if err != nil { |
| 142 replyError(c, rw, err, "Can't generate authentication URI - %s",
err) | 143 replyError(c, rw, err, "Can't generate authentication URI - %s",
err) |
| 143 return | 144 return |
| 144 } | 145 } |
| 145 http.Redirect(rw, r, authURI, http.StatusFound) | 146 http.Redirect(rw, r, authURI, http.StatusFound) |
| 146 } | 147 } |
| 147 | 148 |
| 148 // logoutHandler nukes active session and redirect back to destination URL. | 149 // logoutHandler nukes active session and redirect back to destination URL. |
| 149 func (m *AuthMethod) logoutHandler(c context.Context, rw http.ResponseWriter, r
*http.Request, p httprouter.Params) { | 150 func (m *AuthMethod) logoutHandler(ctx *router.Context) { |
| 151 » c, rw, r := ctx.Context, ctx.Writer, ctx.Request |
| 152 |
| 150 dest, err := normalizeURL(r.URL.Query().Get("r")) | 153 dest, err := normalizeURL(r.URL.Query().Get("r")) |
| 151 if err != nil { | 154 if err != nil { |
| 152 replyError(c, rw, err, "Bad redirect URI (%q) - %s", dest, err) | 155 replyError(c, rw, err, "Bad redirect URI (%q) - %s", dest, err) |
| 153 return | 156 return |
| 154 } | 157 } |
| 155 | 158 |
| 156 // Close a session if there's one. | 159 // Close a session if there's one. |
| 157 sid, err := decodeSessionCookie(c, r) | 160 sid, err := decodeSessionCookie(c, r) |
| 158 if err != nil { | 161 if err != nil { |
| 159 replyError(c, rw, err, "Error when decoding session cookie - %s"
, err) | 162 replyError(c, rw, err, "Error when decoding session cookie - %s"
, err) |
| 160 return | 163 return |
| 161 } | 164 } |
| 162 if sid != "" { | 165 if sid != "" { |
| 163 if err = m.SessionStore.CloseSession(c, sid); err != nil { | 166 if err = m.SessionStore.CloseSession(c, sid); err != nil { |
| 164 replyError(c, rw, err, "Error when closing the session -
%s", err) | 167 replyError(c, rw, err, "Error when closing the session -
%s", err) |
| 165 return | 168 return |
| 166 } | 169 } |
| 167 } | 170 } |
| 168 | 171 |
| 169 // Nuke all session cookies to get to a completely clean state. | 172 // Nuke all session cookies to get to a completely clean state. |
| 170 removeCookie(rw, r, sessionCookieName) | 173 removeCookie(rw, r, sessionCookieName) |
| 171 m.removeIncompatibleCookies(rw, r) | 174 m.removeIncompatibleCookies(rw, r) |
| 172 | 175 |
| 173 // Redirect to the final destination. | 176 // Redirect to the final destination. |
| 174 http.Redirect(rw, r, dest, http.StatusFound) | 177 http.Redirect(rw, r, dest, http.StatusFound) |
| 175 } | 178 } |
| 176 | 179 |
| 177 // callbackHandler handles redirect from OpenID backend. Parameters contain | 180 // callbackHandler handles redirect from OpenID backend. Parameters contain |
| 178 // authorization code that can be exchanged for user profile. | 181 // authorization code that can be exchanged for user profile. |
| 179 func (m *AuthMethod) callbackHandler(c context.Context, rw http.ResponseWriter,
r *http.Request, p httprouter.Params) { | 182 func (m *AuthMethod) callbackHandler(ctx *router.Context) { |
| 183 » c, rw, r := ctx.Context, ctx.Writer, ctx.Request |
| 184 |
| 180 // This code path is hit when user clicks "Deny" on consent page. | 185 // This code path is hit when user clicks "Deny" on consent page. |
| 181 q := r.URL.Query() | 186 q := r.URL.Query() |
| 182 errorMsg := q.Get("error") | 187 errorMsg := q.Get("error") |
| 183 if errorMsg != "" { | 188 if errorMsg != "" { |
| 184 replyError(c, rw, errors.New("login error"), "OpenID login error
: %s", errorMsg) | 189 replyError(c, rw, errors.New("login error"), "OpenID login error
: %s", errorMsg) |
| 185 return | 190 return |
| 186 } | 191 } |
| 187 | 192 |
| 188 // Validate inputs. | 193 // Validate inputs. |
| 189 code := q.Get("code") | 194 code := q.Get("code") |
| (...skipping 140 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 330 // HTTP 400 on fatal errors (that can happen only on bad requests). | 335 // HTTP 400 on fatal errors (that can happen only on bad requests). |
| 331 func replyError(c context.Context, rw http.ResponseWriter, err error, msg string
, args ...interface{}) { | 336 func replyError(c context.Context, rw http.ResponseWriter, err error, msg string
, args ...interface{}) { |
| 332 code := http.StatusBadRequest | 337 code := http.StatusBadRequest |
| 333 if errors.IsTransient(err) { | 338 if errors.IsTransient(err) { |
| 334 code = http.StatusInternalServerError | 339 code = http.StatusInternalServerError |
| 335 } | 340 } |
| 336 msg = fmt.Sprintf(msg, args...) | 341 msg = fmt.Sprintf(msg, args...) |
| 337 logging.Errorf(c, "HTTP %d: %s", code, msg) | 342 logging.Errorf(c, "HTTP %d: %s", code, msg) |
| 338 http.Error(rw, msg, code) | 343 http.Error(rw, msg, code) |
| 339 } | 344 } |
| OLD | NEW |