introspect and client assertion

This commit is contained in:
Livio Amstutz 2021-02-01 17:17:40 +01:00
parent 50ab51bb46
commit 960be5af1f
19 changed files with 413 additions and 156 deletions

View file

@ -1,90 +1,102 @@
package main
// import (
// "encoding/json"
// "fmt"
// "log"
// "net/http"
// "os"
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strings"
"time"
// "github.com/caos/oidc/pkg/oidc"
// "github.com/caos/oidc/pkg/oidc/rp"
// "github.com/caos/utils/logging"
// )
"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
// const (
// publicURL string = "/public"
// protectedURL string = "/protected"
// protectedExchangeURL string = "/protected/exchange"
// )
"github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/rp"
)
const (
publicURL string = "/public"
protectedURL string = "/protected"
protectedClaimURL string = "/protected/{claim}/{value}"
)
func main() {
// clientID := os.Getenv("CLIENT_ID")
// clientSecret := os.Getenv("CLIENT_SECRET")
// issuer := os.Getenv("ISSUER")
// port := os.Getenv("PORT")
keyPath := os.Getenv("KEY")
port := os.Getenv("PORT")
// // ctx := context.Background()
provider, err := rp.NewResourceServerFromKeyFile(keyPath)
if err != nil {
logrus.Fatalf("error creating provider %s", err.Error())
}
// providerConfig := &oidc.ProviderConfig{
// ClientID: clientID,
// ClientSecret: clientSecret,
// Issuer: issuer,
// }
// provider, err := rp.NewDefaultProvider(providerConfig)
// logging.Log("APP-nx6PeF").OnError(err).Panic("error creating provider")
router := mux.NewRouter()
// http.HandleFunc(publicURL, func(w http.ResponseWriter, r *http.Request) {
// w.Write([]byte("OK"))
// })
//public url accessible without any authorization
//will print `OK` and current timestamp
router.HandleFunc(publicURL, func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK " + time.Now().String()))
})
// http.HandleFunc(protectedURL, func(w http.ResponseWriter, r *http.Request) {
// ok, token := checkToken(w, r)
// if !ok {
// return
// }
// resp, err := provider.Introspect(r.Context(), token)
// if err != nil {
// http.Error(w, err.Error(), http.StatusForbidden)
// return
// }
// data, err := json.Marshal(resp)
// if err != nil {
// http.Error(w, err.Error(), http.StatusInternalServerError)
// return
// }
// w.Write(data)
// })
//protected url which needs an active token
//will print the result of the introspection endpoint on success
router.HandleFunc(protectedURL, func(w http.ResponseWriter, r *http.Request) {
ok, token := checkToken(w, r)
if !ok {
return
}
resp, err := rp.Introspect(r.Context(), provider, token)
if err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
data, err := json.Marshal(resp)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(data)
})
// http.HandleFunc(protectedExchangeURL, func(w http.ResponseWriter, r *http.Request) {
// ok, token := checkToken(w, r)
// if !ok {
// return
// }
// tokens, err := provider.DelegationTokenExchange(r.Context(), token, oidc.WithResource([]string{"Test"}))
// if err != nil {
// http.Error(w, "failed to exchange token: "+err.Error(), http.StatusUnauthorized)
// return
// }
//protected url which needs an active token and checks if the response of the introspect endpoint
//contains a requested claim with the required (string) value
//e.g. /protected/username/livio@caos.ch
router.HandleFunc(protectedClaimURL, func(w http.ResponseWriter, r *http.Request) {
ok, token := checkToken(w, r)
if !ok {
return
}
resp, err := rp.Introspect(r.Context(), provider, token)
if err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
params := mux.Vars(r)
requestedClaim := params["claim"]
requestedValue := params["value"]
value, ok := resp.GetClaim(requestedClaim).(string)
if !ok || value == "" || value != requestedValue {
http.Error(w, "claim does not match", http.StatusForbidden)
return
}
w.Write([]byte("authorized with value " + value))
})
// data, err := json.Marshal(tokens)
// if err != nil {
// http.Error(w, err.Error(), http.StatusInternalServerError)
// return
// }
// w.Write(data)
// })
// lis := fmt.Sprintf("127.0.0.1:%s", port)
// log.Printf("listening on http://%s/", lis)
// log.Fatal(http.ListenAndServe(lis, nil))
// }
// func checkToken(w http.ResponseWriter, r *http.Request) (bool, string) {
// token := r.Header.Get("authorization")
// if token == "" {
// http.Error(w, "Auth header missing", http.StatusUnauthorized)
// return false, ""
// }
// return true, token
lis := fmt.Sprintf("127.0.0.1:%s", port)
log.Printf("listening on http://%s/", lis)
log.Fatal(http.ListenAndServe(lis, router))
}
func checkToken(w http.ResponseWriter, r *http.Request) (bool, string) {
auth := r.Header.Get("authorization")
if auth == "" {
http.Error(w, "auth header missing", http.StatusUnauthorized)
return false, ""
}
if !strings.HasPrefix(auth, oidc.PrefixBearer) {
http.Error(w, "invalid header", http.StatusUnauthorized)
return false, ""
}
return true, strings.TrimPrefix(auth, oidc.PrefixBearer)
}

