|
|
|
@ -710,7 +710,7 @@ func (s *Server) sendCodeResponse(w http.ResponseWriter, r *http.Request, authRe |
|
|
|
implicitOrHybrid = true |
|
|
|
implicitOrHybrid = true |
|
|
|
var err error |
|
|
|
var err error |
|
|
|
|
|
|
|
|
|
|
|
accessToken, err = s.newAccessToken(authReq.ClientID, authReq.Claims, authReq.Scopes, authReq.Nonce, authReq.ConnectorID) |
|
|
|
accessToken, _, err = s.newAccessToken(authReq.ClientID, authReq.Claims, authReq.Scopes, authReq.Nonce, authReq.ConnectorID) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
s.logger.Errorf("failed to create new access token: %v", err) |
|
|
|
s.logger.Errorf("failed to create new access token: %v", err) |
|
|
|
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) |
|
|
|
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) |
|
|
|
@ -830,6 +830,11 @@ func (s *Server) handleToken(w http.ResponseWriter, r *http.Request) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
grantType := r.PostFormValue("grant_type") |
|
|
|
grantType := r.PostFormValue("grant_type") |
|
|
|
|
|
|
|
if !contains(s.supportedGrantTypes, grantType) { |
|
|
|
|
|
|
|
s.logger.Errorf("unsupported grant type: %v", grantType) |
|
|
|
|
|
|
|
s.tokenErrHelper(w, errUnsupportedGrantType, "", http.StatusBadRequest) |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
switch grantType { |
|
|
|
switch grantType { |
|
|
|
case grantTypeDeviceCode: |
|
|
|
case grantTypeDeviceCode: |
|
|
|
s.handleDeviceToken(w, r) |
|
|
|
s.handleDeviceToken(w, r) |
|
|
|
@ -839,6 +844,8 @@ func (s *Server) handleToken(w http.ResponseWriter, r *http.Request) { |
|
|
|
s.withClientFromStorage(w, r, s.handleRefreshToken) |
|
|
|
s.withClientFromStorage(w, r, s.handleRefreshToken) |
|
|
|
case grantTypePassword: |
|
|
|
case grantTypePassword: |
|
|
|
s.withClientFromStorage(w, r, s.handlePasswordGrant) |
|
|
|
s.withClientFromStorage(w, r, s.handlePasswordGrant) |
|
|
|
|
|
|
|
case grantTypeTokenExchange: |
|
|
|
|
|
|
|
s.withClientFromStorage(w, r, s.handleTokenExchange) |
|
|
|
default: |
|
|
|
default: |
|
|
|
s.tokenErrHelper(w, errUnsupportedGrantType, "", http.StatusBadRequest) |
|
|
|
s.tokenErrHelper(w, errUnsupportedGrantType, "", http.StatusBadRequest) |
|
|
|
} |
|
|
|
} |
|
|
|
@ -917,7 +924,7 @@ func (s *Server) handleAuthCode(w http.ResponseWriter, r *http.Request, client s |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (s *Server) exchangeAuthCode(w http.ResponseWriter, authCode storage.AuthCode, client storage.Client) (*accessTokenResponse, error) { |
|
|
|
func (s *Server) exchangeAuthCode(w http.ResponseWriter, authCode storage.AuthCode, client storage.Client) (*accessTokenResponse, error) { |
|
|
|
accessToken, err := s.newAccessToken(client.ID, authCode.Claims, authCode.Scopes, authCode.Nonce, authCode.ConnectorID) |
|
|
|
accessToken, _, err := s.newAccessToken(client.ID, authCode.Claims, authCode.Scopes, authCode.Nonce, authCode.ConnectorID) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
s.logger.Errorf("failed to create new access token: %v", err) |
|
|
|
s.logger.Errorf("failed to create new access token: %v", err) |
|
|
|
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) |
|
|
|
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) |
|
|
|
@ -1180,7 +1187,7 @@ func (s *Server) handlePasswordGrant(w http.ResponseWriter, r *http.Request, cli |
|
|
|
Groups: identity.Groups, |
|
|
|
Groups: identity.Groups, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
accessToken, err := s.newAccessToken(client.ID, claims, scopes, nonce, connID) |
|
|
|
accessToken, _, err := s.newAccessToken(client.ID, claims, scopes, nonce, connID) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
s.logger.Errorf("password grant failed to create new access token: %v", err) |
|
|
|
s.logger.Errorf("password grant failed to create new access token: %v", err) |
|
|
|
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) |
|
|
|
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) |
|
|
|
@ -1319,21 +1326,109 @@ func (s *Server) handlePasswordGrant(w http.ResponseWriter, r *http.Request, cli |
|
|
|
s.writeAccessToken(w, resp) |
|
|
|
s.writeAccessToken(w, resp) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (s *Server) handleTokenExchange(w http.ResponseWriter, r *http.Request, client storage.Client) { |
|
|
|
|
|
|
|
ctx := r.Context() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if err := r.ParseForm(); err != nil { |
|
|
|
|
|
|
|
s.logger.Errorf("could not parse request body: %v", err) |
|
|
|
|
|
|
|
s.tokenErrHelper(w, errInvalidRequest, "", http.StatusBadRequest) |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
q := r.Form |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scopes := strings.Fields(q.Get("scope")) // OPTIONAL, map to issued token scope
|
|
|
|
|
|
|
|
requestedTokenType := q.Get("requested_token_type") // OPTIONAL, default to access token
|
|
|
|
|
|
|
|
if requestedTokenType == "" { |
|
|
|
|
|
|
|
requestedTokenType = tokenTypeAccess |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
subjectToken := q.Get("subject_token") // REQUIRED
|
|
|
|
|
|
|
|
subjectTokenType := q.Get("subject_token_type") // REQUIRED
|
|
|
|
|
|
|
|
connID := q.Get("connector_id") // REQUIRED, not in RFC
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
switch subjectTokenType { |
|
|
|
|
|
|
|
case tokenTypeID, tokenTypeAccess: // ok, continue
|
|
|
|
|
|
|
|
default: |
|
|
|
|
|
|
|
s.tokenErrHelper(w, errRequestNotSupported, "Invalid subject_token_type.", http.StatusBadRequest) |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if subjectToken == "" { |
|
|
|
|
|
|
|
s.tokenErrHelper(w, errInvalidRequest, "Missing subject_token", http.StatusBadRequest) |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
conn, err := s.getConnector(connID) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
s.logger.Errorf("failed to get connector: %v", err) |
|
|
|
|
|
|
|
s.tokenErrHelper(w, errInvalidRequest, "Requested connector does not exist.", http.StatusBadRequest) |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
teConn, ok := conn.Connector.(connector.TokenIdentityConnector) |
|
|
|
|
|
|
|
if !ok { |
|
|
|
|
|
|
|
s.logger.Errorf("connector doesn't implement token exchange: %v", connID) |
|
|
|
|
|
|
|
s.tokenErrHelper(w, errInvalidRequest, "Requested connector does not exist.", http.StatusBadRequest) |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
identity, err := teConn.TokenIdentity(ctx, subjectTokenType, subjectToken) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
s.logger.Errorf("failed to verify subject token: %v", err) |
|
|
|
|
|
|
|
s.tokenErrHelper(w, errAccessDenied, "", http.StatusUnauthorized) |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
claims := storage.Claims{ |
|
|
|
|
|
|
|
UserID: identity.UserID, |
|
|
|
|
|
|
|
Username: identity.Username, |
|
|
|
|
|
|
|
PreferredUsername: identity.PreferredUsername, |
|
|
|
|
|
|
|
Email: identity.Email, |
|
|
|
|
|
|
|
EmailVerified: identity.EmailVerified, |
|
|
|
|
|
|
|
Groups: identity.Groups, |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
resp := accessTokenResponse{ |
|
|
|
|
|
|
|
IssuedTokenType: requestedTokenType, |
|
|
|
|
|
|
|
TokenType: "bearer", |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
var expiry time.Time |
|
|
|
|
|
|
|
switch requestedTokenType { |
|
|
|
|
|
|
|
case tokenTypeID: |
|
|
|
|
|
|
|
resp.AccessToken, expiry, err = s.newIDToken(client.ID, claims, scopes, "", "", "", connID) |
|
|
|
|
|
|
|
case tokenTypeAccess: |
|
|
|
|
|
|
|
resp.AccessToken, expiry, err = s.newAccessToken(client.ID, claims, scopes, "", connID) |
|
|
|
|
|
|
|
default: |
|
|
|
|
|
|
|
s.tokenErrHelper(w, errRequestNotSupported, "Invalid requested_token_type.", http.StatusBadRequest) |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
s.logger.Errorf("token exchange failed to create new %v token: %v", requestedTokenType, err) |
|
|
|
|
|
|
|
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
resp.ExpiresIn = int(time.Until(expiry).Seconds()) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Token response must include cache headers https://tools.ietf.org/html/rfc6749#section-5.1
|
|
|
|
|
|
|
|
w.Header().Set("Cache-Control", "no-store") |
|
|
|
|
|
|
|
w.Header().Set("Pragma", "no-cache") |
|
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/json") |
|
|
|
|
|
|
|
json.NewEncoder(w).Encode(resp) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
type accessTokenResponse struct { |
|
|
|
type accessTokenResponse struct { |
|
|
|
AccessToken string `json:"access_token"` |
|
|
|
AccessToken string `json:"access_token"` |
|
|
|
TokenType string `json:"token_type"` |
|
|
|
IssuedTokenType string `json:"issued_token_type,omitempty"` |
|
|
|
ExpiresIn int `json:"expires_in"` |
|
|
|
TokenType string `json:"token_type"` |
|
|
|
RefreshToken string `json:"refresh_token,omitempty"` |
|
|
|
ExpiresIn int `json:"expires_in,omitempty"` |
|
|
|
IDToken string `json:"id_token"` |
|
|
|
RefreshToken string `json:"refresh_token,omitempty"` |
|
|
|
|
|
|
|
IDToken string `json:"id_token,omitempty"` |
|
|
|
|
|
|
|
Scope string `json:"scope,omitempty"` |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (s *Server) toAccessTokenResponse(idToken, accessToken, refreshToken string, expiry time.Time) *accessTokenResponse { |
|
|
|
func (s *Server) toAccessTokenResponse(idToken, accessToken, refreshToken string, expiry time.Time) *accessTokenResponse { |
|
|
|
return &accessTokenResponse{ |
|
|
|
return &accessTokenResponse{ |
|
|
|
accessToken, |
|
|
|
AccessToken: accessToken, |
|
|
|
"bearer", |
|
|
|
TokenType: "bearer", |
|
|
|
int(expiry.Sub(s.now()).Seconds()), |
|
|
|
ExpiresIn: int(expiry.Sub(s.now()).Seconds()), |
|
|
|
refreshToken, |
|
|
|
RefreshToken: refreshToken, |
|
|
|
idToken, |
|
|
|
IDToken: idToken, |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -1355,7 +1450,7 @@ func (s *Server) writeAccessToken(w http.ResponseWriter, resp *accessTokenRespon |
|
|
|
|
|
|
|
|
|
|
|
func (s *Server) renderError(r *http.Request, w http.ResponseWriter, status int, description string) { |
|
|
|
func (s *Server) renderError(r *http.Request, w http.ResponseWriter, status int, description string) { |
|
|
|
if err := s.templates.err(r, w, status, description); err != nil { |
|
|
|
if err := s.templates.err(r, w, status, description); err != nil { |
|
|
|
s.logger.Errorf("Server template error: %v", err) |
|
|
|
s.logger.Errorf("server template error: %v", err) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|