This commit is contained in:
Livio Amstutz 2020-09-16 15:22:15 +02:00
parent 5b6175acfc
commit 64797c1df6
9 changed files with 39 additions and 44 deletions

View file

@ -152,7 +152,7 @@ func (s *AuthStorage) AuthRequestByID(_ context.Context, id string) (op.AuthRequ
return a, nil return a, nil
} }
func (s *AuthStorage) CreateToken(_ context.Context, authReq op.TokenRequest) (string, time.Time, error) { func (s *AuthStorage) CreateToken(_ context.Context, authReq op.TokenRequest) (string, time.Time, error) {
return "authReq.GetID()", time.Now().UTC().Add(5 * time.Minute), nil return "id", time.Now().UTC().Add(5 * time.Minute), nil
} }
func (s *AuthStorage) TerminateSession(_ context.Context, userID, clientID string) error { func (s *AuthStorage) TerminateSession(_ context.Context, userID, clientID string) error {
return nil return nil

View file

@ -23,7 +23,7 @@ type TokenExchangeRequest struct {
} }
type JWTProfileRequest struct { type JWTProfileRequest struct {
assertion string `schema:"assertion"` Assertion string `schema:"assertion"`
} }
func NewTokenExchangeRequest(subjectToken, subjectTokenType string, opts ...TokenExchangeOption) *TokenExchangeRequest { func NewTokenExchangeRequest(subjectToken, subjectTokenType string, opts ...TokenExchangeOption) *TokenExchangeRequest {

View file

@ -212,7 +212,7 @@ func (o *openidProvider) IDTokenHintVerifier() IDTokenHintVerifier {
func (o *openidProvider) JWTProfileVerifier() JWTProfileVerifier { func (o *openidProvider) JWTProfileVerifier() JWTProfileVerifier {
if o.jwtProfileVerifier == nil { if o.jwtProfileVerifier == nil {
o.jwtProfileVerifier = NewJWTProfileVerifier(o.Storage(), o.Issuer()) o.jwtProfileVerifier = NewJWTProfileVerifier(o.Storage(), o.Issuer(), 1*time.Hour, time.Second)
} }
return o.jwtProfileVerifier return o.jwtProfileVerifier
} }

View file

@ -1,13 +1,12 @@
package op package op
import ( import (
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"golang.org/x/net/context"
"gopkg.in/square/go-jose.v2"
"github.com/caos/logging" "github.com/caos/logging"
"gopkg.in/square/go-jose.v2"
"github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/pkg/oidc"
) )

View file

@ -15,7 +15,6 @@ type TokenCreator interface {
} }
type TokenRequest interface { type TokenRequest interface {
GetClientID() string
GetSubject() string GetSubject() string
GetAudience() []string GetAudience() []string
GetScopes() []string GetScopes() []string

View file

@ -6,6 +6,7 @@ import (
"net/http" "net/http"
"github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/oidc/grants/tokenexchange"
"github.com/caos/oidc/pkg/utils" "github.com/caos/oidc/pkg/utils"
) )
@ -161,14 +162,12 @@ func ParseJWTProfileRequest(r *http.Request, decoder utils.Decoder) (string, err
if err != nil { if err != nil {
return "", ErrInvalidRequest("error parsing form") return "", ErrInvalidRequest("error parsing form")
} }
tokenReq := new(struct { tokenReq := new(tokenexchange.JWTProfileRequest)
Token string `schema:"assertion"`
})
err = decoder.Decode(tokenReq, r.Form) err = decoder.Decode(tokenReq, r.Form)
if err != nil { if err != nil {
return "", ErrInvalidRequest("error decoding form") return "", ErrInvalidRequest("error decoding form")
} }
return tokenReq.Token, nil return tokenReq.Assertion, nil
} }
func TokenExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) { func TokenExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) {

View file

@ -16,14 +16,18 @@ type JWTProfileVerifier interface {
} }
type jwtProfileVerifier struct { type jwtProfileVerifier struct {
storage Storage storage Storage
issuer string issuer string
maxAgeIAT time.Duration
offset time.Duration
} }
func NewJWTProfileVerifier(storage Storage, issuer string) JWTProfileVerifier { func NewJWTProfileVerifier(storage Storage, issuer string, maxAgeIAT, offset time.Duration) JWTProfileVerifier {
return &jwtProfileVerifier{ return &jwtProfileVerifier{
storage: storage, storage: storage,
issuer: issuer, issuer: issuer,
maxAgeIAT: maxAgeIAT,
offset: offset,
} }
} }
@ -36,13 +40,11 @@ func (v *jwtProfileVerifier) Storage() Storage {
} }
func (v *jwtProfileVerifier) MaxAgeIAT() time.Duration { func (v *jwtProfileVerifier) MaxAgeIAT() time.Duration {
//TODO: define in conf/opts return v.maxAgeIAT
return 1 * time.Hour
} }
func (v *jwtProfileVerifier) Offset() time.Duration { func (v *jwtProfileVerifier) Offset() time.Duration {
//TODO: define in conf/opts return v.offset
return time.Second
} }
func VerifyJWTAssertion(ctx context.Context, assertion string, v JWTProfileVerifier) (*oidc.JWTTokenRequest, error) { func VerifyJWTAssertion(ctx context.Context, assertion string, v JWTProfileVerifier) (*oidc.JWTTokenRequest, error) {

View file

@ -6,6 +6,8 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/google/uuid"
"github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/oidc/grants" "github.com/caos/oidc/pkg/oidc/grants"
"github.com/caos/oidc/pkg/utils" "github.com/caos/oidc/pkg/utils"
@ -31,12 +33,16 @@ type RelayingParty interface {
//CookieHandler returns a http cookie handler used for various state transfer cookies //CookieHandler returns a http cookie handler used for various state transfer cookies
CookieHandler() *utils.CookieHandler CookieHandler() *utils.CookieHandler
//Client return a standard http client where the token can be used //HttpClient returns a http client used for calls to the openid provider, e.g. calling token endpoint
Client(ctx context.Context, token *oauth2.Token) *http.Client
HttpClient() *http.Client HttpClient() *http.Client
//IsOAuth2Only specifies whether relaying party handles only oauth2 or oidc calls
IsOAuth2Only() bool IsOAuth2Only() bool
//IDTokenVerifier returns the verifier interface used for oidc id_token verification
IDTokenVerifier() IDTokenVerifier IDTokenVerifier() IDTokenVerifier
//ErrorHandler returns the handler used for callback errors
ErrorHandler() func(http.ResponseWriter, *http.Request, string, string, string) ErrorHandler() func(http.ResponseWriter, *http.Request, string, string, string)
} }
@ -90,10 +96,6 @@ func (rp *relayingParty) IDTokenVerifier() IDTokenVerifier {
return rp.idTokenVerifier return rp.idTokenVerifier
} }
func (rp *relayingParty) Client(ctx context.Context, token *oauth2.Token) *http.Client {
return rp.oauthConfig.Client(ctx, token)
}
func (rp *relayingParty) ErrorHandler() func(http.ResponseWriter, *http.Request, string, string, string) { func (rp *relayingParty) ErrorHandler() func(http.ResponseWriter, *http.Request, string, string, string) {
if rp.errorHandler == nil { if rp.errorHandler == nil {
rp.errorHandler = DefaultErrorHandler rp.errorHandler = DefaultErrorHandler
@ -234,18 +236,17 @@ func AuthURLHandler(stateFn func() string, rp RelayingParty) http.HandlerFunc {
} }
} }
//GenerateAndStoreCodeChallenge generates a PKCE code challenge and stores its verifier into a secure cookie
func GenerateAndStoreCodeChallenge(w http.ResponseWriter, rp RelayingParty) (string, error) { func GenerateAndStoreCodeChallenge(w http.ResponseWriter, rp RelayingParty) (string, error) {
var codeVerifier string codeVerifier := uuid.New().String()
codeVerifier = "s"
if err := rp.CookieHandler().SetCookie(w, pkceCode, codeVerifier); err != nil { if err := rp.CookieHandler().SetCookie(w, pkceCode, codeVerifier); err != nil {
return "", err return "", err
} }
return oidc.NewSHACodeChallenge(codeVerifier), nil return oidc.NewSHACodeChallenge(codeVerifier), nil
} }
//AuthURL is the `RelayingParty` interface implementation //CodeExchange handles the oauth2 code exchange, extracting and validating the id_token
//handling the oauth2 code exchange, extracting and validating the id_token //returning it parsed together with the oauth2 tokens (access, refresh)
//returning it paresed together with the oauth2 tokens (access, refresh)
func CodeExchange(ctx context.Context, code string, rp RelayingParty, opts ...CodeExchangeOpt) (tokens *oidc.Tokens, err error) { func CodeExchange(ctx context.Context, code string, rp RelayingParty, opts ...CodeExchangeOpt) (tokens *oidc.Tokens, err error) {
ctx = context.WithValue(ctx, oauth2.HTTPClient, rp.HttpClient()) ctx = context.WithValue(ctx, oauth2.HTTPClient, rp.HttpClient())
codeOpts := make([]oauth2.AuthCodeOption, 0) codeOpts := make([]oauth2.AuthCodeOption, 0)
@ -255,7 +256,7 @@ func CodeExchange(ctx context.Context, code string, rp RelayingParty, opts ...Co
token, err := rp.OAuthConfig().Exchange(ctx, code, codeOpts...) token, err := rp.OAuthConfig().Exchange(ctx, code, codeOpts...)
if err != nil { if err != nil {
return nil, err //TODO: our error return nil, err
} }
if rp.IsOAuth2Only() { if rp.IsOAuth2Only() {
@ -275,8 +276,9 @@ func CodeExchange(ctx context.Context, code string, rp RelayingParty, opts ...Co
return &oidc.Tokens{Token: token, IDTokenClaims: idToken, IDToken: idTokenString}, nil return &oidc.Tokens{Token: token, IDTokenClaims: idToken, IDToken: idTokenString}, nil
} }
//AuthURL is the `RelayingParty` interface implementation //CodeExchangeHandler extends the `CodeExchange` method with a http handler
//extending the `CodeExchange` method with callback function //including cookie handling for secure `state` transfer
//and optional PKCE code verifier checking
func CodeExchangeHandler(callback func(http.ResponseWriter, *http.Request, *oidc.Tokens, string), rp RelayingParty) http.HandlerFunc { func CodeExchangeHandler(callback func(http.ResponseWriter, *http.Request, *oidc.Tokens, string), rp RelayingParty) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
state, err := tryReadStateCookie(w, r, rp) state, err := tryReadStateCookie(w, r, rp)
@ -365,11 +367,6 @@ func tryReadStateCookie(w http.ResponseWriter, r *http.Request, rp RelayingParty
return state, nil return state, nil
} }
type Configuration struct {
Issuer string
*oauth2.Config
}
type OptionFunc func(RelayingParty) type OptionFunc func(RelayingParty)
type Endpoints struct { type Endpoints struct {

View file

@ -32,18 +32,17 @@ type DelegationTokenExchangeRP interface {
DelegationTokenExchange(context.Context, string, ...tokenexchange.TokenExchangeOption) (*oauth2.Token, error) DelegationTokenExchange(context.Context, string, ...tokenexchange.TokenExchangeOption) (*oauth2.Token, error)
} }
//TokenExchange is the `TokenExchangeRP` interface implementation //TokenExchange handles the oauth2 token exchange
//handling the oauth2 token exchange (draft)
func TokenExchange(ctx context.Context, request *tokenexchange.TokenExchangeRequest, rp RelayingParty) (newToken *oauth2.Token, err error) { func TokenExchange(ctx context.Context, request *tokenexchange.TokenExchangeRequest, rp RelayingParty) (newToken *oauth2.Token, err error) {
return CallTokenEndpoint(request, rp) return CallTokenEndpoint(request, rp)
} }
//DelegationTokenExchange is the `TokenExchangeRP` interface implementation //DelegationTokenExchange handles the oauth2 token exchange for a delegation token
//handling the oauth2 token exchange for a delegation token (draft)
func DelegationTokenExchange(ctx context.Context, subjectToken string, rp RelayingParty, reqOpts ...tokenexchange.TokenExchangeOption) (newToken *oauth2.Token, err error) { func DelegationTokenExchange(ctx context.Context, subjectToken string, rp RelayingParty, reqOpts ...tokenexchange.TokenExchangeOption) (newToken *oauth2.Token, err error) {
return TokenExchange(ctx, DelegationTokenRequest(subjectToken, reqOpts...), rp) return TokenExchange(ctx, DelegationTokenRequest(subjectToken, reqOpts...), rp)
} }
//JWTProfileExchange handles the oauth2 jwt profile exchange
func JWTProfileExchange(ctx context.Context, assertion *oidc.JWTProfileAssertion, rp RelayingParty) (*oauth2.Token, error) { func JWTProfileExchange(ctx context.Context, assertion *oidc.JWTProfileAssertion, rp RelayingParty) (*oauth2.Token, error) {
token, err := generateJWTProfileToken(assertion) token, err := generateJWTProfileToken(assertion)
if err != nil { if err != nil {