@ -2,6 +2,7 @@ package server
import (
import (
"encoding/json"
"encoding/json"
"errors"
"fmt"
"fmt"
"net/http"
"net/http"
"net/url"
"net/url"
@ -16,6 +17,7 @@ import (
jose "gopkg.in/square/go-jose.v2"
jose "gopkg.in/square/go-jose.v2"
"github.com/coreos/dex/connector"
"github.com/coreos/dex/connector"
"github.com/coreos/dex/server/internal"
"github.com/coreos/dex/storage"
"github.com/coreos/dex/storage"
)
)
@ -645,20 +647,32 @@ func (s *Server) handleAuthCode(w http.ResponseWriter, r *http.Request, client s
var refreshToken string
var refreshToken string
if reqRefresh {
if reqRefresh {
refresh := storage . RefreshToken {
refresh := storage . RefreshToken {
RefreshToken : storage . NewID ( ) ,
ID : storage . NewID ( ) ,
Token : storage . NewID ( ) ,
ClientID : authCode . ClientID ,
ClientID : authCode . ClientID ,
ConnectorID : authCode . ConnectorID ,
ConnectorID : authCode . ConnectorID ,
Scopes : authCode . Scopes ,
Scopes : authCode . Scopes ,
Claims : authCode . Claims ,
Claims : authCode . Claims ,
Nonce : authCode . Nonce ,
Nonce : authCode . Nonce ,
ConnectorData : authCode . ConnectorData ,
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 {
if err := s . storage . CreateRefresh ( refresh ) ; err != nil {
s . logger . Errorf ( "failed to create refresh token: %v" , err )
s . logger . Errorf ( "failed to create refresh token: %v" , err )
s . tokenErrHelper ( w , errServerError , "" , http . StatusInternalServerError )
s . tokenErrHelper ( w , errServerError , "" , http . StatusInternalServerError )
return
return
}
}
refreshToken = refresh . RefreshToken
}
}
s . writeAccessToken ( w , idToken , refreshToken , expiry )
s . writeAccessToken ( w , idToken , refreshToken , expiry )
}
}
@ -672,16 +686,37 @@ func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, clie
return
return
}
}
refresh , err := s . storage . GetRefresh ( code )
token := new ( internal . RefreshToken )
if err != nil || refresh . ClientID != client . ID {
if err := internal . Unmarshal ( code , token ) ; err != nil {
if err != storage . ErrNotFound {
// For backward compatibility, assume the refresh_token is a raw refresh token ID
s . logger . Errorf ( "failed to get auth code: %v" , err )
// if it fails to decode.
s . tokenErrHelper ( w , errServerError , "" , http . StatusInternalServerError )
//
} else {
// 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 )
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
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
}
// Per the OAuth2 spec, if the client has omitted the scopes, default to the original
// Per the OAuth2 spec, if the client has omitted the scopes, default to the original
// authorized scopes.
// authorized scopes.
@ -720,6 +755,14 @@ func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, clie
s . tokenErrHelper ( w , errServerError , "" , http . StatusInternalServerError )
s . tokenErrHelper ( w , errServerError , "" , http . StatusInternalServerError )
return
return
}
}
ident := connector . Identity {
UserID : refresh . Claims . UserID ,
Username : refresh . Claims . Username ,
Email : refresh . Claims . Email ,
EmailVerified : refresh . Claims . EmailVerified ,
Groups : refresh . Claims . Groups ,
ConnectorData : refresh . ConnectorData ,
}
// Can the connector refresh the identity? If so, attempt to refresh the data
// Can the connector refresh the identity? If so, attempt to refresh the data
// in the connector.
// in the connector.
@ -727,52 +770,63 @@ func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, clie
// TODO(ericchiang): We may want a strict mode where connectors that don't implement
// TODO(ericchiang): We may want a strict mode where connectors that don't implement
// this interface can't perform refreshing.
// this interface can't perform refreshing.
if refreshConn , ok := conn . Connector . ( connector . RefreshConnector ) ; ok {
if refreshConn , ok := conn . Connector . ( connector . RefreshConnector ) ; ok {
ident := connector . Identity {
newIdent , err := refreshConn . Refresh ( r . Context ( ) , parseScopes ( scopes ) , ident )
UserID : refresh . Claims . UserID ,
Username : refresh . Claims . Username ,
Email : refresh . Claims . Email ,
EmailVerified : refresh . Claims . EmailVerified ,
Groups : refresh . Claims . Groups ,
ConnectorData : refresh . ConnectorData ,
}
ident , err := refreshConn . Refresh ( r . Context ( ) , parseScopes ( scopes ) , ident )
if err != nil {
if err != nil {
s . logger . Errorf ( "failed to refresh identity: %v" , err )
s . logger . Errorf ( "failed to refresh identity: %v" , err )
s . tokenErrHelper ( w , errServerError , "" , http . StatusInternalServerError )
s . tokenErrHelper ( w , errServerError , "" , http . StatusInternalServerError )
return
return
}
}
ident = newIdent
}
// Update the claims of the refresh token.
claims := storage . Claims {
//
UserID : ident . UserID ,
// UserID intentionally ignored for now.
Username : ident . Username ,
refresh . Claims . Username = ident . Username
Email : ident . Email ,
refresh . Claims . Email = ident . Email
EmailVerified : ident . EmailVerified ,
refresh . Claims . EmailVerified = ident . EmailVerified
Groups : ident . Groups ,
refresh . Claims . Groups = ident . Groups
refresh . ConnectorData = ident . ConnectorData
}
}
idToken , expiry , err := s . newIDToken ( client . ID , refresh . C laims, scopes , refresh . Nonce )
idToken , expiry , err := s . newIDToken ( client . ID , c laims, scopes , refresh . 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
}
}
// Refresh tokens are claimed exactly once. Delete the current token and
newToken := & internal . RefreshToken {
// create a new one.
RefreshId : refresh . ID ,
if err := s . storage . DeleteRefresh ( code ) ; err != nil {
Token : storage . NewID ( ) ,
s . logger . Errorf ( "failed to delete auth code: %v" , err )
}
rawNewToken , err := internal . Marshal ( newToken )
if err != nil {
s . logger . Errorf ( "failed to marshal refresh token: %v" , err )
s . tokenErrHelper ( w , errServerError , "" , http . StatusInternalServerError )
s . tokenErrHelper ( w , errServerError , "" , http . StatusInternalServerError )
return
return
}
}
refresh . RefreshToken = storage . NewID ( )
if err := s . storage . CreateRefresh ( refresh ) ; err != nil {
updater := func ( old storage . RefreshToken ) ( storage . RefreshToken , error ) {
s . logger . Errorf ( "failed to create refresh token: %v" , err )
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 )
s . tokenErrHelper ( w , errServerError , "" , http . StatusInternalServerError )
return
return
}
}
s . writeAccessToken ( w , idToken , refresh . RefreshToken , expiry )
s . writeAccessToken ( w , idToken , rawNew Token , expiry )
}
}
func ( s * Server ) writeAccessToken ( w http . ResponseWriter , idToken , refreshToken string , expiry time . Time ) {
func ( s * Server ) writeAccessToken ( w http . ResponseWriter , idToken , refreshToken string , expiry time . Time ) {