Merge pull request #766 from ericchiang/implicit-flow
server: fixes for the implicit and hybrid flow
This commit is contained in:
commit
c66cce8b40
@ -144,12 +144,23 @@ func (s *Server) discoveryHandler() (http.Handler, error) {
|
|||||||
|
|
||||||
// handleAuthorization handles the OAuth2 auth endpoint.
|
// handleAuthorization handles the OAuth2 auth endpoint.
|
||||||
func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {
|
||||||
authReq, err := s.parseAuthorizationRequest(s.supportedResponseTypes, r)
|
authReq, err := s.parseAuthorizationRequest(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Errorf("Failed to parse authorization request: %v", err)
|
s.logger.Errorf("Failed to parse authorization request: %v", err)
|
||||||
s.renderError(w, http.StatusInternalServerError, "Failed to connect to the database.")
|
if handler, ok := err.Handle(); ok {
|
||||||
|
// client_id and redirect_uri checked out and we can redirect back to
|
||||||
|
// the client with the error.
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Otherwise render the error to the user.
|
||||||
|
//
|
||||||
|
// TODO(ericchiang): Should we just always render the error?
|
||||||
|
s.renderError(w, err.Status(), err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
authReq.Expiry = s.now().Add(time.Minute * 30)
|
authReq.Expiry = s.now().Add(time.Minute * 30)
|
||||||
if err := s.storage.CreateAuthRequest(authReq); err != nil {
|
if err := s.storage.CreateAuthRequest(authReq); err != nil {
|
||||||
s.logger.Errorf("Failed to create authorization request: %v", err)
|
s.logger.Errorf("Failed to create authorization request: %v", err)
|
||||||
@ -441,12 +452,25 @@ func (s *Server) sendCodeResponse(w http.ResponseWriter, r *http.Request, authRe
|
|||||||
s.renderError(w, http.StatusInternalServerError, "Invalid redirect URI.")
|
s.renderError(w, http.StatusInternalServerError, "Invalid redirect URI.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
q := u.Query()
|
|
||||||
|
var (
|
||||||
|
// Was the initial request using the implicit or hybrid flow instead of
|
||||||
|
// the "normal" code flow?
|
||||||
|
implicitOrHybrid = false
|
||||||
|
|
||||||
|
// Only present in hybrid or code flow. code.ID == "" if this is not set.
|
||||||
|
code storage.AuthCode
|
||||||
|
|
||||||
|
// ID token returned immediately if the response_type includes "id_token".
|
||||||
|
// Only valid for implicit and hybrid flows.
|
||||||
|
idToken string
|
||||||
|
idTokenExpiry time.Time
|
||||||
|
)
|
||||||
|
|
||||||
for _, responseType := range authReq.ResponseTypes {
|
for _, responseType := range authReq.ResponseTypes {
|
||||||
switch responseType {
|
switch responseType {
|
||||||
case responseTypeCode:
|
case responseTypeCode:
|
||||||
code := storage.AuthCode{
|
code = storage.AuthCode{
|
||||||
ID: storage.NewID(),
|
ID: storage.NewID(),
|
||||||
ClientID: authReq.ClientID,
|
ClientID: authReq.ClientID,
|
||||||
ConnectorID: authReq.ConnectorID,
|
ConnectorID: authReq.ConnectorID,
|
||||||
@ -463,32 +487,73 @@ func (s *Server) sendCodeResponse(w http.ResponseWriter, r *http.Request, authRe
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implicit and hybrid flows that try to use the OOB redirect URI are
|
||||||
|
// rejected earlier. If we got here we're using the code flow.
|
||||||
if authReq.RedirectURI == redirectURIOOB {
|
if authReq.RedirectURI == redirectURIOOB {
|
||||||
if err := s.templates.oob(w, code.ID); err != nil {
|
if err := s.templates.oob(w, code.ID); err != nil {
|
||||||
s.logger.Errorf("Server template error: %v", err)
|
s.logger.Errorf("Server template error: %v", err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
q.Set("code", code.ID)
|
|
||||||
case responseTypeToken:
|
case responseTypeToken:
|
||||||
idToken, expiry, err := s.newIDToken(authReq.ClientID, authReq.Claims, authReq.Scopes, authReq.Nonce)
|
implicitOrHybrid = true
|
||||||
|
case responseTypeIDToken:
|
||||||
|
implicitOrHybrid = true
|
||||||
|
var err error
|
||||||
|
idToken, idTokenExpiry, err = s.newIDToken(authReq.ClientID, authReq.Claims, authReq.Scopes, authReq.Nonce)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Errorf("failed to create ID token: %v", err)
|
s.logger.Errorf("failed to create ID token: %v", err)
|
||||||
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
|
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
v := url.Values{}
|
|
||||||
v.Set("access_token", storage.NewID())
|
|
||||||
v.Set("token_type", "bearer")
|
|
||||||
v.Set("id_token", idToken)
|
|
||||||
v.Set("state", authReq.State)
|
|
||||||
v.Set("expires_in", strconv.Itoa(int(expiry.Sub(s.now()).Seconds())))
|
|
||||||
u.Fragment = v.Encode()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if implicitOrHybrid {
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("access_token", storage.NewID())
|
||||||
|
v.Set("token_type", "bearer")
|
||||||
|
v.Set("state", authReq.State)
|
||||||
|
if idToken != "" {
|
||||||
|
v.Set("id_token", idToken)
|
||||||
|
// The hybrid flow with only "code token" or "code id_token" doesn't return an
|
||||||
|
// "expires_in" value. If "code" wasn't provided, indicating the implicit flow,
|
||||||
|
// don't add it.
|
||||||
|
//
|
||||||
|
// https://openid.net/specs/openid-connect-core-1_0.html#HybridAuthResponse
|
||||||
|
if code.ID == "" {
|
||||||
|
v.Set("expires_in", strconv.Itoa(int(idTokenExpiry.Sub(s.now()).Seconds())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if code.ID != "" {
|
||||||
|
v.Set("code", code.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implicit and hybrid flows return their values as part of the fragment.
|
||||||
|
//
|
||||||
|
// HTTP/1.1 303 See Other
|
||||||
|
// Location: https://client.example.org/cb#
|
||||||
|
// access_token=SlAV32hkKG
|
||||||
|
// &token_type=bearer
|
||||||
|
// &id_token=eyJ0 ... NiJ9.eyJ1c ... I6IjIifX0.DeWt4Qu ... ZXso
|
||||||
|
// &expires_in=3600
|
||||||
|
// &state=af0ifjsldkj
|
||||||
|
//
|
||||||
|
u.Fragment = v.Encode()
|
||||||
|
} else {
|
||||||
|
// The code flow add values to the URL query.
|
||||||
|
//
|
||||||
|
// HTTP/1.1 303 See Other
|
||||||
|
// Location: https://client.example.org/cb?
|
||||||
|
// code=SplxlOBeZQQYbYS6WxSbIA
|
||||||
|
// &state=af0ifjsldkj
|
||||||
|
//
|
||||||
|
q := u.Query()
|
||||||
|
q.Set("code", code.ID)
|
||||||
q.Set("state", authReq.State)
|
q.Set("state", authReq.State)
|
||||||
u.RawQuery = q.Encode()
|
u.RawQuery = q.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
http.Redirect(w, r, u.String(), http.StatusSeeOther)
|
http.Redirect(w, r, u.String(), http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,24 @@ type authErr struct {
|
|||||||
Description string
|
Description string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (err *authErr) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (err *authErr) Status() int {
|
||||||
|
if err.State == errServerError {
|
||||||
|
return http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
return http.StatusBadRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *authErr) Error() string {
|
||||||
|
return err.Description
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *authErr) Handle() (http.Handler, bool) {
|
||||||
|
// Didn't get a valid redirect URI.
|
||||||
|
if err.RedirectURI == "" {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
hf := func(w http.ResponseWriter, r *http.Request) {
|
||||||
v := url.Values{}
|
v := url.Values{}
|
||||||
v.Add("state", err.State)
|
v.Add("state", err.State)
|
||||||
v.Add("error", err.Type)
|
v.Add("error", err.Type)
|
||||||
@ -39,6 +56,8 @@ func (err *authErr) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
http.Redirect(w, r, redirectURI, http.StatusSeeOther)
|
http.Redirect(w, r, redirectURI, http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
return http.HandlerFunc(hf), true
|
||||||
|
}
|
||||||
|
|
||||||
func tokenErr(w http.ResponseWriter, typ, description string, statusCode int) error {
|
func tokenErr(w http.ResponseWriter, typ, description string, statusCode int) error {
|
||||||
data := struct {
|
data := struct {
|
||||||
@ -192,20 +211,19 @@ func (s *Server) newIDToken(clientID string, claims storage.Claims, scopes []str
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse the initial request from the OAuth2 client.
|
// parse the initial request from the OAuth2 client.
|
||||||
//
|
func (s *Server) parseAuthorizationRequest(r *http.Request) (req storage.AuthRequest, oauth2Err *authErr) {
|
||||||
// For correctness the logic is largely copied from https://github.com/RangelReale/osin.
|
q := r.URL.Query()
|
||||||
func (s *Server) parseAuthorizationRequest(supportedResponseTypes map[string]bool, r *http.Request) (req storage.AuthRequest, oauth2Err *authErr) {
|
redirectURI, err := url.QueryUnescape(q.Get("redirect_uri"))
|
||||||
if err := r.ParseForm(); err != nil {
|
|
||||||
return req, &authErr{"", "", errInvalidRequest, "Failed to parse request."}
|
|
||||||
}
|
|
||||||
|
|
||||||
redirectURI, err := url.QueryUnescape(r.Form.Get("redirect_uri"))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return req, &authErr{"", "", errInvalidRequest, "No redirect_uri provided."}
|
return req, &authErr{"", "", errInvalidRequest, "No redirect_uri provided."}
|
||||||
}
|
}
|
||||||
state := r.FormValue("state")
|
|
||||||
|
|
||||||
clientID := r.Form.Get("client_id")
|
clientID := q.Get("client_id")
|
||||||
|
state := q.Get("state")
|
||||||
|
nonce := q.Get("nonce")
|
||||||
|
// Some clients, like the old go-oidc, provide extra whitespace. Tolerate this.
|
||||||
|
scopes := strings.Fields(q.Get("scope"))
|
||||||
|
responseTypes := strings.Fields(q.Get("response_type"))
|
||||||
|
|
||||||
client, err := s.storage.GetClient(clientID)
|
client, err := s.storage.GetClient(clientID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -222,12 +240,11 @@ func (s *Server) parseAuthorizationRequest(supportedResponseTypes map[string]boo
|
|||||||
return req, &authErr{"", "", errInvalidRequest, description}
|
return req, &authErr{"", "", errInvalidRequest, description}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// From here on out, we want to redirect back to the client with an error.
|
||||||
newErr := func(typ, format string, a ...interface{}) *authErr {
|
newErr := func(typ, format string, a ...interface{}) *authErr {
|
||||||
return &authErr{state, redirectURI, typ, fmt.Sprintf(format, a...)}
|
return &authErr{state, redirectURI, typ, fmt.Sprintf(format, a...)}
|
||||||
}
|
}
|
||||||
|
|
||||||
scopes := strings.Fields(r.Form.Get("scope"))
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
unrecognized []string
|
unrecognized []string
|
||||||
invalidScopes []string
|
invalidScopes []string
|
||||||
@ -247,7 +264,7 @@ func (s *Server) parseAuthorizationRequest(supportedResponseTypes map[string]boo
|
|||||||
|
|
||||||
isTrusted, err := s.validateCrossClientTrust(clientID, peerID)
|
isTrusted, err := s.validateCrossClientTrust(clientID, peerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return req, newErr(errServerError, "")
|
return req, newErr(errServerError, "Internal server error.")
|
||||||
}
|
}
|
||||||
if !isTrusted {
|
if !isTrusted {
|
||||||
invalidScopes = append(invalidScopes, scope)
|
invalidScopes = append(invalidScopes, scope)
|
||||||
@ -264,37 +281,61 @@ func (s *Server) parseAuthorizationRequest(supportedResponseTypes map[string]boo
|
|||||||
return req, newErr("invalid_scope", "Client can't request scope(s) %q", invalidScopes)
|
return req, newErr("invalid_scope", "Client can't request scope(s) %q", invalidScopes)
|
||||||
}
|
}
|
||||||
|
|
||||||
nonce := r.Form.Get("nonce")
|
var rt struct {
|
||||||
responseTypes := strings.Split(r.Form.Get("response_type"), " ")
|
code bool
|
||||||
|
idToken bool
|
||||||
|
token bool
|
||||||
|
}
|
||||||
|
|
||||||
for _, responseType := range responseTypes {
|
for _, responseType := range responseTypes {
|
||||||
if !supportedResponseTypes[responseType] {
|
switch responseType {
|
||||||
|
case responseTypeCode:
|
||||||
|
rt.code = true
|
||||||
|
case responseTypeIDToken:
|
||||||
|
rt.idToken = true
|
||||||
|
case responseTypeToken:
|
||||||
|
rt.token = true
|
||||||
|
default:
|
||||||
return req, newErr("invalid_request", "Invalid response type %q", responseType)
|
return req, newErr("invalid_request", "Invalid response type %q", responseType)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch responseType {
|
if !s.supportedResponseTypes[responseType] {
|
||||||
case responseTypeCode:
|
return req, newErr(errUnsupportedResponseType, "Unsupported response type %q", responseType)
|
||||||
case responseTypeToken:
|
}
|
||||||
// Implicit flow requires a nonce value.
|
}
|
||||||
|
|
||||||
|
if len(responseTypes) == 0 {
|
||||||
|
return req, newErr("invalid_requests", "No response_type provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
if rt.token && !rt.code && !rt.idToken {
|
||||||
|
// "token" can't be provided by its own.
|
||||||
|
//
|
||||||
|
// https://openid.net/specs/openid-connect-core-1_0.html#Authentication
|
||||||
|
return req, newErr("invalid_request", "Response type 'token' must be provided with type 'id_token' and/or 'code'")
|
||||||
|
}
|
||||||
|
if !rt.code {
|
||||||
|
// Either "id_token code" or "id_token" has been provided which implies the
|
||||||
|
// implicit flow. Implicit flow requires a nonce value.
|
||||||
|
//
|
||||||
// https://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthRequest
|
// https://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthRequest
|
||||||
if nonce == "" {
|
if nonce == "" {
|
||||||
return req, newErr("invalid_request", "Response type 'token' requires a 'nonce' value.")
|
return req, newErr("invalid_request", "Response type 'token' requires a 'nonce' value.")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if rt.token {
|
||||||
if redirectURI == redirectURIOOB {
|
if redirectURI == redirectURIOOB {
|
||||||
err := fmt.Sprintf("Cannot use response type 'token' with redirect_uri '%s'.", redirectURIOOB)
|
err := fmt.Sprintf("Cannot use response type 'token' with redirect_uri '%s'.", redirectURIOOB)
|
||||||
return req, newErr("invalid_request", err)
|
return req, newErr("invalid_request", err)
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
return req, newErr("invalid_request", "Invalid response type %q", responseType)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return storage.AuthRequest{
|
return storage.AuthRequest{
|
||||||
ID: storage.NewID(),
|
ID: storage.NewID(),
|
||||||
ClientID: client.ID,
|
ClientID: client.ID,
|
||||||
State: r.Form.Get("state"),
|
State: state,
|
||||||
Nonce: nonce,
|
Nonce: nonce,
|
||||||
ForceApprovalPrompt: r.Form.Get("approval_prompt") == "force",
|
ForceApprovalPrompt: q.Get("approval_prompt") == "force",
|
||||||
Scopes: scopes,
|
Scopes: scopes,
|
||||||
RedirectURI: redirectURI,
|
RedirectURI: redirectURI,
|
||||||
ResponseTypes: responseTypes,
|
ResponseTypes: responseTypes,
|
||||||
|
@ -1 +1,150 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/coreos/dex/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseAuthorizationRequest(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
clients []storage.Client
|
||||||
|
supportedResponseTypes []string
|
||||||
|
|
||||||
|
queryParams map[string]string
|
||||||
|
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "normal request",
|
||||||
|
clients: []storage.Client{
|
||||||
|
{
|
||||||
|
ID: "foo",
|
||||||
|
RedirectURIs: []string{"https://example.com/foo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
supportedResponseTypes: []string{"code"},
|
||||||
|
queryParams: map[string]string{
|
||||||
|
"client_id": "foo",
|
||||||
|
"redirect_uri": "https://example.com/foo",
|
||||||
|
"response_type": "code",
|
||||||
|
"scope": "openid email profile",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid client id",
|
||||||
|
clients: []storage.Client{
|
||||||
|
{
|
||||||
|
ID: "foo",
|
||||||
|
RedirectURIs: []string{"https://example.com/foo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
supportedResponseTypes: []string{"code"},
|
||||||
|
queryParams: map[string]string{
|
||||||
|
"client_id": "bar",
|
||||||
|
"redirect_uri": "https://example.com/foo",
|
||||||
|
"response_type": "code",
|
||||||
|
"scope": "openid email profile",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid redirect uri",
|
||||||
|
clients: []storage.Client{
|
||||||
|
{
|
||||||
|
ID: "bar",
|
||||||
|
RedirectURIs: []string{"https://example.com/bar"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
supportedResponseTypes: []string{"code"},
|
||||||
|
queryParams: map[string]string{
|
||||||
|
"client_id": "bar",
|
||||||
|
"redirect_uri": "https://example.com/foo",
|
||||||
|
"response_type": "code",
|
||||||
|
"scope": "openid email profile",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "implicit flow",
|
||||||
|
clients: []storage.Client{
|
||||||
|
{
|
||||||
|
ID: "bar",
|
||||||
|
RedirectURIs: []string{"https://example.com/bar"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
supportedResponseTypes: []string{"code", "id_token", "token"},
|
||||||
|
queryParams: map[string]string{
|
||||||
|
"client_id": "bar",
|
||||||
|
"redirect_uri": "https://example.com/bar",
|
||||||
|
"response_type": "code id_token",
|
||||||
|
"scope": "openid email profile",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unsupported response type",
|
||||||
|
clients: []storage.Client{
|
||||||
|
{
|
||||||
|
ID: "bar",
|
||||||
|
RedirectURIs: []string{"https://example.com/bar"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
supportedResponseTypes: []string{"code"},
|
||||||
|
queryParams: map[string]string{
|
||||||
|
"client_id": "bar",
|
||||||
|
"redirect_uri": "https://example.com/bar",
|
||||||
|
"response_type": "code id_token",
|
||||||
|
"scope": "openid email profile",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "only token response type",
|
||||||
|
clients: []storage.Client{
|
||||||
|
{
|
||||||
|
ID: "bar",
|
||||||
|
RedirectURIs: []string{"https://example.com/bar"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
supportedResponseTypes: []string{"code", "id_token", "token"},
|
||||||
|
queryParams: map[string]string{
|
||||||
|
"client_id": "bar",
|
||||||
|
"redirect_uri": "https://example.com/bar",
|
||||||
|
"response_type": "token",
|
||||||
|
"scope": "openid email profile",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
func() {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
httpServer, server := newTestServer(ctx, t, func(c *Config) {
|
||||||
|
c.SupportedResponseTypes = tc.supportedResponseTypes
|
||||||
|
c.Storage = storage.WithStaticClients(c.Storage, tc.clients)
|
||||||
|
})
|
||||||
|
defer httpServer.Close()
|
||||||
|
|
||||||
|
params := url.Values{}
|
||||||
|
for k, v := range tc.queryParams {
|
||||||
|
params.Set(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", httpServer.URL+"/auth?"+params.Encode(), nil)
|
||||||
|
_, err := server.parseAuthorizationRequest(req)
|
||||||
|
if err != nil && !tc.wantErr {
|
||||||
|
t.Errorf("%s: %v", tc.name, err)
|
||||||
|
}
|
||||||
|
if err == nil && tc.wantErr {
|
||||||
|
t.Errorf("%s: expected error", tc.name)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -130,6 +130,7 @@ func (k keyRotater) rotate() error {
|
|||||||
|
|
||||||
// Remove expired verification keys.
|
// Remove expired verification keys.
|
||||||
i := 0
|
i := 0
|
||||||
|
|
||||||
for _, key := range keys.VerificationKeys {
|
for _, key := range keys.VerificationKeys {
|
||||||
if !key.Expiry.After(tNow) {
|
if !key.Expiry.After(tNow) {
|
||||||
keys.VerificationKeys[i] = key
|
keys.VerificationKeys[i] = key
|
||||||
|
@ -159,7 +159,7 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy)
|
|||||||
supported := make(map[string]bool)
|
supported := make(map[string]bool)
|
||||||
for _, respType := range c.SupportedResponseTypes {
|
for _, respType := range c.SupportedResponseTypes {
|
||||||
switch respType {
|
switch respType {
|
||||||
case responseTypeCode, responseTypeToken:
|
case responseTypeCode, responseTypeIDToken, responseTypeToken:
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported response_type %q", respType)
|
return nil, fmt.Errorf("unsupported response_type %q", respType)
|
||||||
}
|
}
|
||||||
|
@ -510,7 +510,7 @@ func TestOAuth2ImplicitFlow(t *testing.T) {
|
|||||||
|
|
||||||
httpServer, s := newTestServer(ctx, t, func(c *Config) {
|
httpServer, s := newTestServer(ctx, t, func(c *Config) {
|
||||||
// Enable support for the implicit flow.
|
// Enable support for the implicit flow.
|
||||||
c.SupportedResponseTypes = []string{"code", "token"}
|
c.SupportedResponseTypes = []string{"code", "token", "id_token"}
|
||||||
})
|
})
|
||||||
defer httpServer.Close()
|
defer httpServer.Close()
|
||||||
|
|
||||||
@ -553,7 +553,7 @@ func TestOAuth2ImplicitFlow(t *testing.T) {
|
|||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
u := oauth2Config.AuthCodeURL(state, oauth2.SetAuthURLParam("response_type", "token"), oidc.Nonce(nonce))
|
u := oauth2Config.AuthCodeURL(state, oauth2.SetAuthURLParam("response_type", "id_token token"), oidc.Nonce(nonce))
|
||||||
http.Redirect(w, r, u, http.StatusSeeOther)
|
http.Redirect(w, r, u, http.StatusSeeOther)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user