|
|
|
@@ -2,6 +2,7 @@ package server
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/url"
|
|
|
|
@@ -16,6 +17,7 @@ import (
|
|
|
|
|
jose "gopkg.in/square/go-jose.v2"
|
|
|
|
|
|
|
|
|
|
"github.com/coreos/dex/connector"
|
|
|
|
|
"github.com/coreos/dex/server/internal"
|
|
|
|
|
"github.com/coreos/dex/storage"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
@@ -645,20 +647,32 @@ func (s *Server) handleAuthCode(w http.ResponseWriter, r *http.Request, client s
|
|
|
|
|
var refreshToken string
|
|
|
|
|
if reqRefresh {
|
|
|
|
|
refresh := storage.RefreshToken{
|
|
|
|
|
RefreshToken: storage.NewID(),
|
|
|
|
|
ID: storage.NewID(),
|
|
|
|
|
Token: storage.NewID(),
|
|
|
|
|
ClientID: authCode.ClientID,
|
|
|
|
|
ConnectorID: authCode.ConnectorID,
|
|
|
|
|
Scopes: authCode.Scopes,
|
|
|
|
|
Claims: authCode.Claims,
|
|
|
|
|
Nonce: authCode.Nonce,
|
|
|
|
|
ConnectorData: authCode.ConnectorData,
|
|
|
|
|
CreatedAt: s.now(),
|
|
|
|
|
LastUsed: s.now(),
|
|
|
|
|
}
|
|
|
|
|
token := &internal.RefreshToken{
|
|
|
|
|
RefreshId: refresh.ID,
|
|
|
|
|
Token: refresh.Token,
|
|
|
|
|
}
|
|
|
|
|
if refreshToken, err = internal.Marshal(token); err != nil {
|
|
|
|
|
s.logger.Errorf("failed to marshal refresh token: %v", err)
|
|
|
|
|
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := s.storage.CreateRefresh(refresh); err != nil {
|
|
|
|
|
s.logger.Errorf("failed to create refresh token: %v", err)
|
|
|
|
|
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
refreshToken = refresh.RefreshToken
|
|
|
|
|
}
|
|
|
|
|
s.writeAccessToken(w, idToken, refreshToken, expiry)
|
|
|
|
|
}
|
|
|
|
@@ -672,14 +686,35 @@ func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, clie
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
refresh, err := s.storage.GetRefresh(code)
|
|
|
|
|
if err != nil || refresh.ClientID != client.ID {
|
|
|
|
|
if err != storage.ErrNotFound {
|
|
|
|
|
s.logger.Errorf("failed to get auth code: %v", err)
|
|
|
|
|
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
|
|
|
|
|
} else {
|
|
|
|
|
s.tokenErrHelper(w, errInvalidRequest, "Refresh token is invalid or has already been claimed by another client.", http.StatusBadRequest)
|
|
|
|
|
token := new(internal.RefreshToken)
|
|
|
|
|
if err := internal.Unmarshal(code, token); err != nil {
|
|
|
|
|
// For backward compatibility, assume the refresh_token is a raw refresh token ID
|
|
|
|
|
// if it fails to decode.
|
|
|
|
|
//
|
|
|
|
|
// Because refresh_token values that aren't unmarshable were generated by servers
|
|
|
|
|
// that don't have a Token value, we'll still reject any attempts to claim a
|
|
|
|
|
// refresh_token twice.
|
|
|
|
|
token = &internal.RefreshToken{RefreshId: code, Token: ""}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
refresh, err := s.storage.GetRefresh(token.RefreshId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.logger.Errorf("failed to get refresh token: %v", err)
|
|
|
|
|
if err == storage.ErrNotFound {
|
|
|
|
|
s.tokenErrHelper(w, errInvalidRequest, "Refresh token is invalid or has already been claimed by another client.", http.StatusBadRequest)
|
|
|
|
|
} else {
|
|
|
|
|
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if refresh.ClientID != client.ID {
|
|
|
|
|
s.logger.Errorf("client %s trying to claim token for client %s", client.ID, refresh.ClientID)
|
|
|
|
|
s.tokenErrHelper(w, errInvalidRequest, "Refresh token is invalid or has already been claimed by another client.", http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if refresh.Token != token.Token {
|
|
|
|
|
s.logger.Errorf("refresh token with id %s claimed twice", refresh.ID)
|
|
|
|
|
s.tokenErrHelper(w, errInvalidRequest, "Refresh token is invalid or has already been claimed by another client.", http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -720,13 +755,6 @@ func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, clie
|
|
|
|
|
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Can the connector refresh the identity? If so, attempt to refresh the data
|
|
|
|
|
// in the connector.
|
|
|
|
|
//
|
|
|
|
|
// TODO(ericchiang): We may want a strict mode where connectors that don't implement
|
|
|
|
|
// this interface can't perform refreshing.
|
|
|
|
|
if refreshConn, ok := conn.Connector.(connector.RefreshConnector); ok {
|
|
|
|
|
ident := connector.Identity{
|
|
|
|
|
UserID: refresh.Claims.UserID,
|
|
|
|
|
Username: refresh.Claims.Username,
|
|
|
|
@@ -735,44 +763,70 @@ func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, clie
|
|
|
|
|
Groups: refresh.Claims.Groups,
|
|
|
|
|
ConnectorData: refresh.ConnectorData,
|
|
|
|
|
}
|
|
|
|
|
ident, err := refreshConn.Refresh(r.Context(), parseScopes(scopes), ident)
|
|
|
|
|
|
|
|
|
|
// Can the connector refresh the identity? If so, attempt to refresh the data
|
|
|
|
|
// in the connector.
|
|
|
|
|
//
|
|
|
|
|
// TODO(ericchiang): We may want a strict mode where connectors that don't implement
|
|
|
|
|
// this interface can't perform refreshing.
|
|
|
|
|
if refreshConn, ok := conn.Connector.(connector.RefreshConnector); ok {
|
|
|
|
|
newIdent, err := refreshConn.Refresh(r.Context(), parseScopes(scopes), ident)
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.logger.Errorf("failed to refresh identity: %v", err)
|
|
|
|
|
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update the claims of the refresh token.
|
|
|
|
|
//
|
|
|
|
|
// UserID intentionally ignored for now.
|
|
|
|
|
refresh.Claims.Username = ident.Username
|
|
|
|
|
refresh.Claims.Email = ident.Email
|
|
|
|
|
refresh.Claims.EmailVerified = ident.EmailVerified
|
|
|
|
|
refresh.Claims.Groups = ident.Groups
|
|
|
|
|
refresh.ConnectorData = ident.ConnectorData
|
|
|
|
|
ident = newIdent
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
idToken, expiry, err := s.newIDToken(client.ID, refresh.Claims, scopes, refresh.Nonce)
|
|
|
|
|
claims := storage.Claims{
|
|
|
|
|
UserID: ident.UserID,
|
|
|
|
|
Username: ident.Username,
|
|
|
|
|
Email: ident.Email,
|
|
|
|
|
EmailVerified: ident.EmailVerified,
|
|
|
|
|
Groups: ident.Groups,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
idToken, expiry, err := s.newIDToken(client.ID, claims, scopes, refresh.Nonce)
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.logger.Errorf("failed to create ID token: %v", err)
|
|
|
|
|
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Refresh tokens are claimed exactly once. Delete the current token and
|
|
|
|
|
// create a new one.
|
|
|
|
|
if err := s.storage.DeleteRefresh(code); err != nil {
|
|
|
|
|
s.logger.Errorf("failed to delete auth code: %v", err)
|
|
|
|
|
newToken := &internal.RefreshToken{
|
|
|
|
|
RefreshId: refresh.ID,
|
|
|
|
|
Token: storage.NewID(),
|
|
|
|
|
}
|
|
|
|
|
rawNewToken, err := internal.Marshal(newToken)
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.logger.Errorf("failed to marshal refresh token: %v", err)
|
|
|
|
|
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
refresh.RefreshToken = storage.NewID()
|
|
|
|
|
if err := s.storage.CreateRefresh(refresh); err != nil {
|
|
|
|
|
s.logger.Errorf("failed to create refresh token: %v", err)
|
|
|
|
|
|
|
|
|
|
updater := func(old storage.RefreshToken) (storage.RefreshToken, error) {
|
|
|
|
|
if old.Token != refresh.Token {
|
|
|
|
|
return old, errors.New("refresh token claimed twice")
|
|
|
|
|
}
|
|
|
|
|
old.Token = newToken.Token
|
|
|
|
|
// Update the claims of the refresh token.
|
|
|
|
|
//
|
|
|
|
|
// UserID intentionally ignored for now.
|
|
|
|
|
old.Claims.Username = ident.Username
|
|
|
|
|
old.Claims.Email = ident.Email
|
|
|
|
|
old.Claims.EmailVerified = ident.EmailVerified
|
|
|
|
|
old.Claims.Groups = ident.Groups
|
|
|
|
|
old.ConnectorData = ident.ConnectorData
|
|
|
|
|
old.LastUsed = s.now()
|
|
|
|
|
return old, nil
|
|
|
|
|
}
|
|
|
|
|
if err := s.storage.UpdateRefreshToken(refresh.ID, updater); err != nil {
|
|
|
|
|
s.logger.Errorf("failed to update refresh token: %v", err)
|
|
|
|
|
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
s.writeAccessToken(w, idToken, refresh.RefreshToken, expiry)
|
|
|
|
|
s.writeAccessToken(w, idToken, rawNewToken, expiry)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Server) writeAccessToken(w http.ResponseWriter, idToken, refreshToken string, expiry time.Time) {
|
|
|
|
|