View file

@ -24,7 +24,7 @@ func NewSHACodeChallenge(code string) string {
func VerifyCodeChallenge(c *CodeChallenge, codeVerifier string) bool {
if c == nil {
return false //TODO: ?
return false
}
if c.Method == CodeChallengeMethodS256 {
codeVerifier = NewSHACodeChallenge(codeVerifier)

View file

@ -5,23 +5,23 @@ const (
)
type DiscoveryConfiguration struct {
Issuer string `json:"issuer,omitempty"`
AuthorizationEndpoint string `json:"authorization_endpoint,omitempty"`
TokenEndpoint string `json:"token_endpoint,omitempty"`
IntrospectionEndpoint string `json:"introspection_endpoint,omitempty"`
UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"`
EndSessionEndpoint string `json:"end_session_endpoint,omitempty"`
CheckSessionIframe string `json:"check_session_iframe,omitempty"`
JwksURI string `json:"jwks_uri,omitempty"`
ScopesSupported []string `json:"scopes_supported,omitempty"`
ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
ResponseModesSupported []string `json:"response_modes_supported,omitempty"`
GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
SubjectTypesSupported []string `json:"subject_types_supported,omitempty"`
IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported,omitempty"`
TokenEndpointAuthMethodsSupported []AuthMethod `json:"token_endpoint_auth_methods_supported,omitempty"`
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported,omitempty"`
ClaimsSupported []string `json:"claims_supported,omitempty"`
Issuer string `json:"issuer,omitempty"`
AuthorizationEndpoint string `json:"authorization_endpoint,omitempty"`
TokenEndpoint string `json:"token_endpoint,omitempty"`
IntrospectionEndpoint string `json:"introspection_endpoint,omitempty"`
UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"`
EndSessionEndpoint string `json:"end_session_endpoint,omitempty"`
CheckSessionIframe string `json:"check_session_iframe,omitempty"`
JwksURI string `json:"jwks_uri,omitempty"`
ScopesSupported []string `json:"scopes_supported,omitempty"`
ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
ResponseModesSupported []string `json:"response_modes_supported,omitempty"`
GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
SubjectTypesSupported []string `json:"subject_types_supported,omitempty"`
IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported,omitempty"`
TokenEndpointAuthMethodsSupported []AuthMethod `json:"token_endpoint_auth_methods_supported,omitempty"`
CodeChallengeMethodsSupported []CodeChallengeMethod `json:"code_challenge_methods_supported,omitempty"`
ClaimsSupported []string `json:"claims_supported,omitempty"`
}
type AuthMethod string

View file

@ -1,9 +1,5 @@
package tokenexchange
import (
"github.com/caos/oidc/pkg/oidc"
)
const (
AccessTokenType = "urn:ietf:params:oauth:token-type:access_token"
RefreshTokenType = "urn:ietf:params:oauth:token-type:refresh_token"
@ -26,22 +22,6 @@ type TokenExchangeRequest struct {
requestedTokenType string `schema:"requested_token_type"`
}
type JWTProfileRequest struct {
Assertion string `schema:"assertion"`
Scope oidc.Scopes `schema:"scope"`
GrantType oidc.GrantType `schema:"grant_type"`
}
//ClientCredentialsGrantBasic creates an oauth2 `Client Credentials` Grant
//sneding client_id and client_secret as basic auth header
func NewJWTProfileRequest(assertion string, scopes ...string) *JWTProfileRequest {
return &JWTProfileRequest{
GrantType: oidc.GrantTypeBearer,
Assertion: assertion,
Scope: scopes,
}
}
func NewTokenExchangeRequest(subjectToken, subjectTokenType string, opts ...TokenExchangeOption) *TokenExchangeRequest {
t := &TokenExchangeRequest{
grantType: TokenExchangeGrantType,

View file

@ -251,5 +251,9 @@ func (i *introspectionResponse) UnmarshalJSON(data []byte) error {
i.UpdatedAt = Time(time.Unix(a.UpdatedAt, 0).UTC())
if err := json.Unmarshal(data, &i.claims); err != nil {
return err
}
return nil
}

18
pkg/oidc/jwt_profile.go Normal file
View file

@ -0,0 +1,18 @@
package oidc
type JWTProfileGrantRequest struct {
Assertion string `schema:"assertion"`
Scope Scopes `schema:"scope"`
GrantType GrantType `schema:"grant_type"`
}
//NewJWTProfileGrantRequest creates an oauth2 `JSON Web Token (JWT) Profile` Grant
//`urn:ietf:params:oauth:grant-type:jwt-bearer`
//sending a self-signed jwt as assertion
func NewJWTProfileGrantRequest(assertion string, scopes ...string) *JWTProfileGrantRequest {
return &JWTProfileGrantRequest{
GrantType: GrantTypeBearer,
Assertion: assertion,
Scope: scopes,
}
}

View file

@ -1,7 +1,10 @@
package oidc
import (
"crypto/rsa"
"crypto/x509"
"encoding/json"
"encoding/pem"
"io/ioutil"
"time"
@ -14,6 +17,8 @@ import (
const (
//BearerToken defines the token_type `Bearer`, which is returned in a successful token response
BearerToken = "Bearer"
PrefixBearer = BearerToken + " "
)
type Tokens struct {
@ -397,7 +402,7 @@ type AccessTokenResponse struct {
type JWTProfileAssertion struct {
PrivateKeyID string `json:"-"`
PrivateKey []byte `json:"-"`
Issuer string `json:"issuer"`
Issuer string `json:"iss"`
Subject string `json:"sub"`
Audience Audience `json:"aud"`
Expiration Time `json:"exp"`
@ -412,6 +417,19 @@ func NewJWTProfileAssertionFromKeyJSON(filename string, audience []string) (*JWT
return NewJWTProfileAssertionFromFileData(data, audience)
}
func NewJWTProfileAssertionStringFromFileData(data []byte, audience []string) (string, error) {
keyData := new(struct {
KeyID string `json:"keyId"`
Key string `json:"key"`
UserID string `json:"userId"`
})
err := json.Unmarshal(data, keyData)
if err != nil {
return "", err
}
return generateJWTProfileToken(NewJWTProfileAssertion(keyData.UserID, keyData.KeyID, audience, []byte(keyData.Key)))
}
func NewJWTProfileAssertionFromFileData(data []byte, audience []string) (*JWTProfileAssertion, error) {
keyData := new(struct {
KeyID string `json:"keyId"`
@ -454,3 +472,46 @@ func AppendClientIDToAudience(clientID string, audience []string) []string {
}
return append(audience, clientID)
}
func generateJWTProfileToken(assertion *JWTProfileAssertion) (string, error) {
privateKey, err := bytesToPrivateKey(assertion.PrivateKey)
if err != nil {
return "", err
}
key := jose.SigningKey{
Algorithm: jose.RS256,
Key: &jose.JSONWebKey{Key: privateKey, KeyID: assertion.PrivateKeyID},
}
signer, err := jose.NewSigner(key, &jose.SignerOptions{})
if err != nil {
return "", err
}
marshalledAssertion, err := json.Marshal(assertion)
if err != nil {
return "", err
}
signedAssertion, err := signer.Sign(marshalledAssertion)
if err != nil {
return "", err
}
return signedAssertion.CompactSerialize()
}
func bytesToPrivateKey(priv []byte) (*rsa.PrivateKey, error) {
block, _ := pem.Decode(priv)
enc := x509.IsEncryptedPEMBlock(block)
b := block.Bytes
var err error
if enc {
b, err = x509.DecryptPEMBlock(block, nil)
if err != nil {
return nil, err
}
}
key, err := x509.ParsePKCS1PrivateKey(b)
if err != nil {
return nil, err
}
return key, nil
}

View file

@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"strings"
"time"
"github.com/gorilla/mux"
@ -12,6 +13,23 @@ import (
"github.com/caos/oidc/pkg/utils"
)
type AuthRequest interface {
GetID() string
GetACR() string
GetAMR() []string
GetAudience() []string
GetAuthTime() time.Time
GetClientID() string
GetCodeChallenge() *oidc.CodeChallenge
GetNonce() string
GetRedirectURI() string
GetResponseType() oidc.ResponseType
GetScopes() []string
GetState() string
GetSubject() string
Done() bool
}
type Authorizer interface {
Storage() Storage
Decoder() utils.Decoder

View file

@ -122,10 +122,10 @@ func AuthMethods(c Configuration) []oidc.AuthMethod {
return authMethods
}
func CodeChallengeMethods(c Configuration) []string {
codeMethods := make([]string, 0, 1)
func CodeChallengeMethods(c Configuration) []oidc.CodeChallengeMethod {
codeMethods := make([]oidc.CodeChallengeMethod, 0, 1)
if c.CodeMethodS256Supported() {
codeMethods = append(codeMethods, CodeMethodS256)
codeMethods = append(codeMethods, oidc.CodeChallengeMethodS256)
}
return codeMethods
}

View file

@ -215,7 +215,7 @@ func Test_AuthMethods(t *testing.T) {
m.EXPECT().AuthMethodPostSupported().Return(false)
return m
}()},
[]string{string(op.AuthMethodBasic)},
[]string{string(oidc.AuthMethodBasic)},
},
{
"basic and post",
@ -223,7 +223,7 @@ func Test_AuthMethods(t *testing.T) {
m.EXPECT().AuthMethodPostSupported().Return(true)
return m
}()},
[]string{string(op.AuthMethodBasic), string(op.AuthMethodPost)},
[]string{string(oidc.AuthMethodBasic), string(oidc.AuthMethodPost)},
},
}
for _, tt := range tests {

View file

@ -17,27 +17,27 @@ import (
)
const (
healthzEndpoint = "/healthz"
healthEndpoint = "/healthz"
readinessEndpoint = "/ready"
defaultAuthorizationEndpoint = "authorize"
defaulTokenEndpoint = "oauth/token"
defaultTokenEndpoint = "oauth/token"
defaultIntrospectEndpoint = "oauth/introspect"
defaultUserinfoEndpoint = "userinfo"
defaultEndSessionEndpoint = "end_session"
defaultKeysEndpoint = "keys"
AuthMethodBasic AuthMethod = "client_secret_basic"
AuthMethodPost AuthMethod = "client_secret_post"
AuthMethodNone AuthMethod = "none"
AuthMethodPrivateKeyJWT AuthMethod = "private_key_jwt"
//AuthMethodBasic AuthMethod = "client_secret_basic"
//AuthMethodPost AuthMethod = "client_secret_post"
//AuthMethodNone AuthMethod = "none"
//AuthMethodPrivateKeyJWT AuthMethod = "private_key_jwt"
CodeMethodS256 = "S256"
//CodeMethodS256 = "S256"
)
var (
DefaultEndpoints = &endpoints{
Authorization: NewEndpoint(defaultAuthorizationEndpoint),
Token: NewEndpoint(defaulTokenEndpoint),
Token: NewEndpoint(defaultTokenEndpoint),
Introspection: NewEndpoint(defaultIntrospectEndpoint),
Userinfo: NewEndpoint(defaultUserinfoEndpoint),
EndSession: NewEndpoint(defaultEndSessionEndpoint),
@ -73,7 +73,7 @@ func CreateRouter(o OpenIDProvider, interceptors ...HttpInterceptor) *mux.Router
handlers.AllowedHeaders([]string{"authorization", "content-type"}),
handlers.AllowedOriginValidator(allowAllOrigins),
))
router.HandleFunc(healthzEndpoint, healthzHandler)
router.HandleFunc(healthEndpoint, healthHandler)
router.HandleFunc(readinessEndpoint, readyHandler(o.Probes()))
router.HandleFunc(oidc.DiscoveryEndpoint, discoveryHandler(o, o.Signer()))
router.Handle(o.AuthorizationEndpoint().Relative(), intercept(authorizeHandler(o)))

View file

@ -10,7 +10,7 @@ import (
type ProbesFn func(context.Context) error
func healthzHandler(w http.ResponseWriter, r *http.Request) {
func healthHandler(w http.ResponseWriter, r *http.Request) {
ok(w)
}

View file

@ -50,23 +50,6 @@ type StorageNotFoundError interface {
IsNotFound()
}
type AuthRequest interface {
GetID() string
GetACR() string
GetAMR() []string
GetAudience() []string
GetAuthTime() time.Time
GetClientID() string
GetCodeChallenge() *oidc.CodeChallenge
GetNonce() string
GetRedirectURI() string
GetResponseType() oidc.ResponseType
GetScopes() []string
GetState() string
GetSubject() string
Done() bool
}
type EndSessionRequest struct {
UserID string
Client Client

View file

@ -7,7 +7,6 @@ import (
"net/url"
"github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/oidc/grants/tokenexchange"
"github.com/caos/oidc/pkg/utils"
)
@ -203,12 +202,12 @@ func JWTProfile(w http.ResponseWriter, r *http.Request, exchanger JWTAuthorizati
utils.MarshalJSON(w, resp)
}
func ParseJWTProfileRequest(r *http.Request, decoder utils.Decoder) (*tokenexchange.JWTProfileRequest, error) {
func ParseJWTProfileRequest(r *http.Request, decoder utils.Decoder) (*oidc.JWTProfileGrantRequest, error) {
err := r.ParseForm()
if err != nil {
return nil, ErrInvalidRequest("error parsing form")
}
tokenReq := new(tokenexchange.JWTProfileRequest)
tokenReq := new(oidc.JWTProfileGrantRequest)
err = decoder.Decode(tokenReq, r.Form)
if err != nil {
return nil, ErrInvalidRequest("error decoding form")

View file

@ -70,7 +70,7 @@ func VerifyJWTAssertion(ctx context.Context, assertion string, v JWTProfileVerif
//TODO: implement delegation (openid core / oauth rfc)
}
keySet := &jwtProfileKeySet{v.Storage(), request.Subject}
keySet := &jwtProfileKeySet{v.Storage(), request.Issuer}
if err = oidc.CheckSignature(ctx, assertion, payload, request, nil, keySet); err != nil {
return nil, err

33
pkg/rp/key.go Normal file
View file

@ -0,0 +1,33 @@
package rp
import (
"encoding/json"
"io/ioutil"
)
const (
serviceAccountKey = "serviceaccount"
applicationKey = "application"
)
type keyFile struct {
Type string `json:"type"` // serviceaccount or application
KeyID string `json:"keyId"`
Key string `json:"key"`
Issuer string `json:"issuer"`
ClientID string `json:"clientId"`
//TokenURL string `json:"token_uri"`
//ProjectID string `json:"project_id"`
}
func ConfigFromKeyFile(path string) (*keyFile, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
var f keyFile
if err := json.Unmarshal(data, &f); err != nil {
return nil, err
}
return &f, nil
}

View file

@ -53,6 +53,9 @@ type RelayingParty interface {
//IsOAuth2Only specifies whether relaying party handles only oauth2 or oidc calls
IsOAuth2Only() bool
ClientKey() []byte
ClientKeyID() string
//IDTokenVerifier returns the verifier interface used for oidc id_token verification
IDTokenVerifier() IDTokenVerifier
@ -74,11 +77,13 @@ type relayingParty struct {
oauthConfig *oauth2.Config
oauth2Only bool
pkce bool
clientKey []byte
clientKeyID string
httpClient *http.Client
cookieHandler *utils.CookieHandler
errorHandler func(http.ResponseWriter, *http.Request, string, string, string)
errorHandler func(http.ResponseWriter, *http.Request, string, string, string)
idTokenVerifier IDTokenVerifier
verifierOpts []VerifierOption
}
@ -103,6 +108,14 @@ func (rp *relayingParty) IsOAuth2Only() bool {
return rp.oauth2Only
}
func (rp *relayingParty) ClientKey() []byte {
return rp.clientKey
}
func (rp *relayingParty) ClientKeyID() string {
return rp.clientKeyID
}
func (rp *relayingParty) IDTokenVerifier() IDTokenVerifier {
if rp.idTokenVerifier == nil {
rp.idTokenVerifier = NewIDTokenVerifier(rp.issuer, rp.oauthConfig.ClientID, NewRemoteKeySet(rp.httpClient, rp.endpoints.JKWsURL), rp.verifierOpts...)
@ -314,6 +327,14 @@ func CodeExchangeHandler(callback func(http.ResponseWriter, *http.Request, *oidc
}
codeOpts = append(codeOpts, WithCodeVerifier(codeVerifier))
}
//if len(rp.ClientKey()) > 0 {
// assertion, err := oidc.NewJWTProfileAssertionStringFromFileData(rp.ClientKey(), []string{rp.OAuthConfig().Endpoint.TokenURL})
// if err != nil {
// http.Error(w, "failed to build assertion: "+err.Error(), http.StatusUnauthorized)
// return
// }
// codeOpts = append(codeOpts, WithClientAssertionJWT(assertion))
//}
tokens, err := CodeExchange(r.Context(), params.Get("code"), rp, codeOpts...)
if err != nil {
http.Error(w, "failed to exchange token: "+err.Error(), http.StatusUnauthorized)
@ -439,3 +460,13 @@ func WithCodeVerifier(codeVerifier string) CodeExchangeOpt {
return []oauth2.AuthCodeOption{oauth2.SetAuthURLParam("code_verifier", codeVerifier)}
}
}
//WithClientAssertionJWT sets the `client_assertion` param in the token request
func WithClientAssertionJWT(clientAssertion string) CodeExchangeOpt {
return func() []oauth2.AuthCodeOption {
return []oauth2.AuthCodeOption{
oauth2.SetAuthURLParam("client_assertion", clientAssertion),
oauth2.SetAuthURLParam("client_assertion_type", oidc.ClientAssertionTypeJWTAssertion),
}
}
}

118
pkg/rp/resource_server.go Normal file
View file

@ -0,0 +1,118 @@
package rp
import (
"context"
"errors"
"net/http"
"golang.org/x/oauth2"
"golang.org/x/oauth2/clientcredentials"
"golang.org/x/oauth2/jwt"
"github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/utils"
)
type ResourceServer interface {
IntrospectionURL() string
HttpClient() *http.Client
}
type resourceServer struct {
issuer string
tokenURL string
introspectURL string
httpClient *http.Client
}
func (r *resourceServer) IntrospectionURL() string {
return r.introspectURL
}
func (r *resourceServer) HttpClient() *http.Client {
return r.httpClient
}
func NewResourceServerClientCredentials(issuer, clientID, clientSecret string, option RSOption) (ResourceServer, error) {
authorizer := func(tokenURL string) func(ctx context.Context) *http.Client {
return (&clientcredentials.Config{
ClientID: clientID,
ClientSecret: clientSecret,
TokenURL: tokenURL,
}).Client
}
return newResourceServer(issuer, authorizer, option)
}
func NewResourceServerJWTProfile(issuer, clientID, keyID string, key []byte, options ...RSOption) (ResourceServer, error) {
authorizer := func(tokenURL string) func(ctx context.Context) *http.Client {
return (&jwt.Config{
Email: clientID,
Subject: clientID,
PrivateKey: key,
PrivateKeyID: keyID,
Audience: issuer,
TokenURL: tokenURL,
}).Client
}
return newResourceServer(issuer, authorizer, options...)
}
func newResourceServer(issuer string, authorizer func(tokenURL string) func(ctx context.Context) *http.Client, options ...RSOption) (*resourceServer, error) {
rp := &resourceServer{
issuer: issuer,
httpClient: utils.DefaultHTTPClient,
}
for _, optFunc := range options {
optFunc(rp)
}
if rp.introspectURL == "" || rp.tokenURL == "" {
endpoints, err := Discover(rp.issuer, rp.httpClient)
if err != nil {
return nil, err
}
rp.tokenURL = endpoints.TokenURL
rp.introspectURL = endpoints.IntrospectURL
}
if rp.introspectURL == "" || rp.tokenURL == "" {
return nil, errors.New("introspectURL and/or tokenURL is empty: please provide with either `WithStaticEndpoints` or a discovery url")
}
rp.httpClient = authorizer(rp.tokenURL)(context.WithValue(context.Background(), oauth2.HTTPClient, rp.HttpClient()))
return rp, nil
}
func NewResourceServerFromKeyFile(path string, options ...RSOption) (ResourceServer, error) {
c, err := ConfigFromKeyFile(path)
if err != nil {
return nil, err
}
return NewResourceServerJWTProfile(c.Issuer, c.ClientID, c.KeyID, []byte(c.Key), options...)
}
type RSOption func(*resourceServer)
//WithClient provides the ability to set an http client to be used for the resource server
func WithClient(client *http.Client) RSOption {
return func(server *resourceServer) {
server.httpClient = client
}
}
//WithStaticEndpoints provides the ability to set static token and introspect URL
func WithStaticEndpoints(tokenURL, introspectURL string) RSOption {
return func(server *resourceServer) {
server.tokenURL = tokenURL
server.introspectURL = introspectURL
}
}
func Introspect(ctx context.Context, rp ResourceServer, token string) (oidc.IntrospectionResponse, error) {
req, err := utils.FormRequest(rp.IntrospectionURL(), &oidc.IntrospectionRequest{Token: token}, encoder, nil)
if err != nil {
return nil, err
}
resp := oidc.NewIntrospectionResponse()
if err := utils.HttpRequest(rp.HttpClient(), req, resp); err != nil {
return nil, err
}
return resp, nil
}

View file

@ -43,8 +43,8 @@ func DelegationTokenExchange(ctx context.Context, subjectToken string, rp Relayi
}
//JWTProfileExchange handles the oauth2 jwt profile exchange
func JWTProfileExchange(ctx context.Context, jwtProfileRequest *tokenexchange.JWTProfileRequest, rp RelayingParty) (*oauth2.Token, error) {
return CallTokenEndpoint(jwtProfileRequest, rp)
func JWTProfileExchange(ctx context.Context, jwtProfileGrantRequest *oidc.JWTProfileGrantRequest, rp RelayingParty) (*oauth2.Token, error) {
return CallTokenEndpoint(jwtProfileGrantRequest, rp)
}
//JWTProfileExchange handles the oauth2 jwt profile exchange
@ -53,7 +53,7 @@ func JWTProfileAssertionExchange(ctx context.Context, assertion *oidc.JWTProfile
if err != nil {
return nil, err
}
return JWTProfileExchange(ctx, tokenexchange.NewJWTProfileRequest(token, scopes...), rp)
return JWTProfileExchange(ctx, oidc.NewJWTProfileGrantRequest(token, scopes...), rp)
}
func generateJWTProfileToken(assertion *oidc.JWTProfileAssertion) (string, error) {