diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 0101ea5..6bdfcdf 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 471c09e..c404ba3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: go: ['1.14', '1.15', '1.16', '1.17'] name: Go ${{ matrix.go }} test steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup go uses: actions/setup-go@v2 with: @@ -36,7 +36,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Source checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Semantic Release uses: cycjimmy/semantic-release-action@v2 with: diff --git a/pkg/client/client.go b/pkg/client/client.go index 1828d1d..ac6cd56 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -26,8 +26,13 @@ var ( ) //Discover calls the discovery endpoint of the provided issuer and returns its configuration -func Discover(issuer string, httpClient *http.Client) (*oidc.DiscoveryConfiguration, error) { +//It accepts an optional argument "wellknownUrl" which can be used to overide the dicovery endpoint url +func Discover(issuer string, httpClient *http.Client, wellKnownUrl ...string) (*oidc.DiscoveryConfiguration, error) { + wellKnown := strings.TrimSuffix(issuer, "/") + oidc.DiscoveryEndpoint + if len(wellKnownUrl) == 1 && wellKnownUrl[0] != "" { + wellKnown = wellKnownUrl[0] + } req, err := http.NewRequest("GET", wellKnown, nil) if err != nil { return nil, err diff --git a/pkg/client/rp/relaying_party.go b/pkg/client/rp/relaying_party.go index 23c37fc..98ab354 100644 --- a/pkg/client/rp/relaying_party.go +++ b/pkg/client/rp/relaying_party.go @@ -69,11 +69,12 @@ var ( ) type relyingParty struct { - issuer string - endpoints Endpoints - oauthConfig *oauth2.Config - oauth2Only bool - pkce bool + issuer string + DiscoveryEndpoint string + endpoints Endpoints + oauthConfig *oauth2.Config + oauth2Only bool + pkce bool httpClient *http.Client cookieHandler *httphelper.CookieHandler @@ -170,7 +171,7 @@ func NewRelyingPartyOIDC(issuer, clientID, clientSecret, redirectURI string, sco return nil, err } } - discoveryConfiguration, err := client.Discover(rp.issuer, rp.httpClient) + discoveryConfiguration, err := client.Discover(rp.issuer, rp.httpClient, rp.DiscoveryEndpoint) if err != nil { return nil, err } @@ -184,6 +185,13 @@ func NewRelyingPartyOIDC(issuer, clientID, clientSecret, redirectURI string, sco //Option is the type for providing dynamic options to the relyingParty type Option func(*relyingParty) error +func WithCustomDiscoveryUrl(url string) Option { + return func(rp *relyingParty) error { + rp.DiscoveryEndpoint = url + return nil + } +} + //WithCookieHandler set a `CookieHandler` for securing the various redirects func WithCookieHandler(cookieHandler *httphelper.CookieHandler) Option { return func(rp *relyingParty) error { diff --git a/pkg/op/op.go b/pkg/op/op.go index 8a5be26..e910bf6 100644 --- a/pkg/op/op.go +++ b/pkg/op/op.go @@ -19,6 +19,7 @@ import ( const ( healthEndpoint = "/healthz" readinessEndpoint = "/ready" + authCallbackPathSuffix = "/callback" defaultAuthorizationEndpoint = "authorize" defaultTokenEndpoint = "oauth/token" defaultIntrospectEndpoint = "oauth/introspect" @@ -72,7 +73,7 @@ func CreateRouter(o OpenIDProvider, interceptors ...HttpInterceptor) *mux.Router router.HandleFunc(readinessEndpoint, readyHandler(o.Probes())) router.HandleFunc(oidc.DiscoveryEndpoint, discoveryHandler(o, o.Signer())) router.Handle(o.AuthorizationEndpoint().Relative(), intercept(authorizeHandler(o))) - router.NewRoute().Path(o.AuthorizationEndpoint().Relative()+"/callback").Queries("id", "{id}").Handler(intercept(authorizeCallbackHandler(o))) + router.NewRoute().Path(authCallbackPath(o)).Queries("id", "{id}").Handler(intercept(authorizeCallbackHandler(o))) router.Handle(o.TokenEndpoint().Relative(), intercept(tokenHandler(o))) router.HandleFunc(o.IntrospectionEndpoint().Relative(), introspectionHandler(o)) router.HandleFunc(o.UserinfoEndpoint().Relative(), userinfoHandler(o)) @@ -82,6 +83,17 @@ func CreateRouter(o OpenIDProvider, interceptors ...HttpInterceptor) *mux.Router return router } +//AuthCallbackURL builds the url for the redirect (with the requestID) after a successful login +func AuthCallbackURL(o OpenIDProvider) func(string) string { + return func(requestID string) string { + return o.AuthorizationEndpoint().Absolute(o.Issuer()) + authCallbackPathSuffix + "?id=" + requestID + } +} + +func authCallbackPath(o OpenIDProvider) string { + return o.AuthorizationEndpoint().Relative() + authCallbackPathSuffix +} + type Config struct { Issuer string CryptoKey [32]byte @@ -125,8 +137,8 @@ func NewOpenIDProvider(ctx context.Context, config *Config, storage Storage, opO } keyCh := make(chan jose.SigningKey) - o.signer = NewSigner(ctx, storage, keyCh) go storage.GetSigningKey(ctx, keyCh) + o.signer = NewSigner(ctx, storage, keyCh) o.httpHandler = CreateRouter(o, o.interceptors...) diff --git a/pkg/op/signer.go b/pkg/op/signer.go index c53819e..0dabf67 100644 --- a/pkg/op/signer.go +++ b/pkg/op/signer.go @@ -25,6 +25,12 @@ func NewSigner(ctx context.Context, storage AuthStorage, keyCh <-chan jose.Signi storage: storage, } + select { + case <-ctx.Done(): + return nil + case key := <-keyCh: + s.exchangeSigningKey(key) + } go s.refreshSigningKey(ctx, keyCh) return s @@ -50,23 +56,27 @@ func (s *tokenSigner) refreshSigningKey(ctx context.Context, keyCh <-chan jose.S case <-ctx.Done(): return case key := <-keyCh: - s.alg = key.Algorithm - if key.Algorithm == "" || key.Key == nil { - s.signer = nil - logging.Warn("signer has no key") - continue - } - var err error - s.signer, err = jose.NewSigner(key, &jose.SignerOptions{}) - if err != nil { - logging.New().WithError(err).Error("error creating signer") - continue - } - logging.Info("signer exchanged signing key") + s.exchangeSigningKey(key) } } } +func (s *tokenSigner) exchangeSigningKey(key jose.SigningKey) { + s.alg = key.Algorithm + if key.Algorithm == "" || key.Key == nil { + s.signer = nil + logging.Warn("signer has no key") + return + } + var err error + s.signer, err = jose.NewSigner(key, &jose.SignerOptions{}) + if err != nil { + logging.New().WithError(err).Error("error creating signer") + return + } + logging.Info("signer exchanged signing key") +} + func (s *tokenSigner) SignatureAlgorithm() jose.SignatureAlgorithm { return s.alg }