diff --git a/.releaserc.js b/.releaserc.js index 6500ace..7b9f1ce 100644 --- a/.releaserc.js +++ b/.releaserc.js @@ -1,5 +1,8 @@ module.exports = { - branches: ["main"], + branches: [ + {name: "main"}, + {name: "dynamic-issuer", prerelease: true}, + ], plugins: [ "@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator", diff --git a/README.md b/README.md index d0356ce..26a08ec 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ The most important packages of the library: /client/app web app / RP demonstrating authorization code flow using various authentication methods (code, PKCE, JWT profile) /client/github example of the extended OAuth2 library, providing an HTTP client with a reuse token source /client/service demonstration of JWT Profile Authorization Grant - /server example of an OpenID Provider implementation including some very basic login UI + /server examples of an OpenID Provider implementations (including dynamic) with some very basic login UI ## How To Use It @@ -44,16 +44,27 @@ Check the `/example` folder where example code for different scenarios is locate ```bash # start oidc op server # oidc discovery http://localhost:9998/.well-known/openid-configuration -go run github.com/zitadel/oidc/example/server +go run github.com/zitadel/oidc/v2/example/server # start oidc web client (in a new terminal) CLIENT_ID=web CLIENT_SECRET=secret ISSUER=http://localhost:9998 SCOPES="openid profile" PORT=9999 go run github.com/zitadel/oidc/example/client/app ``` - open http://localhost:9999/login in your browser - you will be redirected to op server and the login UI -- login with user `test-user` and password `verysecure` +- login with user `test-user@localhost` and password `verysecure` - the OP will redirect you to the client app, which displays the user info +for the dynamic issuer, just start it with: +```bash +go run github.com/zitadel/oidc/v2/example/server/dynamic +``` +the oidc web client above will still work, but if you add `oidc.local` (pointing to 127.0.0.1) in your hosts file you can also start it with: +```bash +CLIENT_ID=web CLIENT_SECRET=secret ISSUER=http://oidc.local:9998/ SCOPES="openid profile" PORT=9999 go run github.com/zitadel/oidc/v2/example/client/app +``` + +> Note: Usernames are suffixed with the hostname (`test-user@localhost` or `test-user@oidc.local`) + ## Features | | Code Flow | Implicit Flow | Hybrid Flow | Discovery | PKCE | Token Exchange | mTLS | JWT Profile | Refresh Token | Client Credentials | diff --git a/example/client/api/api.go b/example/client/api/api.go index 0ab669d..c475354 100644 --- a/example/client/api/api.go +++ b/example/client/api/api.go @@ -12,8 +12,8 @@ import ( "github.com/gorilla/mux" "github.com/sirupsen/logrus" - "github.com/zitadel/oidc/pkg/client/rs" - "github.com/zitadel/oidc/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/client/rs" + "github.com/zitadel/oidc/v2/pkg/oidc" ) const ( diff --git a/example/client/app/app.go b/example/client/app/app.go index b7f2868..3e5f19c 100644 --- a/example/client/app/app.go +++ b/example/client/app/app.go @@ -11,9 +11,9 @@ import ( "github.com/google/uuid" "github.com/sirupsen/logrus" - "github.com/zitadel/oidc/pkg/client/rp" - httphelper "github.com/zitadel/oidc/pkg/http" - "github.com/zitadel/oidc/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/client/rp" + httphelper "github.com/zitadel/oidc/v2/pkg/http" + "github.com/zitadel/oidc/v2/pkg/oidc" ) var ( diff --git a/example/client/github/github.go b/example/client/github/github.go index feb3e26..57bb3ae 100644 --- a/example/client/github/github.go +++ b/example/client/github/github.go @@ -10,9 +10,9 @@ import ( "golang.org/x/oauth2" githubOAuth "golang.org/x/oauth2/github" - "github.com/zitadel/oidc/pkg/client/rp" - "github.com/zitadel/oidc/pkg/client/rp/cli" - "github.com/zitadel/oidc/pkg/http" + "github.com/zitadel/oidc/v2/pkg/client/rp" + "github.com/zitadel/oidc/v2/pkg/client/rp/cli" + "github.com/zitadel/oidc/v2/pkg/http" ) var ( diff --git a/example/client/service/service.go b/example/client/service/service.go index b3819d5..9526174 100644 --- a/example/client/service/service.go +++ b/example/client/service/service.go @@ -13,7 +13,7 @@ import ( "github.com/sirupsen/logrus" "golang.org/x/oauth2" - "github.com/zitadel/oidc/pkg/client/profile" + "github.com/zitadel/oidc/v2/pkg/client/profile" ) var client = http.DefaultClient diff --git a/example/doc.go b/example/doc.go index 7212a7d..fd4f038 100644 --- a/example/doc.go +++ b/example/doc.go @@ -5,7 +5,6 @@ Package example contains some example of the various use of this library: /app web app / RP demonstrating authorization code flow using various authentication methods (code, PKCE, JWT profile) /github example of the extended OAuth2 library, providing an HTTP client with a reuse token source /service demonstration of JWT Profile Authorization Grant -/server example of an OpenID Provider implementation including some very basic login UI - +/server examples of an OpenID Provider implementations (including dynamic) with some very basic */ package example diff --git a/example/server/dynamic/login.go b/example/server/dynamic/login.go new file mode 100644 index 0000000..e7c6e5f --- /dev/null +++ b/example/server/dynamic/login.go @@ -0,0 +1,113 @@ +package main + +import ( + "context" + "fmt" + "html/template" + "net/http" + + "github.com/gorilla/mux" + + "github.com/zitadel/oidc/v2/pkg/op" +) + +const ( + queryAuthRequestID = "authRequestID" +) + +var ( + loginTmpl, _ = template.New("login").Parse(` + + + + + Login + + +
+ +
+ + +
+
+ + +
+

{{.Error}}

+ +
+ + `) +) + +type login struct { + authenticate authenticate + router *mux.Router + callback func(context.Context, string) string +} + +func NewLogin(authenticate authenticate, callback func(context.Context, string) string, issuerInterceptor *op.IssuerInterceptor) *login { + l := &login{ + authenticate: authenticate, + callback: callback, + } + l.createRouter(issuerInterceptor) + return l +} + +func (l *login) createRouter(issuerInterceptor *op.IssuerInterceptor) { + l.router = mux.NewRouter() + l.router.Path("/username").Methods("GET").HandlerFunc(l.loginHandler) + l.router.Path("/username").Methods("POST").HandlerFunc(issuerInterceptor.HandlerFunc(l.checkLoginHandler)) +} + +type authenticate interface { + CheckUsernamePassword(ctx context.Context, username, password, id string) error +} + +func (l *login) loginHandler(w http.ResponseWriter, r *http.Request) { + err := r.ParseForm() + if err != nil { + http.Error(w, fmt.Sprintf("cannot parse form:%s", err), http.StatusInternalServerError) + return + } + //the oidc package will pass the id of the auth request as query parameter + //we will use this id through the login process and therefore pass it to the login page + renderLogin(w, r.FormValue(queryAuthRequestID), nil) +} + +func renderLogin(w http.ResponseWriter, id string, err error) { + var errMsg string + if err != nil { + errMsg = err.Error() + } + data := &struct { + ID string + Error string + }{ + ID: id, + Error: errMsg, + } + err = loginTmpl.Execute(w, data) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } +} + +func (l *login) checkLoginHandler(w http.ResponseWriter, r *http.Request) { + err := r.ParseForm() + if err != nil { + http.Error(w, fmt.Sprintf("cannot parse form:%s", err), http.StatusInternalServerError) + return + } + username := r.FormValue("username") + password := r.FormValue("password") + id := r.FormValue("id") + err = l.authenticate.CheckUsernamePassword(r.Context(), username, password, id) + if err != nil { + renderLogin(w, id, err) + return + } + http.Redirect(w, r, l.callback(r.Context(), id), http.StatusFound) +} diff --git a/example/server/dynamic/op.go b/example/server/dynamic/op.go new file mode 100644 index 0000000..02c12b2 --- /dev/null +++ b/example/server/dynamic/op.go @@ -0,0 +1,138 @@ +package main + +import ( + "context" + "crypto/sha256" + "fmt" + "log" + "net/http" + + "github.com/gorilla/mux" + "golang.org/x/text/language" + + "github.com/zitadel/oidc/v2/example/server/storage" + "github.com/zitadel/oidc/v2/pkg/op" +) + +const ( + pathLoggedOut = "/logged-out" +) + +var ( + hostnames = []string{ + "localhost", //note that calling 127.0.0.1 / ::1 won't work as the hostname does not match + "oidc.local", //add this to your hosts file (pointing to 127.0.0.1) + //feel free to add more... + } +) + +func init() { + storage.RegisterClients( + storage.NativeClient("native"), + storage.WebClient("web", "secret"), + storage.WebClient("api", "secret"), + ) +} + +func main() { + ctx := context.Background() + + port := "9998" + issuers := make([]string, len(hostnames)) + for i, hostname := range hostnames { + issuers[i] = fmt.Sprintf("http://%s:%s/", hostname, port) + } + + //the OpenID Provider requires a 32-byte key for (token) encryption + //be sure to create a proper crypto random key and manage it securely! + key := sha256.Sum256([]byte("test")) + + router := mux.NewRouter() + + //for simplicity, we provide a very small default page for users who have signed out + router.HandleFunc(pathLoggedOut, func(w http.ResponseWriter, req *http.Request) { + _, err := w.Write([]byte("signed out successfully")) + if err != nil { + log.Printf("error serving logged out page: %v", err) + } + }) + + //the OpenIDProvider interface needs a Storage interface handling various checks and state manipulations + //this might be the layer for accessing your database + //in this example it will be handled in-memory + //the NewMultiStorage is able to handle multiple issuers + storage := storage.NewMultiStorage(issuers) + + //creation of the OpenIDProvider with the just created in-memory Storage + provider, err := newDynamicOP(ctx, storage, key) + if err != nil { + log.Fatal(err) + } + + //the provider will only take care of the OpenID Protocol, so there must be some sort of UI for the login process + //for the simplicity of the example this means a simple page with username and password field + //be sure to provide an IssuerInterceptor with the IssuerFromRequest from the OP so the login can select / and pass it to the storage + l := NewLogin(storage, op.AuthCallbackURL(provider), op.NewIssuerInterceptor(provider.IssuerFromRequest)) + + //regardless of how many pages / steps there are in the process, the UI must be registered in the router, + //so we will direct all calls to /login to the login UI + router.PathPrefix("/login/").Handler(http.StripPrefix("/login", l.router)) + + //we register the http handler of the OP on the root, so that the discovery endpoint (/.well-known/openid-configuration) + //is served on the correct path + // + //if your issuer ends with a path (e.g. http://localhost:9998/custom/path/), + //then you would have to set the path prefix (/custom/path/): + //router.PathPrefix("/custom/path/").Handler(http.StripPrefix("/custom/path", provider.HttpHandler())) + router.PathPrefix("/").Handler(provider.HttpHandler()) + + server := &http.Server{ + Addr: ":" + port, + Handler: router, + } + err = server.ListenAndServe() + if err != nil { + log.Fatal(err) + } + <-ctx.Done() +} + +// newDynamicOP will create an OpenID Provider for localhost on a specified port with a given encryption key +// and a predefined default logout uri +// it will enable all options (see descriptions) +func newDynamicOP(ctx context.Context, storage op.Storage, key [32]byte) (*op.Provider, error) { + config := &op.Config{ + CryptoKey: key, + + //will be used if the end_session endpoint is called without a post_logout_redirect_uri + DefaultLogoutRedirectURI: pathLoggedOut, + + //enables code_challenge_method S256 for PKCE (and therefore PKCE in general) + CodeMethodS256: true, + + //enables additional client_id/client_secret authentication by form post (not only HTTP Basic Auth) + AuthMethodPost: true, + + //enables additional authentication by using private_key_jwt + AuthMethodPrivateKeyJWT: true, + + //enables refresh_token grant use + GrantTypeRefreshToken: true, + + //enables use of the `request` Object parameter + RequestObjectSupported: true, + + //this example has only static texts (in English), so we'll set the here accordingly + SupportedUILocales: []language.Tag{language.English}, + } + handler, err := op.NewDynamicOpenIDProvider(ctx, "/", config, storage, + //we must explicitly allow the use of the http issuer + op.WithAllowInsecure(), + //as an example on how to customize an endpoint this will change the authorization_endpoint from /authorize to /auth + op.WithCustomAuthEndpoint(op.NewEndpoint("auth")), + ) + if err != nil { + return nil, err + } + return handler, nil +} diff --git a/example/server/exampleop/login.go b/example/server/exampleop/login.go index fd3dead..5da86d1 100644 --- a/example/server/exampleop/login.go +++ b/example/server/exampleop/login.go @@ -1,6 +1,7 @@ package exampleop import ( + "context" "fmt" "html/template" "net/http" @@ -44,10 +45,10 @@ var loginTmpl, _ = template.New("login").Parse(` type login struct { authenticate authenticate router *mux.Router - callback func(string) string + callback func(context.Context, string) string } -func NewLogin(authenticate authenticate, callback func(string) string) *login { +func NewLogin(authenticate authenticate, callback func(context.Context, string) string) *login { l := &login{ authenticate: authenticate, callback: callback, @@ -109,5 +110,5 @@ func (l *login) checkLoginHandler(w http.ResponseWriter, r *http.Request) { renderLogin(w, id, err) return } - http.Redirect(w, r, l.callback(id), http.StatusFound) + http.Redirect(w, r, l.callback(r.Context(), id), http.StatusFound) } diff --git a/example/server/exampleop/op.go b/example/server/exampleop/op.go index 4794d8a..d3a450c 100644 --- a/example/server/exampleop/op.go +++ b/example/server/exampleop/op.go @@ -5,13 +5,12 @@ import ( "crypto/sha256" "log" "net/http" - "os" "github.com/gorilla/mux" "golang.org/x/text/language" - "github.com/zitadel/oidc/example/server/storage" - "github.com/zitadel/oidc/pkg/op" + "github.com/zitadel/oidc/v2/example/server/storage" + "github.com/zitadel/oidc/v2/pkg/op" ) const ( @@ -35,9 +34,6 @@ type Storage interface { // // Use one of the pre-made clients in storage/clients.go or register a new one. func SetupServer(ctx context.Context, issuer string, storage Storage) *mux.Router { - // this will allow us to use an issuer with http:// instead of https:// - os.Setenv(op.OidcDevMode, "true") - // the OpenID Provider requires a 32-byte key for (token) encryption // be sure to create a proper crypto random key and manage it securely! key := sha256.Sum256([]byte("test")) @@ -81,7 +77,6 @@ func SetupServer(ctx context.Context, issuer string, storage Storage) *mux.Route // it will enable all options (see descriptions) func newOP(ctx context.Context, storage op.Storage, issuer string, key [32]byte) (op.OpenIDProvider, error) { config := &op.Config{ - Issuer: issuer, CryptoKey: key, // will be used if the end_session endpoint is called without a post_logout_redirect_uri @@ -105,7 +100,9 @@ func newOP(ctx context.Context, storage op.Storage, issuer string, key [32]byte) // this example has only static texts (in English), so we'll set the here accordingly SupportedUILocales: []language.Tag{language.English}, } - handler, err := op.NewOpenIDProvider(ctx, config, storage, + handler, err := op.NewOpenIDProvider(ctx, issuer, config, storage, + //we must explicitly allow the use of the http issuer + op.WithAllowInsecure(), // as an example on how to customize an endpoint this will change the authorization_endpoint from /authorize to /auth op.WithCustomAuthEndpoint(op.NewEndpoint("auth")), ) diff --git a/example/server/main.go b/example/server/main.go index 3cfd20d..327e294 100644 --- a/example/server/main.go +++ b/example/server/main.go @@ -2,23 +2,28 @@ package main import ( "context" + "fmt" "log" "net/http" - "github.com/zitadel/oidc/example/server/exampleop" - "github.com/zitadel/oidc/example/server/storage" + "github.com/zitadel/oidc/v2/example/server/exampleop" + "github.com/zitadel/oidc/v2/example/server/storage" ) func main() { ctx := context.Background() + //we will run on :9998 + port := "9998" + //which gives us the issuer: //http://localhost:9998/ + issuer := fmt.Sprintf("http://localhost:%s/", port) + // the OpenIDProvider interface needs a Storage interface handling various checks and state manipulations // this might be the layer for accessing your database // in this example it will be handled in-memory - storage := storage.NewStorage(storage.NewUserStore()) + storage := storage.NewStorage(storage.NewUserStore(issuer)) - port := "9998" - router := exampleop.SetupServer(ctx, "http://localhost:"+port, storage) + router := exampleop.SetupServer(ctx, issuer, storage) server := &http.Server{ Addr: ":" + port, diff --git a/example/server/storage/client.go b/example/server/storage/client.go index 0f3a703..bd6ff3c 100644 --- a/example/server/storage/client.go +++ b/example/server/storage/client.go @@ -3,8 +3,8 @@ package storage import ( "time" - "github.com/zitadel/oidc/pkg/oidc" - "github.com/zitadel/oidc/pkg/op" + "github.com/zitadel/oidc/v2/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/op" ) var ( @@ -113,14 +113,14 @@ func (c *Client) IsScopeAllowed(scope string) bool { // IDTokenUserinfoClaimsAssertion allows specifying if claims of scope profile, email, phone and address are asserted into the id_token // even if an access token if issued which violates the OIDC Core spec -//(5.4. Requesting Claims using Scope Values: https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims) +// (5.4. Requesting Claims using Scope Values: https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims) // some clients though require that e.g. email is always in the id_token when requested even if an access_token is issued func (c *Client) IDTokenUserinfoClaimsAssertion() bool { return c.idTokenUserinfoClaimsAssertion } // ClockSkew enables clients to instruct the OP to apply a clock skew on the various times and expirations -//(subtract from issued_at, add to expiration, ...) +// (subtract from issued_at, add to expiration, ...) func (c *Client) ClockSkew() time.Duration { return c.clockSkew } @@ -141,7 +141,7 @@ func RegisterClients(registerClients ...*Client) { // user-defined redirectURIs may include: // - http://localhost without port specification (e.g. http://localhost/auth/callback) // - custom protocol (e.g. custom://auth/callback) -//(the examples will be used as default, if none is provided) +// (the examples will be used as default, if none is provided) func NativeClient(id string, redirectURIs ...string) *Client { if len(redirectURIs) == 0 { redirectURIs = []string{ @@ -168,7 +168,7 @@ func NativeClient(id string, redirectURIs ...string) *Client { // WebClient will create a client of type web, which will always use Basic Auth and allow the use of refresh tokens // user-defined redirectURIs may include: // - http://localhost with port specification (e.g. http://localhost:9999/auth/callback) -//(the example will be used as default, if none is provided) +// (the example will be used as default, if none is provided) func WebClient(id, secret string, redirectURIs ...string) *Client { if len(redirectURIs) == 0 { redirectURIs = []string{ diff --git a/example/server/storage/oidc.go b/example/server/storage/oidc.go index 91afd90..505ab72 100644 --- a/example/server/storage/oidc.go +++ b/example/server/storage/oidc.go @@ -5,9 +5,8 @@ import ( "golang.org/x/text/language" - "github.com/zitadel/oidc/pkg/op" - - "github.com/zitadel/oidc/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/op" ) const ( diff --git a/example/server/storage/storage.go b/example/server/storage/storage.go index 130822e..64bffc8 100644 --- a/example/server/storage/storage.go +++ b/example/server/storage/storage.go @@ -12,8 +12,8 @@ import ( "github.com/google/uuid" "gopkg.in/square/go-jose.v2" - "github.com/zitadel/oidc/pkg/oidc" - "github.com/zitadel/oidc/pkg/op" + "github.com/zitadel/oidc/v2/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/op" ) // serviceKey1 is a public key which will be used for the JWT Profile Authorization Grant @@ -45,9 +45,41 @@ type Storage struct { } type signingKey struct { - ID string - Algorithm string - Key *rsa.PrivateKey + id string + algorithm jose.SignatureAlgorithm + key *rsa.PrivateKey +} + +func (s *signingKey) SignatureAlgorithm() jose.SignatureAlgorithm { + return s.algorithm +} + +func (s *signingKey) Key() interface{} { + return s.key +} + +func (s *signingKey) ID() string { + return s.id +} + +type publicKey struct { + signingKey +} + +func (s *publicKey) ID() string { + return s.id +} + +func (s *publicKey) Algorithm() jose.SignatureAlgorithm { + return s.algorithm +} + +func (s *publicKey) Use() string { + return "sig" +} + +func (s *publicKey) Key() interface{} { + return &s.key.PublicKey } func NewStorage(userStore UserStore) *Storage { @@ -67,9 +99,9 @@ func NewStorage(userStore UserStore) *Storage { }, }, signingKey: signingKey{ - ID: "id", - Algorithm: "RS256", - Key: key, + id: uuid.NewString(), + algorithm: jose.RS256, + key: key, }, } } @@ -288,41 +320,29 @@ func (s *Storage) RevokeToken(ctx context.Context, tokenIDOrToken string, userID return nil } -// GetSigningKey implements the op.Storage interface +// SigningKey implements the op.Storage interface // it will be called when creating the OpenID Provider -func (s *Storage) GetSigningKey(ctx context.Context, keyCh chan<- jose.SigningKey) { +func (s *Storage) SigningKey(ctx context.Context) (op.SigningKey, error) { // in this example the signing key is a static rsa.PrivateKey and the algorithm used is RS256 // you would obviously have a more complex implementation and store / retrieve the key from your database as well - // - // the idea of the signing key channel is, that you can (with what ever mechanism) rotate your signing key and - // switch the key of the signer via this channel - keyCh <- jose.SigningKey{ - Algorithm: jose.SignatureAlgorithm(s.signingKey.Algorithm), // always tell the signer with algorithm to use - Key: jose.JSONWebKey{ - KeyID: s.signingKey.ID, // always give the key an id so, that it will include it in the token header as `kid` claim - Key: s.signingKey.Key, - }, - } + return &s.signingKey, nil } -// GetKeySet implements the op.Storage interface +// SignatureAlgorithms implements the op.Storage interface +// it will be called to get the sign +func (s *Storage) SignatureAlgorithms(context.Context) ([]jose.SignatureAlgorithm, error) { + return []jose.SignatureAlgorithm{s.signingKey.algorithm}, nil +} + +// KeySet implements the op.Storage interface // it will be called to get the current (public) keys, among others for the keys_endpoint or for validating access_tokens on the userinfo_endpoint, ... -func (s *Storage) GetKeySet(ctx context.Context) (*jose.JSONWebKeySet, error) { +func (s *Storage) KeySet(ctx context.Context) ([]op.Key, error) { // as mentioned above, this example only has a single signing key without key rotation, // so it will directly use its public key // // when using key rotation you typically would store the public keys alongside the private keys in your database - // and give both of them an expiration date, with the public key having a longer lifetime (e.g. rotate private key every - return &jose.JSONWebKeySet{ - Keys: []jose.JSONWebKey{ - { - KeyID: s.signingKey.ID, - Algorithm: s.signingKey.Algorithm, - Use: oidc.KeyUseSignature, - Key: &s.signingKey.Key.PublicKey, - }, - }, - }, nil + //and give both of them an expiration date, with the public key having a longer lifetime + return []op.Key{&publicKey{s.signingKey}}, nil } // GetClientByClientID implements the op.Storage interface diff --git a/example/server/storage/storage_dynamic.go b/example/server/storage/storage_dynamic.go new file mode 100644 index 0000000..ec6a92e --- /dev/null +++ b/example/server/storage/storage_dynamic.go @@ -0,0 +1,260 @@ +package storage + +import ( + "context" + "time" + + "gopkg.in/square/go-jose.v2" + + "github.com/zitadel/oidc/v2/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/op" +) + +type multiStorage struct { + issuers map[string]*Storage +} + +// NewMultiStorage implements the op.Storage interface by wrapping multiple storage structs +// and selecting them by the calling issuer +func NewMultiStorage(issuers []string) *multiStorage { + s := make(map[string]*Storage) + for _, issuer := range issuers { + s[issuer] = NewStorage(NewUserStore(issuer)) + } + return &multiStorage{issuers: s} +} + +// CheckUsernamePassword implements the `authenticate` interface of the login +func (s *multiStorage) CheckUsernamePassword(ctx context.Context, username, password, id string) error { + storage, err := s.storageFromContext(ctx) + if err != nil { + return err + } + return storage.CheckUsernamePassword(username, password, id) +} + +// CreateAuthRequest implements the op.Storage interface +// it will be called after parsing and validation of the authentication request +func (s *multiStorage) CreateAuthRequest(ctx context.Context, authReq *oidc.AuthRequest, userID string) (op.AuthRequest, error) { + storage, err := s.storageFromContext(ctx) + if err != nil { + return nil, err + } + return storage.CreateAuthRequest(ctx, authReq, userID) +} + +// AuthRequestByID implements the op.Storage interface +// it will be called after the Login UI redirects back to the OIDC endpoint +func (s *multiStorage) AuthRequestByID(ctx context.Context, id string) (op.AuthRequest, error) { + storage, err := s.storageFromContext(ctx) + if err != nil { + return nil, err + } + return storage.AuthRequestByID(ctx, id) +} + +// AuthRequestByCode implements the op.Storage interface +// it will be called after parsing and validation of the token request (in an authorization code flow) +func (s *multiStorage) AuthRequestByCode(ctx context.Context, code string) (op.AuthRequest, error) { + storage, err := s.storageFromContext(ctx) + if err != nil { + return nil, err + } + return storage.AuthRequestByCode(ctx, code) +} + +// SaveAuthCode implements the op.Storage interface +// it will be called after the authentication has been successful and before redirecting the user agent to the redirect_uri +// (in an authorization code flow) +func (s *multiStorage) SaveAuthCode(ctx context.Context, id string, code string) error { + storage, err := s.storageFromContext(ctx) + if err != nil { + return err + } + return storage.SaveAuthCode(ctx, id, code) +} + +// DeleteAuthRequest implements the op.Storage interface +// it will be called after creating the token response (id and access tokens) for a valid +// - authentication request (in an implicit flow) +// - token request (in an authorization code flow) +func (s *multiStorage) DeleteAuthRequest(ctx context.Context, id string) error { + storage, err := s.storageFromContext(ctx) + if err != nil { + return err + } + return storage.DeleteAuthRequest(ctx, id) +} + +// CreateAccessToken implements the op.Storage interface +// it will be called for all requests able to return an access token (Authorization Code Flow, Implicit Flow, JWT Profile, ...) +func (s *multiStorage) CreateAccessToken(ctx context.Context, request op.TokenRequest) (string, time.Time, error) { + storage, err := s.storageFromContext(ctx) + if err != nil { + return "", time.Time{}, err + } + return storage.CreateAccessToken(ctx, request) +} + +// CreateAccessAndRefreshTokens implements the op.Storage interface +// it will be called for all requests able to return an access and refresh token (Authorization Code Flow, Refresh Token Request) +func (s *multiStorage) CreateAccessAndRefreshTokens(ctx context.Context, request op.TokenRequest, currentRefreshToken string) (accessTokenID string, newRefreshToken string, expiration time.Time, err error) { + storage, err := s.storageFromContext(ctx) + if err != nil { + return "", "", time.Time{}, err + } + return storage.CreateAccessAndRefreshTokens(ctx, request, currentRefreshToken) +} + +// TokenRequestByRefreshToken implements the op.Storage interface +// it will be called after parsing and validation of the refresh token request +func (s *multiStorage) TokenRequestByRefreshToken(ctx context.Context, refreshToken string) (op.RefreshTokenRequest, error) { + storage, err := s.storageFromContext(ctx) + if err != nil { + return nil, err + } + return storage.TokenRequestByRefreshToken(ctx, refreshToken) +} + +// TerminateSession implements the op.Storage interface +// it will be called after the user signed out, therefore the access and refresh token of the user of this client must be removed +func (s *multiStorage) TerminateSession(ctx context.Context, userID string, clientID string) error { + storage, err := s.storageFromContext(ctx) + if err != nil { + return err + } + return storage.TerminateSession(ctx, userID, clientID) +} + +// RevokeToken implements the op.Storage interface +// it will be called after parsing and validation of the token revocation request +func (s *multiStorage) RevokeToken(ctx context.Context, token string, userID string, clientID string) *oidc.Error { + storage, err := s.storageFromContext(ctx) + if err != nil { + return err + } + return storage.RevokeToken(ctx, token, userID, clientID) +} + +// SigningKey implements the op.Storage interface +// it will be called when creating the OpenID Provider +func (s *multiStorage) SigningKey(ctx context.Context) (op.SigningKey, error) { + storage, err := s.storageFromContext(ctx) + if err != nil { + return nil, err + } + return storage.SigningKey(ctx) +} + +// SignatureAlgorithms implements the op.Storage interface +// it will be called to get the sign +func (s *multiStorage) SignatureAlgorithms(ctx context.Context) ([]jose.SignatureAlgorithm, error) { + storage, err := s.storageFromContext(ctx) + if err != nil { + return nil, err + } + return storage.SignatureAlgorithms(ctx) +} + +// KeySet implements the op.Storage interface +// it will be called to get the current (public) keys, among others for the keys_endpoint or for validating access_tokens on the userinfo_endpoint, ... +func (s *multiStorage) KeySet(ctx context.Context) ([]op.Key, error) { + storage, err := s.storageFromContext(ctx) + if err != nil { + return nil, err + } + return storage.KeySet(ctx) +} + +// GetClientByClientID implements the op.Storage interface +// it will be called whenever information (type, redirect_uris, ...) about the client behind the client_id is needed +func (s *multiStorage) GetClientByClientID(ctx context.Context, clientID string) (op.Client, error) { + storage, err := s.storageFromContext(ctx) + if err != nil { + return nil, err + } + return storage.GetClientByClientID(ctx, clientID) +} + +// AuthorizeClientIDSecret implements the op.Storage interface +// it will be called for validating the client_id, client_secret on token or introspection requests +func (s *multiStorage) AuthorizeClientIDSecret(ctx context.Context, clientID, clientSecret string) error { + storage, err := s.storageFromContext(ctx) + if err != nil { + return err + } + return storage.AuthorizeClientIDSecret(ctx, clientID, clientSecret) +} + +// SetUserinfoFromScopes implements the op.Storage interface +// it will be called for the creation of an id_token, so we'll just pass it to the private function without any further check +func (s *multiStorage) SetUserinfoFromScopes(ctx context.Context, userinfo oidc.UserInfoSetter, userID, clientID string, scopes []string) error { + storage, err := s.storageFromContext(ctx) + if err != nil { + return err + } + return storage.SetUserinfoFromScopes(ctx, userinfo, userID, clientID, scopes) +} + +// SetUserinfoFromToken implements the op.Storage interface +// it will be called for the userinfo endpoint, so we read the token and pass the information from that to the private function +func (s *multiStorage) SetUserinfoFromToken(ctx context.Context, userinfo oidc.UserInfoSetter, tokenID, subject, origin string) error { + storage, err := s.storageFromContext(ctx) + if err != nil { + return err + } + return storage.SetUserinfoFromToken(ctx, userinfo, tokenID, subject, origin) +} + +// SetIntrospectionFromToken implements the op.Storage interface +// it will be called for the introspection endpoint, so we read the token and pass the information from that to the private function +func (s *multiStorage) SetIntrospectionFromToken(ctx context.Context, introspection oidc.IntrospectionResponse, tokenID, subject, clientID string) error { + storage, err := s.storageFromContext(ctx) + if err != nil { + return err + } + return storage.SetIntrospectionFromToken(ctx, introspection, tokenID, subject, clientID) +} + +// GetPrivateClaimsFromScopes implements the op.Storage interface +// it will be called for the creation of a JWT access token to assert claims for custom scopes +func (s *multiStorage) GetPrivateClaimsFromScopes(ctx context.Context, userID, clientID string, scopes []string) (claims map[string]interface{}, err error) { + storage, err := s.storageFromContext(ctx) + if err != nil { + return nil, err + } + return storage.GetPrivateClaimsFromScopes(ctx, userID, clientID, scopes) +} + +// GetKeyByIDAndUserID implements the op.Storage interface +// it will be called to validate the signatures of a JWT (JWT Profile Grant and Authentication) +func (s *multiStorage) GetKeyByIDAndUserID(ctx context.Context, keyID, userID string) (*jose.JSONWebKey, error) { + storage, err := s.storageFromContext(ctx) + if err != nil { + return nil, err + } + return storage.GetKeyByIDAndUserID(ctx, keyID, userID) +} + +// ValidateJWTProfileScopes implements the op.Storage interface +// it will be called to validate the scopes of a JWT Profile Authorization Grant request +func (s *multiStorage) ValidateJWTProfileScopes(ctx context.Context, userID string, scopes []string) ([]string, error) { + storage, err := s.storageFromContext(ctx) + if err != nil { + return nil, err + } + return storage.ValidateJWTProfileScopes(ctx, userID, scopes) +} + +// Health implements the op.Storage interface +func (s *multiStorage) Health(ctx context.Context) error { + return nil +} + +func (s *multiStorage) storageFromContext(ctx context.Context) (*Storage, *oidc.Error) { + storage, ok := s.issuers[op.IssuerFromContext(ctx)] + if !ok { + return nil, oidc.ErrInvalidRequest().WithDescription("invalid issuer") + } + return storage, nil +} diff --git a/example/server/storage/user.go b/example/server/storage/user.go index 423af59..82c06d0 100644 --- a/example/server/storage/user.go +++ b/example/server/storage/user.go @@ -2,6 +2,7 @@ package storage import ( "crypto/rsa" + "strings" "golang.org/x/text/language" ) @@ -33,12 +34,13 @@ type userStore struct { users map[string]*User } -func NewUserStore() UserStore { +func NewUserStore(issuer string) UserStore { + hostname := strings.Split(strings.Split(issuer, "://")[1], ":")[0] return userStore{ users: map[string]*User{ "id1": { ID: "id1", - Username: "test-user", + Username: "test-user@" + hostname, Password: "verysecure", FirstName: "Test", LastName: "User", diff --git a/go.mod b/go.mod index 18ae410..2691e57 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/zitadel/oidc +module github.com/zitadel/oidc/v2 go 1.16 @@ -15,9 +15,8 @@ require ( github.com/rs/cors v1.8.3 github.com/sirupsen/logrus v1.9.0 github.com/stretchr/testify v1.8.1 - github.com/zitadel/logging v0.3.4 golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 - golang.org/x/text v0.5.0 + golang.org/x/text v0.6.0 gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect gopkg.in/square/go-jose.v2 v2.6.0 ) diff --git a/go.sum b/go.sum index ec7b82e..c73eb9d 100644 --- a/go.sum +++ b/go.sum @@ -131,13 +131,11 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo= github.com/rs/cors v1.8.3/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -149,8 +147,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zitadel/logging v0.3.4 h1:9hZsTjMMTE3X2LUi0xcF9Q9EdLo+FAezeu52ireBbHM= -github.com/zitadel/logging v0.3.4/go.mod h1:aPpLQhE+v6ocNK0TWrBrd363hZ95KcI17Q1ixAQwZF0= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -252,7 +248,6 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -272,7 +267,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= @@ -285,8 +279,8 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -420,10 +414,7 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/client/client.go b/pkg/client/client.go index 62f1019..eaa1a80 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -14,9 +14,9 @@ import ( "golang.org/x/oauth2" "gopkg.in/square/go-jose.v2" - "github.com/zitadel/oidc/pkg/crypto" - httphelper "github.com/zitadel/oidc/pkg/http" - "github.com/zitadel/oidc/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/crypto" + httphelper "github.com/zitadel/oidc/v2/pkg/http" + "github.com/zitadel/oidc/v2/pkg/oidc" ) var Encoder = func() httphelper.Encoder { diff --git a/pkg/client/jwt_profile.go b/pkg/client/jwt_profile.go index a711de9..1686de6 100644 --- a/pkg/client/jwt_profile.go +++ b/pkg/client/jwt_profile.go @@ -5,8 +5,8 @@ import ( "golang.org/x/oauth2" - "github.com/zitadel/oidc/pkg/http" - "github.com/zitadel/oidc/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/http" + "github.com/zitadel/oidc/v2/pkg/oidc" ) // JWTProfileExchange handles the oauth2 jwt profile exchange diff --git a/pkg/client/profile/jwt_profile.go b/pkg/client/profile/jwt_profile.go index b29fcaa..a934f7d 100644 --- a/pkg/client/profile/jwt_profile.go +++ b/pkg/client/profile/jwt_profile.go @@ -7,8 +7,8 @@ import ( "golang.org/x/oauth2" "gopkg.in/square/go-jose.v2" - "github.com/zitadel/oidc/pkg/client" - "github.com/zitadel/oidc/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/client" + "github.com/zitadel/oidc/v2/pkg/oidc" ) // jwtProfileTokenSource implement the oauth2.TokenSource diff --git a/pkg/client/rp/cli/cli.go b/pkg/client/rp/cli/cli.go index 6e30e4e..936f319 100644 --- a/pkg/client/rp/cli/cli.go +++ b/pkg/client/rp/cli/cli.go @@ -4,9 +4,9 @@ import ( "context" "net/http" - "github.com/zitadel/oidc/pkg/client/rp" - httphelper "github.com/zitadel/oidc/pkg/http" - "github.com/zitadel/oidc/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/client/rp" + httphelper "github.com/zitadel/oidc/v2/pkg/http" + "github.com/zitadel/oidc/v2/pkg/oidc" ) const ( diff --git a/pkg/client/rp/delegation.go b/pkg/client/rp/delegation.go index a2b1f00..b16a39e 100644 --- a/pkg/client/rp/delegation.go +++ b/pkg/client/rp/delegation.go @@ -1,13 +1,13 @@ package rp import ( - "github.com/zitadel/oidc/pkg/oidc/grants/tokenexchange" + "github.com/zitadel/oidc/v2/pkg/oidc/grants/tokenexchange" ) // DelegationTokenRequest is an implementation of TokenExchangeRequest // it exchanges an "urn:ietf:params:oauth:token-type:access_token" with an optional -//"urn:ietf:params:oauth:token-type:access_token" actor token for an -//"urn:ietf:params:oauth:token-type:access_token" delegation token +// "urn:ietf:params:oauth:token-type:access_token" actor token for an +// "urn:ietf:params:oauth:token-type:access_token" delegation token func DelegationTokenRequest(subjectToken string, opts ...tokenexchange.TokenExchangeOption) *tokenexchange.TokenExchangeRequest { return tokenexchange.NewTokenExchangeRequest(subjectToken, tokenexchange.AccessTokenType, opts...) } diff --git a/pkg/client/rp/integration_test.go b/pkg/client/rp/integration_test.go index 6f5f489..e29ddd3 100644 --- a/pkg/client/rp/integration_test.go +++ b/pkg/client/rp/integration_test.go @@ -15,28 +15,27 @@ import ( "testing" "time" - "github.com/zitadel/oidc/example/server/exampleop" - "github.com/zitadel/oidc/example/server/storage" - "github.com/jeremija/gosubmit" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/zitadel/oidc/pkg/client/rp" - httphelper "github.com/zitadel/oidc/pkg/http" - "github.com/zitadel/oidc/pkg/oidc" + "github.com/zitadel/oidc/v2/example/server/exampleop" + "github.com/zitadel/oidc/v2/example/server/storage" + "github.com/zitadel/oidc/v2/pkg/client/rp" + httphelper "github.com/zitadel/oidc/v2/pkg/http" + "github.com/zitadel/oidc/v2/pkg/oidc" ) func TestRelyingPartySession(t *testing.T) { t.Log("------- start example OP ------") ctx := context.Background() - exampleStorage := storage.NewStorage(storage.NewUserStore()) + targetURL := "http://local-site" + exampleStorage := storage.NewStorage(storage.NewUserStore(targetURL)) var dh deferredHandler opServer := httptest.NewServer(&dh) defer opServer.Close() t.Logf("auth server at %s", opServer.URL) dh.Handler = exampleop.SetupServer(ctx, opServer.URL, exampleStorage) - targetURL := "http://local-site" localURL, err := url.Parse(targetURL + "/login?requestID=1234") require.NoError(t, err, "local url") @@ -109,7 +108,7 @@ func TestRelyingPartySession(t *testing.T) { t.Log("------- post to login form, get redirect to OP ------") postLoginRedirectURL := fillForm(t, "fill login form", httpClient, form, loginPageURL, - gosubmit.Set("username", "test-user"), + gosubmit.Set("username", "test-user@local-site"), gosubmit.Set("password", "verysecure")) t.Logf("Get redirect from %s", postLoginRedirectURL) diff --git a/pkg/client/rp/jwks.go b/pkg/client/rp/jwks.go index cc49eb7..3438bd6 100644 --- a/pkg/client/rp/jwks.go +++ b/pkg/client/rp/jwks.go @@ -9,8 +9,8 @@ import ( "gopkg.in/square/go-jose.v2" - httphelper "github.com/zitadel/oidc/pkg/http" - "github.com/zitadel/oidc/pkg/oidc" + httphelper "github.com/zitadel/oidc/v2/pkg/http" + "github.com/zitadel/oidc/v2/pkg/oidc" ) func NewRemoteKeySet(client *http.Client, jwksURL string, opts ...func(*remoteKeySet)) oidc.KeySet { diff --git a/pkg/client/rp/mock/verifier.mock.go b/pkg/client/rp/mock/verifier.mock.go index b20db68..9d1daa1 100644 --- a/pkg/client/rp/mock/verifier.mock.go +++ b/pkg/client/rp/mock/verifier.mock.go @@ -10,7 +10,7 @@ import ( "github.com/golang/mock/gomock" - "github.com/zitadel/oidc/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/oidc" ) // MockVerifier is a mock of Verifier interface diff --git a/pkg/client/rp/relying_party.go b/pkg/client/rp/relying_party.go index 86b65da..d2e3cf7 100644 --- a/pkg/client/rp/relying_party.go +++ b/pkg/client/rp/relying_party.go @@ -14,9 +14,9 @@ import ( "golang.org/x/oauth2" "gopkg.in/square/go-jose.v2" - "github.com/zitadel/oidc/pkg/client" - httphelper "github.com/zitadel/oidc/pkg/http" - "github.com/zitadel/oidc/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/client" + httphelper "github.com/zitadel/oidc/v2/pkg/http" + "github.com/zitadel/oidc/v2/pkg/oidc" ) const ( @@ -255,7 +255,7 @@ func WithVerifierOpts(opts ...VerifierOption) Option { // WithClientKey specifies the path to the key.json to be used for the JWT Profile Client Authentication on the token endpoint // -//deprecated: use WithJWTProfile(SignerFromKeyPath(path)) instead +// deprecated: use WithJWTProfile(SignerFromKeyPath(path)) instead func WithClientKey(path string) Option { return WithJWTProfile(SignerFromKeyPath(path)) } @@ -304,7 +304,7 @@ func SignerFromKeyAndKeyID(key []byte, keyID string) SignerFromKey { // Discover calls the discovery endpoint of the provided issuer and returns the found endpoints // -//deprecated: use client.Discover +// deprecated: use client.Discover func Discover(issuer string, httpClient *http.Client) (Endpoints, error) { wellKnown := strings.TrimSuffix(issuer, "/") + oidc.DiscoveryEndpoint req, err := http.NewRequest("GET", wellKnown, nil) @@ -323,7 +323,7 @@ func Discover(issuer string, httpClient *http.Client) (Endpoints, error) { } // AuthURL returns the auth request url -//(wrapping the oauth2 `AuthCodeURL`) +// (wrapping the oauth2 `AuthCodeURL`) func AuthURL(state string, rp RelyingParty, opts ...AuthURLOpt) string { authOpts := make([]oauth2.AuthCodeOption, 0) for _, opt := range opts { diff --git a/pkg/client/rp/tockenexchange.go b/pkg/client/rp/tockenexchange.go index 3950fe1..c1ac88d 100644 --- a/pkg/client/rp/tockenexchange.go +++ b/pkg/client/rp/tockenexchange.go @@ -5,7 +5,7 @@ import ( "golang.org/x/oauth2" - "github.com/zitadel/oidc/pkg/oidc/grants/tokenexchange" + "github.com/zitadel/oidc/v2/pkg/oidc/grants/tokenexchange" ) // TokenExchangeRP extends the `RelyingParty` interface for the *draft* oauth2 `Token Exchange` diff --git a/pkg/client/rp/verifier.go b/pkg/client/rp/verifier.go index 6b3b3fd..f3db128 100644 --- a/pkg/client/rp/verifier.go +++ b/pkg/client/rp/verifier.go @@ -6,7 +6,7 @@ import ( "gopkg.in/square/go-jose.v2" - "github.com/zitadel/oidc/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/oidc" ) type IDTokenVerifier interface { @@ -20,7 +20,7 @@ type IDTokenVerifier interface { } // VerifyTokens implement the Token Response Validation as defined in OIDC specification -//https://openid.net/specs/openid-connect-core-1_0.html#TokenResponseValidation +// https://openid.net/specs/openid-connect-core-1_0.html#TokenResponseValidation func VerifyTokens(ctx context.Context, accessToken, idTokenString string, v IDTokenVerifier) (oidc.IDTokenClaims, error) { idToken, err := VerifyIDToken(ctx, idTokenString, v) if err != nil { @@ -33,7 +33,7 @@ func VerifyTokens(ctx context.Context, accessToken, idTokenString string, v IDTo } // VerifyIDToken validates the id token according to -//https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation +// https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation func VerifyIDToken(ctx context.Context, token string, v IDTokenVerifier) (oidc.IDTokenClaims, error) { claims := oidc.EmptyIDTokenClaims() @@ -89,7 +89,7 @@ func VerifyIDToken(ctx context.Context, token string, v IDTokenVerifier) (oidc.I } // VerifyAccessToken validates the access token according to -//https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowTokenValidation +// https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowTokenValidation func VerifyAccessToken(accessToken, atHash string, sigAlgorithm jose.SignatureAlgorithm) error { if atHash == "" { return nil diff --git a/pkg/client/rs/resource_server.go b/pkg/client/rs/resource_server.go index b1bc47e..1d9860f 100644 --- a/pkg/client/rs/resource_server.go +++ b/pkg/client/rs/resource_server.go @@ -6,9 +6,9 @@ import ( "net/http" "time" - "github.com/zitadel/oidc/pkg/client" - httphelper "github.com/zitadel/oidc/pkg/http" - "github.com/zitadel/oidc/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/client" + httphelper "github.com/zitadel/oidc/v2/pkg/http" + "github.com/zitadel/oidc/v2/pkg/oidc" ) type ResourceServer interface { diff --git a/pkg/oidc/code_challenge.go b/pkg/oidc/code_challenge.go index e1e459c..37c1783 100644 --- a/pkg/oidc/code_challenge.go +++ b/pkg/oidc/code_challenge.go @@ -3,7 +3,7 @@ package oidc import ( "crypto/sha256" - "github.com/zitadel/oidc/pkg/crypto" + "github.com/zitadel/oidc/v2/pkg/crypto" ) const ( diff --git a/pkg/oidc/token.go b/pkg/oidc/token.go index 784684d..198049d 100644 --- a/pkg/oidc/token.go +++ b/pkg/oidc/token.go @@ -9,8 +9,8 @@ import ( "golang.org/x/oauth2" "gopkg.in/square/go-jose.v2" - "github.com/zitadel/oidc/pkg/crypto" - "github.com/zitadel/oidc/pkg/http" + "github.com/zitadel/oidc/v2/pkg/crypto" + "github.com/zitadel/oidc/v2/pkg/http" ) const ( diff --git a/pkg/oidc/token_request.go b/pkg/oidc/token_request.go index ec11057..2b56535 100644 --- a/pkg/oidc/token_request.go +++ b/pkg/oidc/token_request.go @@ -214,8 +214,10 @@ type TokenExchangeRequest struct { } type ClientCredentialsRequest struct { - GrantType GrantType `schema:"grant_type"` - Scope SpaceDelimitedArray `schema:"scope"` - ClientID string `schema:"client_id"` - ClientSecret string `schema:"client_secret"` + GrantType GrantType `schema:"grant_type"` + Scope SpaceDelimitedArray `schema:"scope"` + ClientID string `schema:"client_id"` + ClientSecret string `schema:"client_secret"` + ClientAssertion string `schema:"client_assertion"` + ClientAssertionType string `schema:"client_assertion_type"` } diff --git a/pkg/oidc/verifier.go b/pkg/oidc/verifier.go index cc18c80..1757651 100644 --- a/pkg/oidc/verifier.go +++ b/pkg/oidc/verifier.go @@ -12,7 +12,7 @@ import ( "gopkg.in/square/go-jose.v2" - str "github.com/zitadel/oidc/pkg/strings" + str "github.com/zitadel/oidc/v2/pkg/strings" ) type Claims interface { diff --git a/pkg/op/auth_request.go b/pkg/op/auth_request.go index d8c960e..b13f642 100644 --- a/pkg/op/auth_request.go +++ b/pkg/op/auth_request.go @@ -11,9 +11,9 @@ import ( "github.com/gorilla/mux" - httphelper "github.com/zitadel/oidc/pkg/http" - "github.com/zitadel/oidc/pkg/oidc" - str "github.com/zitadel/oidc/pkg/strings" + httphelper "github.com/zitadel/oidc/v2/pkg/http" + "github.com/zitadel/oidc/v2/pkg/oidc" + str "github.com/zitadel/oidc/v2/pkg/strings" ) type AuthRequest interface { @@ -38,10 +38,8 @@ type Authorizer interface { Storage() Storage Decoder() httphelper.Decoder Encoder() httphelper.Encoder - Signer() Signer - IDTokenHintVerifier() IDTokenHintVerifier + IDTokenHintVerifier(context.Context) IDTokenHintVerifier Crypto() Crypto - Issuer() string RequestObjectSupported() bool } @@ -72,8 +70,9 @@ func Authorize(w http.ResponseWriter, r *http.Request, authorizer Authorizer) { AuthRequestError(w, r, authReq, err, authorizer.Encoder()) return } + ctx := r.Context() if authReq.RequestParam != "" && authorizer.RequestObjectSupported() { - authReq, err = ParseRequestObject(r.Context(), authReq, authorizer.Storage(), authorizer.Issuer()) + authReq, err = ParseRequestObject(ctx, authReq, authorizer.Storage(), IssuerFromContext(ctx)) if err != nil { AuthRequestError(w, r, authReq, err, authorizer.Encoder()) return @@ -91,7 +90,7 @@ func Authorize(w http.ResponseWriter, r *http.Request, authorizer Authorizer) { if validater, ok := authorizer.(AuthorizeValidator); ok { validation = validater.ValidateAuthRequest } - userID, err := validation(r.Context(), authReq, authorizer.Storage(), authorizer.IDTokenHintVerifier()) + userID, err := validation(ctx, authReq, authorizer.Storage(), authorizer.IDTokenHintVerifier(ctx)) if err != nil { AuthRequestError(w, r, authReq, err, authorizer.Encoder()) return @@ -100,12 +99,12 @@ func Authorize(w http.ResponseWriter, r *http.Request, authorizer Authorizer) { AuthRequestError(w, r, authReq, oidc.ErrRequestNotSupported(), authorizer.Encoder()) return } - req, err := authorizer.Storage().CreateAuthRequest(r.Context(), authReq, userID) + req, err := authorizer.Storage().CreateAuthRequest(ctx, authReq, userID) if err != nil { AuthRequestError(w, r, authReq, oidc.DefaultToServerError(err, "unable to save auth request"), authorizer.Encoder()) return } - client, err := authorizer.Storage().GetClientByClientID(r.Context(), req.GetClientID()) + client, err := authorizer.Storage().GetClientByClientID(ctx, req.GetClientID()) if err != nil { AuthRequestError(w, r, req, oidc.DefaultToServerError(err, "unable to retrieve client by id"), authorizer.Encoder()) return diff --git a/pkg/op/auth_request_test.go b/pkg/op/auth_request_test.go index dc6f655..7a9701b 100644 --- a/pkg/op/auth_request_test.go +++ b/pkg/op/auth_request_test.go @@ -13,10 +13,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - httphelper "github.com/zitadel/oidc/pkg/http" - "github.com/zitadel/oidc/pkg/oidc" - "github.com/zitadel/oidc/pkg/op" - "github.com/zitadel/oidc/pkg/op/mock" + httphelper "github.com/zitadel/oidc/v2/pkg/http" + "github.com/zitadel/oidc/v2/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/op" + "github.com/zitadel/oidc/v2/pkg/op/mock" ) // diff --git a/pkg/op/client.go b/pkg/op/client.go index d9f7ab0..e8a3347 100644 --- a/pkg/op/client.go +++ b/pkg/op/client.go @@ -3,7 +3,7 @@ package op import ( "time" - "github.com/zitadel/oidc/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/oidc" ) //go:generate go get github.com/dmarkham/enumer diff --git a/pkg/op/config.go b/pkg/op/config.go index 82cbb47..c40fa2d 100644 --- a/pkg/op/config.go +++ b/pkg/op/config.go @@ -2,20 +2,24 @@ package op import ( "errors" + "net/http" "net/url" - "os" + "strings" "golang.org/x/text/language" ) -const ( - OidcDevMode = "ZITADEL_OIDC_DEV" - // deprecated: use OidcDevMode (ZITADEL_OIDC_DEV=true) - devMode = "CAOS_OIDC_DEV" +var ( + ErrInvalidIssuerPath = errors.New("no fragments or query allowed for issuer") + ErrInvalidIssuerNoIssuer = errors.New("missing issuer") + ErrInvalidIssuerURL = errors.New("invalid url for issuer") + ErrInvalidIssuerMissingHost = errors.New("host for issuer missing") + ErrInvalidIssuerHTTPS = errors.New("scheme for issuer must be `https`") ) type Configuration interface { - Issuer() string + IssuerFromRequest(r *http.Request) string + Insecure() bool AuthorizationEndpoint() Endpoint TokenEndpoint() Endpoint IntrospectionEndpoint() Endpoint @@ -42,36 +46,74 @@ type Configuration interface { SupportedUILocales() []language.Tag } -func ValidateIssuer(issuer string) error { +type IssuerFromRequest func(r *http.Request) string + +func IssuerFromHost(path string) func(bool) (IssuerFromRequest, error) { + return func(allowInsecure bool) (IssuerFromRequest, error) { + issuerPath, err := url.Parse(path) + if err != nil { + return nil, ErrInvalidIssuerURL + } + if err := ValidateIssuerPath(issuerPath); err != nil { + return nil, err + } + return func(r *http.Request) string { + return dynamicIssuer(r.Host, path, allowInsecure) + }, nil + } +} + +func StaticIssuer(issuer string) func(bool) (IssuerFromRequest, error) { + return func(allowInsecure bool) (IssuerFromRequest, error) { + if err := ValidateIssuer(issuer, allowInsecure); err != nil { + return nil, err + } + return func(_ *http.Request) string { + return issuer + }, nil + } +} + +func ValidateIssuer(issuer string, allowInsecure bool) error { if issuer == "" { - return errors.New("missing issuer") + return ErrInvalidIssuerNoIssuer } u, err := url.Parse(issuer) if err != nil { - return errors.New("invalid url for issuer") + return ErrInvalidIssuerURL } if u.Host == "" { - return errors.New("host for issuer missing") + return ErrInvalidIssuerMissingHost } if u.Scheme != "https" { - if !devLocalAllowed(u) { - return errors.New("scheme for issuer must be `https`") + if !devLocalAllowed(u, allowInsecure) { + return ErrInvalidIssuerHTTPS } } - if u.Fragment != "" || len(u.Query()) > 0 { - return errors.New("no fragments or query allowed for issuer") + return ValidateIssuerPath(u) +} + +func ValidateIssuerPath(issuer *url.URL) error { + if issuer.Fragment != "" || len(issuer.Query()) > 0 { + return ErrInvalidIssuerPath } return nil } -func devLocalAllowed(url *url.URL) bool { - _, b := os.LookupEnv(OidcDevMode) - if !b { - // check the old / current env var as well - _, b = os.LookupEnv(devMode) - if !b { - return b - } +func devLocalAllowed(url *url.URL, allowInsecure bool) bool { + if !allowInsecure { + return false } return url.Scheme == "http" } + +func dynamicIssuer(issuer, path string, allowInsecure bool) string { + schema := "https" + if allowInsecure { + schema = "http" + } + if len(path) > 0 && !strings.HasPrefix(path, "/") { + path = "/" + path + } + return schema + "://" + issuer + path +} diff --git a/pkg/op/config_test.go b/pkg/op/config_test.go index 9ff75f1..cfe4e61 100644 --- a/pkg/op/config_test.go +++ b/pkg/op/config_test.go @@ -1,13 +1,17 @@ package op import ( - "os" + "net/http/httptest" + "net/url" "testing" + + "github.com/stretchr/testify/assert" ) func TestValidateIssuer(t *testing.T) { type args struct { - issuer string + issuer string + allowInsecure bool } tests := []struct { name string @@ -16,65 +20,97 @@ func TestValidateIssuer(t *testing.T) { }{ { "missing issuer fails", - args{""}, + args{ + issuer: "", + }, true, }, { "invalid url for issuer fails", - args{":issuer"}, - true, - }, - { - "invalid url for issuer fails", - args{":issuer"}, + args{ + issuer: ":issuer", + }, true, }, { "host for issuer missing fails", - args{"https:///issuer"}, - true, - }, - { - "host for not https fails", - args{"http://issuer.com"}, + args{ + issuer: "https:///issuer", + }, true, }, { "host with fragment fails", - args{"https://issuer.com/#issuer"}, + args{ + issuer: "https://issuer.com/#issuer", + }, true, }, { "host with query fails", - args{"https://issuer.com?issuer=me"}, + args{ + issuer: "https://issuer.com?issuer=me", + }, + true, + }, + { + "host with http fails", + args{ + issuer: "http://issuer.com", + }, true, }, { "host with https ok", - args{"https://issuer.com"}, + args{ + issuer: "https://issuer.com", + }, false, }, { - "localhost with http fails", - args{"http://localhost:9999"}, + "custom scheme fails", + args{ + issuer: "custom://localhost:9999", + }, + true, + }, + { + "http with allowInsecure ok", + args{ + issuer: "http://localhost:9999", + allowInsecure: true, + }, + false, + }, + { + "https with allowInsecure ok", + args{ + issuer: "https://localhost:9999", + allowInsecure: true, + }, + false, + }, + { + "custom scheme with allowInsecure fails", + args{ + issuer: "custom://localhost:9999", + allowInsecure: true, + }, true, }, } - // ensure env is not set - //nolint:errcheck - os.Unsetenv(OidcDevMode) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := ValidateIssuer(tt.args.issuer); (err != nil) != tt.wantErr { + if err := ValidateIssuer(tt.args.issuer, tt.args.allowInsecure); (err != nil) != tt.wantErr { t.Errorf("ValidateIssuer() error = %v, wantErr %v", err, tt.wantErr) } }) } } -func TestValidateIssuerDevLocalAllowed(t *testing.T) { +func TestValidateIssuerPath(t *testing.T) { type args struct { - issuer string + issuerPath *url.URL } tests := []struct { name string @@ -82,17 +118,217 @@ func TestValidateIssuerDevLocalAllowed(t *testing.T) { wantErr bool }{ { - "localhost with http with dev ok", - args{"http://localhost:9999"}, + "empty ok", + args{func() *url.URL { + u, _ := url.Parse("") + return u + }()}, false, }, + { + "custom ok", + args{func() *url.URL { + u, _ := url.Parse("/custom") + return u + }()}, + false, + }, + { + "fragment fails", + args{func() *url.URL { + u, _ := url.Parse("#fragment") + return u + }()}, + true, + }, + { + "query fails", + args{func() *url.URL { + u, _ := url.Parse("?query=value") + return u + }()}, + true, + }, } - //nolint:errcheck - os.Setenv(OidcDevMode, "true") for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := ValidateIssuer(tt.args.issuer); (err != nil) != tt.wantErr { - t.Errorf("ValidateIssuer() error = %v, wantErr %v", err, tt.wantErr) + if err := ValidateIssuerPath(tt.args.issuerPath); (err != nil) != tt.wantErr { + t.Errorf("ValidateIssuerPath() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestIssuerFromHost(t *testing.T) { + type args struct { + path string + allowInsecure bool + target string + } + type res struct { + issuer string + err error + } + tests := []struct { + name string + args args + res res + }{ + { + "invalid issuer path", + args{ + path: "/#fragment", + allowInsecure: false, + }, + res{ + issuer: "", + err: ErrInvalidIssuerPath, + }, + }, + { + "empty path secure", + args{ + path: "", + allowInsecure: false, + target: "https://issuer.com", + }, + res{ + issuer: "https://issuer.com", + err: nil, + }, + }, + { + "custom path secure", + args{ + path: "/custom/", + allowInsecure: false, + target: "https://issuer.com", + }, + res{ + issuer: "https://issuer.com/custom/", + err: nil, + }, + }, + { + "custom path no leading slash", + args{ + path: "custom/", + allowInsecure: false, + target: "https://issuer.com", + }, + res{ + issuer: "https://issuer.com/custom/", + err: nil, + }, + }, + { + "empty path unsecure", + args{ + path: "", + allowInsecure: true, + target: "http://issuer.com", + }, + res{ + issuer: "http://issuer.com", + err: nil, + }, + }, + { + "custom path unsecure", + args{ + path: "/custom/", + allowInsecure: true, + target: "http://issuer.com", + }, + res{ + issuer: "http://issuer.com/custom/", + err: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + issuer, err := IssuerFromHost(tt.args.path)(tt.args.allowInsecure) + if tt.res.err == nil { + assert.NoError(t, err) + req := httptest.NewRequest("", tt.args.target, nil) + assert.Equal(t, tt.res.issuer, issuer(req)) + } + if tt.res.err != nil { + assert.ErrorIs(t, err, tt.res.err) + } + }) + } +} + +func TestStaticIssuer(t *testing.T) { + type args struct { + issuer string + allowInsecure bool + } + type res struct { + issuer string + err error + } + tests := []struct { + name string + args args + res res + }{ + { + "invalid issuer", + args{ + issuer: "", + allowInsecure: false, + }, + res{ + issuer: "", + err: ErrInvalidIssuerNoIssuer, + }, + }, + { + "empty path secure", + args{ + issuer: "https://issuer.com", + allowInsecure: false, + }, + res{ + issuer: "https://issuer.com", + err: nil, + }, + }, + { + "custom path secure", + args{ + issuer: "https://issuer.com/custom/", + allowInsecure: false, + }, + res{ + issuer: "https://issuer.com/custom/", + err: nil, + }, + }, + { + "unsecure", + args{ + issuer: "http://issuer.com", + allowInsecure: true, + }, + res{ + issuer: "http://issuer.com", + err: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + issuer, err := StaticIssuer(tt.args.issuer)(tt.args.allowInsecure) + if tt.res.err == nil { + assert.NoError(t, err) + assert.Equal(t, tt.res.issuer, issuer(nil)) + } + if tt.res.err != nil { + assert.ErrorIs(t, err, tt.res.err) } }) } diff --git a/pkg/op/context.go b/pkg/op/context.go new file mode 100644 index 0000000..4406273 --- /dev/null +++ b/pkg/op/context.go @@ -0,0 +1,49 @@ +package op + +import ( + "context" + "net/http" +) + +type key int + +var ( + issuer key = 0 +) + +type IssuerInterceptor struct { + issuerFromRequest IssuerFromRequest +} + +//NewIssuerInterceptor will set the issuer into the context +//by the provided IssuerFromRequest (e.g. returned from StaticIssuer or IssuerFromHost) +func NewIssuerInterceptor(issuerFromRequest IssuerFromRequest) *IssuerInterceptor { + return &IssuerInterceptor{ + issuerFromRequest: issuerFromRequest, + } +} + +func (i *IssuerInterceptor) Handler(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + i.setIssuerCtx(w, r, next) + }) +} + +func (i *IssuerInterceptor) HandlerFunc(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + i.setIssuerCtx(w, r, next) + } +} + +//IssuerFromContext reads the issuer from the context (set by an IssuerInterceptor) +//it will return an empty string if not found +func IssuerFromContext(ctx context.Context) string { + ctxIssuer, _ := ctx.Value(issuer).(string) + return ctxIssuer +} + +func (i *IssuerInterceptor) setIssuerCtx(w http.ResponseWriter, r *http.Request, next http.Handler) { + ctx := context.WithValue(r.Context(), issuer, i.issuerFromRequest(r)) + r = r.WithContext(ctx) + next.ServeHTTP(w, r) +} diff --git a/pkg/op/context_test.go b/pkg/op/context_test.go new file mode 100644 index 0000000..e6bfcec --- /dev/null +++ b/pkg/op/context_test.go @@ -0,0 +1,76 @@ +package op + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIssuerInterceptor(t *testing.T) { + type fields struct { + issuerFromRequest IssuerFromRequest + } + type args struct { + r *http.Request + next http.Handler + } + type res struct { + issuer string + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + "empty", + fields{ + func(r *http.Request) string { + return "" + }, + }, + args{}, + res{ + issuer: "", + }, + }, + { + "static", + fields{ + func(r *http.Request) string { + return "static" + }, + }, + args{}, + res{ + issuer: "static", + }, + }, + { + "host", + fields{ + func(r *http.Request) string { + return r.Host + }, + }, + args{}, + res{ + issuer: "issuer.com", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + i := NewIssuerInterceptor(tt.fields.issuerFromRequest) + next := http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { + assert.Equal(t, tt.res.issuer, IssuerFromContext(r.Context())) + }) + req := httptest.NewRequest("", "https://issuer.com", nil) + i.Handler(next).ServeHTTP(nil, req) + i.HandlerFunc(next).ServeHTTP(nil, req) + }) + } +} diff --git a/pkg/op/crypto.go b/pkg/op/crypto.go index f14b1de..6786022 100644 --- a/pkg/op/crypto.go +++ b/pkg/op/crypto.go @@ -1,7 +1,7 @@ package op import ( - "github.com/zitadel/oidc/pkg/crypto" + "github.com/zitadel/oidc/v2/pkg/crypto" ) type Crypto interface { diff --git a/pkg/op/discovery.go b/pkg/op/discovery.go index 100bfc8..9a25afc 100644 --- a/pkg/op/discovery.go +++ b/pkg/op/discovery.go @@ -1,49 +1,17 @@ package op import ( + "context" "net/http" - httphelper "github.com/zitadel/oidc/pkg/http" - "github.com/zitadel/oidc/pkg/oidc" + "gopkg.in/square/go-jose.v2" + + httphelper "github.com/zitadel/oidc/v2/pkg/http" + "github.com/zitadel/oidc/v2/pkg/oidc" ) -func discoveryHandler(c Configuration, s Signer) func(http.ResponseWriter, *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - Discover(w, CreateDiscoveryConfig(c, s)) - } -} - -func Discover(w http.ResponseWriter, config *oidc.DiscoveryConfiguration) { - httphelper.MarshalJSON(w, config) -} - -func CreateDiscoveryConfig(c Configuration, s Signer) *oidc.DiscoveryConfiguration { - return &oidc.DiscoveryConfiguration{ - Issuer: c.Issuer(), - AuthorizationEndpoint: c.AuthorizationEndpoint().Absolute(c.Issuer()), - TokenEndpoint: c.TokenEndpoint().Absolute(c.Issuer()), - IntrospectionEndpoint: c.IntrospectionEndpoint().Absolute(c.Issuer()), - UserinfoEndpoint: c.UserinfoEndpoint().Absolute(c.Issuer()), - RevocationEndpoint: c.RevocationEndpoint().Absolute(c.Issuer()), - EndSessionEndpoint: c.EndSessionEndpoint().Absolute(c.Issuer()), - JwksURI: c.KeysEndpoint().Absolute(c.Issuer()), - ScopesSupported: Scopes(c), - ResponseTypesSupported: ResponseTypes(c), - GrantTypesSupported: GrantTypes(c), - SubjectTypesSupported: SubjectTypes(c), - IDTokenSigningAlgValuesSupported: SigAlgorithms(s), - RequestObjectSigningAlgValuesSupported: RequestObjectSigAlgorithms(c), - TokenEndpointAuthMethodsSupported: AuthMethodsTokenEndpoint(c), - TokenEndpointAuthSigningAlgValuesSupported: TokenSigAlgorithms(c), - IntrospectionEndpointAuthSigningAlgValuesSupported: IntrospectionSigAlgorithms(c), - IntrospectionEndpointAuthMethodsSupported: AuthMethodsIntrospectionEndpoint(c), - RevocationEndpointAuthSigningAlgValuesSupported: RevocationSigAlgorithms(c), - RevocationEndpointAuthMethodsSupported: AuthMethodsRevocationEndpoint(c), - ClaimsSupported: SupportedClaims(c), - CodeChallengeMethodsSupported: CodeChallengeMethods(c), - UILocalesSupported: c.SupportedUILocales(), - RequestParameterSupported: c.RequestObjectSupported(), - } +type DiscoverStorage interface { + SignatureAlgorithms(context.Context) ([]jose.SignatureAlgorithm, error) } var DefaultSupportedScopes = []string{ @@ -55,6 +23,46 @@ var DefaultSupportedScopes = []string{ oidc.ScopeOfflineAccess, } +func discoveryHandler(c Configuration, s DiscoverStorage) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + Discover(w, CreateDiscoveryConfig(r, c, s)) + } +} + +func Discover(w http.ResponseWriter, config *oidc.DiscoveryConfiguration) { + httphelper.MarshalJSON(w, config) +} + +func CreateDiscoveryConfig(r *http.Request, config Configuration, storage DiscoverStorage) *oidc.DiscoveryConfiguration { + issuer := config.IssuerFromRequest(r) + return &oidc.DiscoveryConfiguration{ + Issuer: issuer, + AuthorizationEndpoint: config.AuthorizationEndpoint().Absolute(issuer), + TokenEndpoint: config.TokenEndpoint().Absolute(issuer), + IntrospectionEndpoint: config.IntrospectionEndpoint().Absolute(issuer), + UserinfoEndpoint: config.UserinfoEndpoint().Absolute(issuer), + RevocationEndpoint: config.RevocationEndpoint().Absolute(issuer), + EndSessionEndpoint: config.EndSessionEndpoint().Absolute(issuer), + JwksURI: config.KeysEndpoint().Absolute(issuer), + ScopesSupported: Scopes(config), + ResponseTypesSupported: ResponseTypes(config), + GrantTypesSupported: GrantTypes(config), + SubjectTypesSupported: SubjectTypes(config), + IDTokenSigningAlgValuesSupported: SigAlgorithms(r.Context(), storage), + RequestObjectSigningAlgValuesSupported: RequestObjectSigAlgorithms(config), + TokenEndpointAuthMethodsSupported: AuthMethodsTokenEndpoint(config), + TokenEndpointAuthSigningAlgValuesSupported: TokenSigAlgorithms(config), + IntrospectionEndpointAuthSigningAlgValuesSupported: IntrospectionSigAlgorithms(config), + IntrospectionEndpointAuthMethodsSupported: AuthMethodsIntrospectionEndpoint(config), + RevocationEndpointAuthSigningAlgValuesSupported: RevocationSigAlgorithms(config), + RevocationEndpointAuthMethodsSupported: AuthMethodsRevocationEndpoint(config), + ClaimsSupported: SupportedClaims(config), + CodeChallengeMethodsSupported: CodeChallengeMethods(config), + UILocalesSupported: config.SupportedUILocales(), + RequestParameterSupported: config.RequestObjectSupported(), + } +} + func Scopes(c Configuration) []string { return DefaultSupportedScopes // TODO: config } @@ -87,6 +95,88 @@ func GrantTypes(c Configuration) []oidc.GrantType { return grantTypes } +func SubjectTypes(c Configuration) []string { + return []string{"public"} //TODO: config +} + +func SigAlgorithms(ctx context.Context, storage DiscoverStorage) []string { + algorithms, err := storage.SignatureAlgorithms(ctx) + if err != nil { + return nil + } + algs := make([]string, len(algorithms)) + for i, algorithm := range algorithms { + algs[i] = string(algorithm) + } + return algs +} + +func RequestObjectSigAlgorithms(c Configuration) []string { + if !c.RequestObjectSupported() { + return nil + } + return c.RequestObjectSigningAlgorithmsSupported() +} + +func AuthMethodsTokenEndpoint(c Configuration) []oidc.AuthMethod { + authMethods := []oidc.AuthMethod{ + oidc.AuthMethodNone, + oidc.AuthMethodBasic, + } + if c.AuthMethodPostSupported() { + authMethods = append(authMethods, oidc.AuthMethodPost) + } + if c.AuthMethodPrivateKeyJWTSupported() { + authMethods = append(authMethods, oidc.AuthMethodPrivateKeyJWT) + } + return authMethods +} + +func TokenSigAlgorithms(c Configuration) []string { + if !c.AuthMethodPrivateKeyJWTSupported() { + return nil + } + return c.TokenEndpointSigningAlgorithmsSupported() +} + +func IntrospectionSigAlgorithms(c Configuration) []string { + if !c.IntrospectionAuthMethodPrivateKeyJWTSupported() { + return nil + } + return c.IntrospectionEndpointSigningAlgorithmsSupported() +} + +func AuthMethodsIntrospectionEndpoint(c Configuration) []oidc.AuthMethod { + authMethods := []oidc.AuthMethod{ + oidc.AuthMethodBasic, + } + if c.AuthMethodPrivateKeyJWTSupported() { + authMethods = append(authMethods, oidc.AuthMethodPrivateKeyJWT) + } + return authMethods +} + +func RevocationSigAlgorithms(c Configuration) []string { + if !c.RevocationAuthMethodPrivateKeyJWTSupported() { + return nil + } + return c.RevocationEndpointSigningAlgorithmsSupported() +} + +func AuthMethodsRevocationEndpoint(c Configuration) []oidc.AuthMethod { + authMethods := []oidc.AuthMethod{ + oidc.AuthMethodNone, + oidc.AuthMethodBasic, + } + if c.AuthMethodPostSupported() { + authMethods = append(authMethods, oidc.AuthMethodPost) + } + if c.AuthMethodPrivateKeyJWTSupported() { + authMethods = append(authMethods, oidc.AuthMethodPrivateKeyJWT) + } + return authMethods +} + func SupportedClaims(c Configuration) []string { return []string{ // TODO: config "sub", @@ -116,59 +206,6 @@ func SupportedClaims(c Configuration) []string { } } -func SigAlgorithms(s Signer) []string { - return []string{string(s.SignatureAlgorithm())} -} - -func SubjectTypes(c Configuration) []string { - return []string{"public"} // TODO: config -} - -func AuthMethodsTokenEndpoint(c Configuration) []oidc.AuthMethod { - authMethods := []oidc.AuthMethod{ - oidc.AuthMethodNone, - oidc.AuthMethodBasic, - } - if c.AuthMethodPostSupported() { - authMethods = append(authMethods, oidc.AuthMethodPost) - } - if c.AuthMethodPrivateKeyJWTSupported() { - authMethods = append(authMethods, oidc.AuthMethodPrivateKeyJWT) - } - return authMethods -} - -func TokenSigAlgorithms(c Configuration) []string { - if !c.AuthMethodPrivateKeyJWTSupported() { - return nil - } - return c.TokenEndpointSigningAlgorithmsSupported() -} - -func AuthMethodsIntrospectionEndpoint(c Configuration) []oidc.AuthMethod { - authMethods := []oidc.AuthMethod{ - oidc.AuthMethodBasic, - } - if c.AuthMethodPrivateKeyJWTSupported() { - authMethods = append(authMethods, oidc.AuthMethodPrivateKeyJWT) - } - return authMethods -} - -func AuthMethodsRevocationEndpoint(c Configuration) []oidc.AuthMethod { - authMethods := []oidc.AuthMethod{ - oidc.AuthMethodNone, - oidc.AuthMethodBasic, - } - if c.AuthMethodPostSupported() { - authMethods = append(authMethods, oidc.AuthMethodPost) - } - if c.AuthMethodPrivateKeyJWTSupported() { - authMethods = append(authMethods, oidc.AuthMethodPrivateKeyJWT) - } - return authMethods -} - func CodeChallengeMethods(c Configuration) []oidc.CodeChallengeMethod { codeMethods := make([]oidc.CodeChallengeMethod, 0, 1) if c.CodeMethodS256Supported() { @@ -176,24 +213,3 @@ func CodeChallengeMethods(c Configuration) []oidc.CodeChallengeMethod { } return codeMethods } - -func IntrospectionSigAlgorithms(c Configuration) []string { - if !c.IntrospectionAuthMethodPrivateKeyJWTSupported() { - return nil - } - return c.IntrospectionEndpointSigningAlgorithmsSupported() -} - -func RevocationSigAlgorithms(c Configuration) []string { - if !c.RevocationAuthMethodPrivateKeyJWTSupported() { - return nil - } - return c.RevocationEndpointSigningAlgorithmsSupported() -} - -func RequestObjectSigAlgorithms(c Configuration) []string { - if !c.RequestObjectSupported() { - return nil - } - return c.RequestObjectSigningAlgorithmsSupported() -} diff --git a/pkg/op/discovery_test.go b/pkg/op/discovery_test.go index 1d74f75..e1b07dd 100644 --- a/pkg/op/discovery_test.go +++ b/pkg/op/discovery_test.go @@ -1,18 +1,19 @@ package op_test import ( + "context" "net/http" "net/http/httptest" - "reflect" "testing" "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gopkg.in/square/go-jose.v2" - "github.com/zitadel/oidc/pkg/oidc" - "github.com/zitadel/oidc/pkg/op" - "github.com/zitadel/oidc/pkg/op/mock" + "github.com/zitadel/oidc/v2/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/op" + "github.com/zitadel/oidc/v2/pkg/op/mock" ) func TestDiscover(t *testing.T) { @@ -47,8 +48,9 @@ func TestDiscover(t *testing.T) { func TestCreateDiscoveryConfig(t *testing.T) { type args struct { - c op.Configuration - s op.Signer + request *http.Request + c op.Configuration + s op.DiscoverStorage } tests := []struct { name string @@ -59,9 +61,8 @@ func TestCreateDiscoveryConfig(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := op.CreateDiscoveryConfig(tt.args.c, tt.args.s); !reflect.DeepEqual(got, tt.want) { - t.Errorf("CreateDiscoveryConfig() = %v, want %v", got, tt.want) - } + got := op.CreateDiscoveryConfig(tt.args.request, tt.args.c, tt.args.s) + assert.Equal(t, tt.want, got) }) } } @@ -83,9 +84,8 @@ func Test_scopes(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := op.Scopes(tt.args.c); !reflect.DeepEqual(got, tt.want) { - t.Errorf("scopes() = %v, want %v", got, tt.want) - } + got := op.Scopes(tt.args.c) + assert.Equal(t, tt.want, got) }) } } @@ -99,13 +99,16 @@ func Test_ResponseTypes(t *testing.T) { args args want []string }{ - // TODO: Add test cases. + { + "code and implicit flow", + args{}, + []string{"code", "id_token", "id_token token"}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := op.ResponseTypes(tt.args.c); !reflect.DeepEqual(got, tt.want) { - t.Errorf("responseTypes() = %v, want %v", got, tt.want) - } + got := op.ResponseTypes(tt.args.c) + assert.Equal(t, tt.want, got) }) } } @@ -117,63 +120,51 @@ func Test_GrantTypes(t *testing.T) { tests := []struct { name string args args - want []string - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := op.GrantTypes(tt.args.c); !reflect.DeepEqual(got, tt.want) { - t.Errorf("grantTypes() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSupportedClaims(t *testing.T) { - type args struct { - c op.Configuration - } - tests := []struct { - name string - args args - want []string - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := op.SupportedClaims(tt.args.c); !reflect.DeepEqual(got, tt.want) { - t.Errorf("SupportedClaims() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_SigAlgorithms(t *testing.T) { - m := mock.NewMockSigner(gomock.NewController(t)) - type args struct { - s op.Signer - } - tests := []struct { - name string - args args - want []string + want []oidc.GrantType }{ { - "", - args{func() op.Signer { - m.EXPECT().SignatureAlgorithm().Return(jose.RS256) - return m - }()}, - []string{"RS256"}, + "code and implicit flow", + args{ + func() op.Configuration { + c := mock.NewMockConfiguration(gomock.NewController(t)) + c.EXPECT().GrantTypeRefreshTokenSupported().Return(false) + c.EXPECT().GrantTypeTokenExchangeSupported().Return(false) + c.EXPECT().GrantTypeJWTAuthorizationSupported().Return(false) + c.EXPECT().GrantTypeClientCredentialsSupported().Return(false) + return c + }(), + }, + []oidc.GrantType{ + oidc.GrantTypeCode, + oidc.GrantTypeImplicit, + }, + }, + { + "code, implicit flow, refresh token, token exchange, jwt profile, client_credentials", + args{ + func() op.Configuration { + c := mock.NewMockConfiguration(gomock.NewController(t)) + c.EXPECT().GrantTypeRefreshTokenSupported().Return(true) + c.EXPECT().GrantTypeTokenExchangeSupported().Return(true) + c.EXPECT().GrantTypeJWTAuthorizationSupported().Return(true) + c.EXPECT().GrantTypeClientCredentialsSupported().Return(true) + return c + }(), + }, + []oidc.GrantType{ + oidc.GrantTypeCode, + oidc.GrantTypeImplicit, + oidc.GrantTypeRefreshToken, + oidc.GrantTypeClientCredentials, + oidc.GrantTypeTokenExchange, + oidc.GrantTypeBearer, + }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := op.SigAlgorithms(tt.args.s); !reflect.DeepEqual(got, tt.want) { - t.Errorf("sigAlgorithms() = %v, want %v", got, tt.want) - } + got := op.GrantTypes(tt.args.c) + assert.Equal(t, tt.want, got) }) } } @@ -195,9 +186,80 @@ func Test_SubjectTypes(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := op.SubjectTypes(tt.args.c); !reflect.DeepEqual(got, tt.want) { - t.Errorf("subjectTypes() = %v, want %v", got, tt.want) - } + got := op.SubjectTypes(tt.args.c) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_SigAlgorithms(t *testing.T) { + m := mock.NewMockDiscoverStorage(gomock.NewController(t)) + type args struct { + s op.DiscoverStorage + } + tests := []struct { + name string + args args + want []string + }{ + { + "", + args{func() op.DiscoverStorage { + m.EXPECT().SignatureAlgorithms(gomock.Any()).Return([]jose.SignatureAlgorithm{jose.RS256}, nil) + return m + }()}, + []string{"RS256"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := op.SigAlgorithms(context.Background(), tt.args.s) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_RequestObjectSigAlgorithms(t *testing.T) { + m := mock.NewMockConfiguration(gomock.NewController(t)) + type args struct { + c op.Configuration + } + tests := []struct { + name string + args args + want []string + }{ + { + "not supported, empty", + args{func() op.Configuration { + m.EXPECT().RequestObjectSupported().Return(false) + return m + }()}, + nil, + }, + { + "supported, empty", + args{func() op.Configuration { + m.EXPECT().RequestObjectSupported().Return(true) + m.EXPECT().RequestObjectSigningAlgorithmsSupported().Return(nil) + return m + }()}, + nil, + }, + { + "supported, list", + args{func() op.Configuration { + m.EXPECT().RequestObjectSupported().Return(true) + m.EXPECT().RequestObjectSigningAlgorithmsSupported().Return([]string{"RS256"}) + return m + }()}, + []string{"RS256"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := op.RequestObjectSigAlgorithms(tt.args.c) + assert.Equal(t, tt.want, got) }) } } @@ -244,9 +306,311 @@ func Test_AuthMethodsTokenEndpoint(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := op.AuthMethodsTokenEndpoint(tt.args.c); !reflect.DeepEqual(got, tt.want) { - t.Errorf("authMethods() = %v, want %v", got, tt.want) - } + got := op.AuthMethodsTokenEndpoint(tt.args.c) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_TokenSigAlgorithms(t *testing.T) { + m := mock.NewMockConfiguration(gomock.NewController(t)) + type args struct { + c op.Configuration + } + tests := []struct { + name string + args args + want []string + }{ + { + "not supported, empty", + args{func() op.Configuration { + m.EXPECT().AuthMethodPrivateKeyJWTSupported().Return(false) + return m + }()}, + nil, + }, + { + "supported, empty", + args{func() op.Configuration { + m.EXPECT().AuthMethodPrivateKeyJWTSupported().Return(true) + m.EXPECT().TokenEndpointSigningAlgorithmsSupported().Return(nil) + return m + }()}, + nil, + }, + { + "supported, list", + args{func() op.Configuration { + m.EXPECT().AuthMethodPrivateKeyJWTSupported().Return(true) + m.EXPECT().TokenEndpointSigningAlgorithmsSupported().Return([]string{"RS256"}) + return m + }()}, + []string{"RS256"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := op.TokenSigAlgorithms(tt.args.c) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_IntrospectionSigAlgorithms(t *testing.T) { + m := mock.NewMockConfiguration(gomock.NewController(t)) + type args struct { + c op.Configuration + } + tests := []struct { + name string + args args + want []string + }{ + { + "not supported, empty", + args{func() op.Configuration { + m.EXPECT().IntrospectionAuthMethodPrivateKeyJWTSupported().Return(false) + return m + }()}, + nil, + }, + { + "supported, empty", + args{func() op.Configuration { + m.EXPECT().IntrospectionAuthMethodPrivateKeyJWTSupported().Return(true) + m.EXPECT().IntrospectionEndpointSigningAlgorithmsSupported().Return(nil) + return m + }()}, + nil, + }, + { + "supported, list", + args{func() op.Configuration { + m.EXPECT().IntrospectionAuthMethodPrivateKeyJWTSupported().Return(true) + m.EXPECT().IntrospectionEndpointSigningAlgorithmsSupported().Return([]string{"RS256"}) + return m + }()}, + []string{"RS256"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := op.IntrospectionSigAlgorithms(tt.args.c) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_AuthMethodsIntrospectionEndpoint(t *testing.T) { + type args struct { + c op.Configuration + } + tests := []struct { + name string + args args + want []oidc.AuthMethod + }{ + { + "basic only", + args{func() op.Configuration { + m := mock.NewMockConfiguration(gomock.NewController(t)) + m.EXPECT().AuthMethodPrivateKeyJWTSupported().Return(false) + return m + }()}, + []oidc.AuthMethod{oidc.AuthMethodBasic}, + }, + { + "basic and private_key_jwt", + args{func() op.Configuration { + m := mock.NewMockConfiguration(gomock.NewController(t)) + m.EXPECT().AuthMethodPrivateKeyJWTSupported().Return(true) + return m + }()}, + []oidc.AuthMethod{oidc.AuthMethodBasic, oidc.AuthMethodPrivateKeyJWT}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := op.AuthMethodsIntrospectionEndpoint(tt.args.c) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_RevocationSigAlgorithms(t *testing.T) { + m := mock.NewMockConfiguration(gomock.NewController(t)) + type args struct { + c op.Configuration + } + tests := []struct { + name string + args args + want []string + }{ + { + "not supported, empty", + args{func() op.Configuration { + m.EXPECT().RevocationAuthMethodPrivateKeyJWTSupported().Return(false) + return m + }()}, + nil, + }, + { + "supported, empty", + args{func() op.Configuration { + m.EXPECT().RevocationAuthMethodPrivateKeyJWTSupported().Return(true) + m.EXPECT().RevocationEndpointSigningAlgorithmsSupported().Return(nil) + return m + }()}, + nil, + }, + { + "supported, list", + args{func() op.Configuration { + m.EXPECT().RevocationAuthMethodPrivateKeyJWTSupported().Return(true) + m.EXPECT().RevocationEndpointSigningAlgorithmsSupported().Return([]string{"RS256"}) + return m + }()}, + []string{"RS256"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := op.RevocationSigAlgorithms(tt.args.c) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_AuthMethodsRevocationEndpoint(t *testing.T) { + type args struct { + c op.Configuration + } + tests := []struct { + name string + args args + want []oidc.AuthMethod + }{ + { + "none and basic", + args{func() op.Configuration { + m := mock.NewMockConfiguration(gomock.NewController(t)) + m.EXPECT().AuthMethodPostSupported().Return(false) + m.EXPECT().AuthMethodPrivateKeyJWTSupported().Return(false) + return m + }()}, + []oidc.AuthMethod{oidc.AuthMethodNone, oidc.AuthMethodBasic}, + }, + { + "none, basic and post", + args{func() op.Configuration { + m := mock.NewMockConfiguration(gomock.NewController(t)) + m.EXPECT().AuthMethodPostSupported().Return(true) + m.EXPECT().AuthMethodPrivateKeyJWTSupported().Return(false) + return m + }()}, + []oidc.AuthMethod{oidc.AuthMethodNone, oidc.AuthMethodBasic, oidc.AuthMethodPost}, + }, + { + "none, basic, post and private_key_jwt", + args{func() op.Configuration { + m := mock.NewMockConfiguration(gomock.NewController(t)) + m.EXPECT().AuthMethodPostSupported().Return(true) + m.EXPECT().AuthMethodPrivateKeyJWTSupported().Return(true) + return m + }()}, + []oidc.AuthMethod{oidc.AuthMethodNone, oidc.AuthMethodBasic, oidc.AuthMethodPost, oidc.AuthMethodPrivateKeyJWT}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := op.AuthMethodsRevocationEndpoint(tt.args.c) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestSupportedClaims(t *testing.T) { + type args struct { + c op.Configuration + } + tests := []struct { + name string + args args + want []string + }{ + { + "scopes", + args{}, + []string{ + "sub", + "aud", + "exp", + "iat", + "iss", + "auth_time", + "nonce", + "acr", + "amr", + "c_hash", + "at_hash", + "act", + "scopes", + "client_id", + "azp", + "preferred_username", + "name", + "family_name", + "given_name", + "locale", + "email", + "email_verified", + "phone_number", + "phone_number_verified", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := op.SupportedClaims(tt.args.c) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_CodeChallengeMethods(t *testing.T) { + type args struct { + c op.Configuration + } + tests := []struct { + name string + args args + want []oidc.CodeChallengeMethod + }{ + { + "not supported", + args{func() op.Configuration { + m := mock.NewMockConfiguration(gomock.NewController(t)) + m.EXPECT().CodeMethodS256Supported().Return(false) + return m + }()}, + []oidc.CodeChallengeMethod{}, + }, + { + "S256", + args{func() op.Configuration { + m := mock.NewMockConfiguration(gomock.NewController(t)) + m.EXPECT().CodeMethodS256Supported().Return(true) + return m + }()}, + []oidc.CodeChallengeMethod{oidc.CodeChallengeMethodS256}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := op.CodeChallengeMethods(tt.args.c) + assert.Equal(t, tt.want, got) }) } } diff --git a/pkg/op/endpoint_test.go b/pkg/op/endpoint_test.go index 7c8d1ce..50de89c 100644 --- a/pkg/op/endpoint_test.go +++ b/pkg/op/endpoint_test.go @@ -3,7 +3,7 @@ package op_test import ( "testing" - "github.com/zitadel/oidc/pkg/op" + "github.com/zitadel/oidc/v2/pkg/op" ) func TestEndpoint_Path(t *testing.T) { diff --git a/pkg/op/error.go b/pkg/op/error.go index 3c820d6..acca4ab 100644 --- a/pkg/op/error.go +++ b/pkg/op/error.go @@ -3,8 +3,8 @@ package op import ( "net/http" - httphelper "github.com/zitadel/oidc/pkg/http" - "github.com/zitadel/oidc/pkg/oidc" + httphelper "github.com/zitadel/oidc/v2/pkg/http" + "github.com/zitadel/oidc/v2/pkg/oidc" ) type ErrAuthRequest interface { diff --git a/pkg/op/keys.go b/pkg/op/keys.go index a80211e..239ecbd 100644 --- a/pkg/op/keys.go +++ b/pkg/op/keys.go @@ -6,11 +6,11 @@ import ( "gopkg.in/square/go-jose.v2" - httphelper "github.com/zitadel/oidc/pkg/http" + httphelper "github.com/zitadel/oidc/v2/pkg/http" ) type KeyProvider interface { - GetKeySet(context.Context) (*jose.JSONWebKeySet, error) + KeySet(context.Context) ([]Key, error) } func keysHandler(k KeyProvider) func(http.ResponseWriter, *http.Request) { @@ -20,10 +20,23 @@ func keysHandler(k KeyProvider) func(http.ResponseWriter, *http.Request) { } func Keys(w http.ResponseWriter, r *http.Request, k KeyProvider) { - keySet, err := k.GetKeySet(r.Context()) + keySet, err := k.KeySet(r.Context()) if err != nil { httphelper.MarshalJSONWithStatus(w, err, http.StatusInternalServerError) return } - httphelper.MarshalJSON(w, keySet) + httphelper.MarshalJSON(w, jsonWebKeySet(keySet)) +} + +func jsonWebKeySet(keys []Key) *jose.JSONWebKeySet { + webKeys := make([]jose.JSONWebKey, len(keys)) + for i, key := range keys { + webKeys[i] = jose.JSONWebKey{ + KeyID: key.ID(), + Algorithm: string(key.Algorithm()), + Use: key.Use(), + Key: key.Key(), + } + } + return &jose.JSONWebKeySet{Keys: webKeys} } diff --git a/pkg/op/keys_test.go b/pkg/op/keys_test.go index 7618589..2e56b78 100644 --- a/pkg/op/keys_test.go +++ b/pkg/op/keys_test.go @@ -11,9 +11,9 @@ import ( "github.com/stretchr/testify/assert" "gopkg.in/square/go-jose.v2" - "github.com/zitadel/oidc/pkg/oidc" - "github.com/zitadel/oidc/pkg/op" - "github.com/zitadel/oidc/pkg/op/mock" + "github.com/zitadel/oidc/v2/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/op" + "github.com/zitadel/oidc/v2/pkg/op/mock" ) func TestKeys(t *testing.T) { @@ -35,7 +35,7 @@ func TestKeys(t *testing.T) { args: args{ k: func() op.KeyProvider { m := mock.NewMockKeyProvider(gomock.NewController(t)) - m.EXPECT().GetKeySet(gomock.Any()).Return(nil, oidc.ErrServerError()) + m.EXPECT().KeySet(gomock.Any()).Return(nil, oidc.ErrServerError()) return m }(), }, @@ -51,39 +51,39 @@ func TestKeys(t *testing.T) { args: args{ k: func() op.KeyProvider { m := mock.NewMockKeyProvider(gomock.NewController(t)) - m.EXPECT().GetKeySet(gomock.Any()).Return(nil, nil) + m.EXPECT().KeySet(gomock.Any()).Return(nil, nil) return m }(), }, res: res{ statusCode: http.StatusOK, contentType: "application/json", + body: `{"keys":[]} +`, }, }, { name: "list", args: args{ k: func() op.KeyProvider { - m := mock.NewMockKeyProvider(gomock.NewController(t)) - m.EXPECT().GetKeySet(gomock.Any()).Return( - &jose.JSONWebKeySet{Keys: []jose.JSONWebKey{ - { - Key: &rsa.PublicKey{ - N: big.NewInt(1), - E: 1, - }, - KeyID: "id", - }, - }}, - nil, - ) + ctrl := gomock.NewController(t) + m := mock.NewMockKeyProvider(ctrl) + k := mock.NewMockKey(ctrl) + k.EXPECT().Key().Return(&rsa.PublicKey{ + N: big.NewInt(1), + E: 1, + }) + k.EXPECT().ID().Return("id") + k.EXPECT().Algorithm().Return(jose.RS256) + k.EXPECT().Use().Return("sig") + m.EXPECT().KeySet(gomock.Any()).Return([]op.Key{k}, nil) return m }(), }, res: res{ statusCode: http.StatusOK, contentType: "application/json", - body: `{"keys":[{"kty":"RSA","kid":"id","n":"AQ","e":"AQ"}]} + body: `{"keys":[{"use":"sig","kty":"RSA","kid":"id","alg":"RS256","n":"AQ","e":"AQ"}]} `, }, }, diff --git a/pkg/op/mock/authorizer.mock.go b/pkg/op/mock/authorizer.mock.go index 52f3877..cc913ee 100644 --- a/pkg/op/mock/authorizer.mock.go +++ b/pkg/op/mock/authorizer.mock.go @@ -1,15 +1,16 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/zitadel/oidc/pkg/op (interfaces: Authorizer) +// Source: github.com/zitadel/oidc/v2/pkg/op (interfaces: Authorizer) // Package mock is a generated GoMock package. package mock import ( + context "context" reflect "reflect" gomock "github.com/golang/mock/gomock" - http "github.com/zitadel/oidc/pkg/http" - op "github.com/zitadel/oidc/pkg/op" + http "github.com/zitadel/oidc/v2/pkg/http" + op "github.com/zitadel/oidc/v2/pkg/op" ) // MockAuthorizer is a mock of Authorizer interface. @@ -78,31 +79,17 @@ func (mr *MockAuthorizerMockRecorder) Encoder() *gomock.Call { } // IDTokenHintVerifier mocks base method. -func (m *MockAuthorizer) IDTokenHintVerifier() op.IDTokenHintVerifier { +func (m *MockAuthorizer) IDTokenHintVerifier(arg0 context.Context) op.IDTokenHintVerifier { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IDTokenHintVerifier") + ret := m.ctrl.Call(m, "IDTokenHintVerifier", arg0) ret0, _ := ret[0].(op.IDTokenHintVerifier) return ret0 } // IDTokenHintVerifier indicates an expected call of IDTokenHintVerifier. -func (mr *MockAuthorizerMockRecorder) IDTokenHintVerifier() *gomock.Call { +func (mr *MockAuthorizerMockRecorder) IDTokenHintVerifier(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IDTokenHintVerifier", reflect.TypeOf((*MockAuthorizer)(nil).IDTokenHintVerifier)) -} - -// Issuer mocks base method. -func (m *MockAuthorizer) Issuer() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Issuer") - ret0, _ := ret[0].(string) - return ret0 -} - -// Issuer indicates an expected call of Issuer. -func (mr *MockAuthorizerMockRecorder) Issuer() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Issuer", reflect.TypeOf((*MockAuthorizer)(nil).Issuer)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IDTokenHintVerifier", reflect.TypeOf((*MockAuthorizer)(nil).IDTokenHintVerifier), arg0) } // RequestObjectSupported mocks base method. @@ -119,20 +106,6 @@ func (mr *MockAuthorizerMockRecorder) RequestObjectSupported() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequestObjectSupported", reflect.TypeOf((*MockAuthorizer)(nil).RequestObjectSupported)) } -// Signer mocks base method. -func (m *MockAuthorizer) Signer() op.Signer { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Signer") - ret0, _ := ret[0].(op.Signer) - return ret0 -} - -// Signer indicates an expected call of Signer. -func (mr *MockAuthorizerMockRecorder) Signer() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Signer", reflect.TypeOf((*MockAuthorizer)(nil).Signer)) -} - // Storage mocks base method. func (m *MockAuthorizer) Storage() op.Storage { m.ctrl.T.Helper() diff --git a/pkg/op/mock/authorizer.mock.impl.go b/pkg/op/mock/authorizer.mock.impl.go index d4f29d5..3f1d525 100644 --- a/pkg/op/mock/authorizer.mock.impl.go +++ b/pkg/op/mock/authorizer.mock.impl.go @@ -8,8 +8,8 @@ import ( "github.com/gorilla/schema" "gopkg.in/square/go-jose.v2" - "github.com/zitadel/oidc/pkg/oidc" - "github.com/zitadel/oidc/pkg/op" + "github.com/zitadel/oidc/v2/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/op" ) func NewAuthorizer(t *testing.T) op.Authorizer { @@ -20,23 +20,13 @@ func NewAuthorizerExpectValid(t *testing.T, wantErr bool) op.Authorizer { m := NewAuthorizer(t) ExpectDecoder(m) ExpectEncoder(m) - ExpectSigner(m, t) + //ExpectSigner(m, t) ExpectStorage(m, t) ExpectVerifier(m, t) // ExpectErrorHandler(m, t, wantErr) return m } -// func NewAuthorizerExpectDecoderFails(t *testing.T) op.Authorizer { -// m := NewAuthorizer(t) -// ExpectDecoderFails(m) -// ExpectEncoder(m) -// ExpectSigner(m, t) -// ExpectStorage(m, t) -// ExpectErrorHandler(m, t) -// return m -// } - func ExpectDecoder(a op.Authorizer) { mockA := a.(*MockAuthorizer) mockA.EXPECT().Decoder().AnyTimes().Return(schema.NewDecoder()) @@ -47,17 +37,18 @@ func ExpectEncoder(a op.Authorizer) { mockA.EXPECT().Encoder().AnyTimes().Return(schema.NewEncoder()) } -func ExpectSigner(a op.Authorizer, t *testing.T) { - mockA := a.(*MockAuthorizer) - mockA.EXPECT().Signer().DoAndReturn( - func() op.Signer { - return &Sig{} - }) -} +// +//func ExpectSigner(a op.Authorizer, t *testing.T) { +// mockA := a.(*MockAuthorizer) +// mockA.EXPECT().Signer().DoAndReturn( +// func() op.Signer { +// return &Sig{} +// }) +//} func ExpectVerifier(a op.Authorizer, t *testing.T) { mockA := a.(*MockAuthorizer) - mockA.EXPECT().IDTokenHintVerifier().DoAndReturn( + mockA.EXPECT().IDTokenHintVerifier(gomock.Any()).DoAndReturn( func() op.IDTokenHintVerifier { return op.NewIDTokenHintVerifier("", nil) }) diff --git a/pkg/op/mock/client.go b/pkg/op/mock/client.go index 3b16e5e..36df84a 100644 --- a/pkg/op/mock/client.go +++ b/pkg/op/mock/client.go @@ -5,8 +5,8 @@ import ( "github.com/golang/mock/gomock" - "github.com/zitadel/oidc/pkg/oidc" - "github.com/zitadel/oidc/pkg/op" + "github.com/zitadel/oidc/v2/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/op" ) func NewClient(t *testing.T) op.Client { diff --git a/pkg/op/mock/client.mock.go b/pkg/op/mock/client.mock.go index cfe3703..e3d19fb 100644 --- a/pkg/op/mock/client.mock.go +++ b/pkg/op/mock/client.mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/zitadel/oidc/pkg/op (interfaces: Client) +// Source: github.com/zitadel/oidc/v2/pkg/op (interfaces: Client) // Package mock is a generated GoMock package. package mock @@ -9,8 +9,8 @@ import ( time "time" gomock "github.com/golang/mock/gomock" - oidc "github.com/zitadel/oidc/pkg/oidc" - op "github.com/zitadel/oidc/pkg/op" + oidc "github.com/zitadel/oidc/v2/pkg/oidc" + op "github.com/zitadel/oidc/v2/pkg/op" ) // MockClient is a mock of Client interface. diff --git a/pkg/op/mock/configuration.mock.go b/pkg/op/mock/configuration.mock.go index e0c90dc..fc3158a 100644 --- a/pkg/op/mock/configuration.mock.go +++ b/pkg/op/mock/configuration.mock.go @@ -1,14 +1,15 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/zitadel/oidc/pkg/op (interfaces: Configuration) +// Source: github.com/zitadel/oidc/v2/pkg/op (interfaces: Configuration) // Package mock is a generated GoMock package. package mock import ( + http "net/http" reflect "reflect" gomock "github.com/golang/mock/gomock" - op "github.com/zitadel/oidc/pkg/op" + op "github.com/zitadel/oidc/v2/pkg/op" language "golang.org/x/text/language" ) @@ -161,6 +162,20 @@ func (mr *MockConfigurationMockRecorder) GrantTypeTokenExchangeSupported() *gomo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrantTypeTokenExchangeSupported", reflect.TypeOf((*MockConfiguration)(nil).GrantTypeTokenExchangeSupported)) } +// Insecure mocks base method. +func (m *MockConfiguration) Insecure() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Insecure") + ret0, _ := ret[0].(bool) + return ret0 +} + +// Insecure indicates an expected call of Insecure. +func (mr *MockConfigurationMockRecorder) Insecure() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insecure", reflect.TypeOf((*MockConfiguration)(nil).Insecure)) +} + // IntrospectionAuthMethodPrivateKeyJWTSupported mocks base method. func (m *MockConfiguration) IntrospectionAuthMethodPrivateKeyJWTSupported() bool { m.ctrl.T.Helper() @@ -203,18 +218,18 @@ func (mr *MockConfigurationMockRecorder) IntrospectionEndpointSigningAlgorithmsS return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IntrospectionEndpointSigningAlgorithmsSupported", reflect.TypeOf((*MockConfiguration)(nil).IntrospectionEndpointSigningAlgorithmsSupported)) } -// Issuer mocks base method. -func (m *MockConfiguration) Issuer() string { +// IssuerFromRequest mocks base method. +func (m *MockConfiguration) IssuerFromRequest(arg0 *http.Request) string { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Issuer") + ret := m.ctrl.Call(m, "IssuerFromRequest", arg0) ret0, _ := ret[0].(string) return ret0 } -// Issuer indicates an expected call of Issuer. -func (mr *MockConfigurationMockRecorder) Issuer() *gomock.Call { +// IssuerFromRequest indicates an expected call of IssuerFromRequest. +func (mr *MockConfigurationMockRecorder) IssuerFromRequest(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Issuer", reflect.TypeOf((*MockConfiguration)(nil).Issuer)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IssuerFromRequest", reflect.TypeOf((*MockConfiguration)(nil).IssuerFromRequest), arg0) } // KeysEndpoint mocks base method. diff --git a/pkg/op/mock/discovery.mock.go b/pkg/op/mock/discovery.mock.go new file mode 100644 index 0000000..0c78d52 --- /dev/null +++ b/pkg/op/mock/discovery.mock.go @@ -0,0 +1,51 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/zitadel/oidc/v2/pkg/op (interfaces: DiscoverStorage) + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + jose "gopkg.in/square/go-jose.v2" +) + +// MockDiscoverStorage is a mock of DiscoverStorage interface. +type MockDiscoverStorage struct { + ctrl *gomock.Controller + recorder *MockDiscoverStorageMockRecorder +} + +// MockDiscoverStorageMockRecorder is the mock recorder for MockDiscoverStorage. +type MockDiscoverStorageMockRecorder struct { + mock *MockDiscoverStorage +} + +// NewMockDiscoverStorage creates a new mock instance. +func NewMockDiscoverStorage(ctrl *gomock.Controller) *MockDiscoverStorage { + mock := &MockDiscoverStorage{ctrl: ctrl} + mock.recorder = &MockDiscoverStorageMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDiscoverStorage) EXPECT() *MockDiscoverStorageMockRecorder { + return m.recorder +} + +// SignatureAlgorithms mocks base method. +func (m *MockDiscoverStorage) SignatureAlgorithms(arg0 context.Context) ([]jose.SignatureAlgorithm, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SignatureAlgorithms", arg0) + ret0, _ := ret[0].([]jose.SignatureAlgorithm) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SignatureAlgorithms indicates an expected call of SignatureAlgorithms. +func (mr *MockDiscoverStorageMockRecorder) SignatureAlgorithms(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SignatureAlgorithms", reflect.TypeOf((*MockDiscoverStorage)(nil).SignatureAlgorithms), arg0) +} diff --git a/pkg/op/mock/generate.go b/pkg/op/mock/generate.go index c9c7efa..0066571 100644 --- a/pkg/op/mock/generate.go +++ b/pkg/op/mock/generate.go @@ -1,8 +1,9 @@ package mock -//go:generate mockgen -package mock -destination ./storage.mock.go github.com/zitadel/oidc/pkg/op Storage -//go:generate mockgen -package mock -destination ./authorizer.mock.go github.com/zitadel/oidc/pkg/op Authorizer -//go:generate mockgen -package mock -destination ./client.mock.go github.com/zitadel/oidc/pkg/op Client -//go:generate mockgen -package mock -destination ./configuration.mock.go github.com/zitadel/oidc/pkg/op Configuration -//go:generate mockgen -package mock -destination ./signer.mock.go github.com/zitadel/oidc/pkg/op Signer -//go:generate mockgen -package mock -destination ./key.mock.go github.com/zitadel/oidc/pkg/op KeyProvider +//go:generate mockgen -package mock -destination ./storage.mock.go github.com/zitadel/oidc/v2/pkg/op Storage +//go:generate mockgen -package mock -destination ./authorizer.mock.go github.com/zitadel/oidc/v2/pkg/op Authorizer +//go:generate mockgen -package mock -destination ./client.mock.go github.com/zitadel/oidc/v2/pkg/op Client +//go:generate mockgen -package mock -destination ./configuration.mock.go github.com/zitadel/oidc/v2/pkg/op Configuration +//go:generate mockgen -package mock -destination ./discovery.mock.go github.com/zitadel/oidc/v2/pkg/op DiscoverStorage +//go:generate mockgen -package mock -destination ./signer.mock.go github.com/zitadel/oidc/v2/pkg/op SigningKey,Key +//go:generate mockgen -package mock -destination ./key.mock.go github.com/zitadel/oidc/v2/pkg/op KeyProvider diff --git a/pkg/op/mock/key.mock.go b/pkg/op/mock/key.mock.go index 56d12dc..8831651 100644 --- a/pkg/op/mock/key.mock.go +++ b/pkg/op/mock/key.mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/zitadel/oidc/pkg/op (interfaces: KeyProvider) +// Source: github.com/zitadel/oidc/v2/pkg/op (interfaces: KeyProvider) // Package mock is a generated GoMock package. package mock @@ -9,7 +9,7 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - jose "gopkg.in/square/go-jose.v2" + op "github.com/zitadel/oidc/v2/pkg/op" ) // MockKeyProvider is a mock of KeyProvider interface. @@ -35,17 +35,17 @@ func (m *MockKeyProvider) EXPECT() *MockKeyProviderMockRecorder { return m.recorder } -// GetKeySet mocks base method. -func (m *MockKeyProvider) GetKeySet(arg0 context.Context) (*jose.JSONWebKeySet, error) { +// KeySet mocks base method. +func (m *MockKeyProvider) KeySet(arg0 context.Context) ([]op.Key, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetKeySet", arg0) - ret0, _ := ret[0].(*jose.JSONWebKeySet) + ret := m.ctrl.Call(m, "KeySet", arg0) + ret0, _ := ret[0].([]op.Key) ret1, _ := ret[1].(error) return ret0, ret1 } -// GetKeySet indicates an expected call of GetKeySet. -func (mr *MockKeyProviderMockRecorder) GetKeySet(arg0 interface{}) *gomock.Call { +// KeySet indicates an expected call of KeySet. +func (mr *MockKeyProviderMockRecorder) KeySet(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetKeySet", reflect.TypeOf((*MockKeyProvider)(nil).GetKeySet), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeySet", reflect.TypeOf((*MockKeyProvider)(nil).KeySet), arg0) } diff --git a/pkg/op/mock/signer.mock.go b/pkg/op/mock/signer.mock.go index 42a92fb..78c0efe 100644 --- a/pkg/op/mock/signer.mock.go +++ b/pkg/op/mock/signer.mock.go @@ -1,56 +1,69 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/zitadel/oidc/pkg/op (interfaces: Signer) +// Source: github.com/zitadel/oidc/v2/pkg/op (interfaces: SigningKey,Key) // Package mock is a generated GoMock package. package mock import ( - context "context" reflect "reflect" gomock "github.com/golang/mock/gomock" jose "gopkg.in/square/go-jose.v2" ) -// MockSigner is a mock of Signer interface. -type MockSigner struct { +// MockSigningKey is a mock of SigningKey interface. +type MockSigningKey struct { ctrl *gomock.Controller - recorder *MockSignerMockRecorder + recorder *MockSigningKeyMockRecorder } -// MockSignerMockRecorder is the mock recorder for MockSigner. -type MockSignerMockRecorder struct { - mock *MockSigner +// MockSigningKeyMockRecorder is the mock recorder for MockSigningKey. +type MockSigningKeyMockRecorder struct { + mock *MockSigningKey } -// NewMockSigner creates a new mock instance. -func NewMockSigner(ctrl *gomock.Controller) *MockSigner { - mock := &MockSigner{ctrl: ctrl} - mock.recorder = &MockSignerMockRecorder{mock} +// NewMockSigningKey creates a new mock instance. +func NewMockSigningKey(ctrl *gomock.Controller) *MockSigningKey { + mock := &MockSigningKey{ctrl: ctrl} + mock.recorder = &MockSigningKeyMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockSigner) EXPECT() *MockSignerMockRecorder { +func (m *MockSigningKey) EXPECT() *MockSigningKeyMockRecorder { return m.recorder } -// Health mocks base method. -func (m *MockSigner) Health(arg0 context.Context) error { +// ID mocks base method. +func (m *MockSigningKey) ID() string { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Health", arg0) - ret0, _ := ret[0].(error) + ret := m.ctrl.Call(m, "ID") + ret0, _ := ret[0].(string) return ret0 } -// Health indicates an expected call of Health. -func (mr *MockSignerMockRecorder) Health(arg0 interface{}) *gomock.Call { +// ID indicates an expected call of ID. +func (mr *MockSigningKeyMockRecorder) ID() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Health", reflect.TypeOf((*MockSigner)(nil).Health), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockSigningKey)(nil).ID)) +} + +// Key mocks base method. +func (m *MockSigningKey) Key() interface{} { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Key") + ret0, _ := ret[0].(interface{}) + return ret0 +} + +// Key indicates an expected call of Key. +func (mr *MockSigningKeyMockRecorder) Key() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Key", reflect.TypeOf((*MockSigningKey)(nil).Key)) } // SignatureAlgorithm mocks base method. -func (m *MockSigner) SignatureAlgorithm() jose.SignatureAlgorithm { +func (m *MockSigningKey) SignatureAlgorithm() jose.SignatureAlgorithm { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SignatureAlgorithm") ret0, _ := ret[0].(jose.SignatureAlgorithm) @@ -58,21 +71,86 @@ func (m *MockSigner) SignatureAlgorithm() jose.SignatureAlgorithm { } // SignatureAlgorithm indicates an expected call of SignatureAlgorithm. -func (mr *MockSignerMockRecorder) SignatureAlgorithm() *gomock.Call { +func (mr *MockSigningKeyMockRecorder) SignatureAlgorithm() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SignatureAlgorithm", reflect.TypeOf((*MockSigner)(nil).SignatureAlgorithm)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SignatureAlgorithm", reflect.TypeOf((*MockSigningKey)(nil).SignatureAlgorithm)) } -// Signer mocks base method. -func (m *MockSigner) Signer() jose.Signer { +// MockKey is a mock of Key interface. +type MockKey struct { + ctrl *gomock.Controller + recorder *MockKeyMockRecorder +} + +// MockKeyMockRecorder is the mock recorder for MockKey. +type MockKeyMockRecorder struct { + mock *MockKey +} + +// NewMockKey creates a new mock instance. +func NewMockKey(ctrl *gomock.Controller) *MockKey { + mock := &MockKey{ctrl: ctrl} + mock.recorder = &MockKeyMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockKey) EXPECT() *MockKeyMockRecorder { + return m.recorder +} + +// Algorithm mocks base method. +func (m *MockKey) Algorithm() jose.SignatureAlgorithm { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Signer") - ret0, _ := ret[0].(jose.Signer) + ret := m.ctrl.Call(m, "Algorithm") + ret0, _ := ret[0].(jose.SignatureAlgorithm) return ret0 } -// Signer indicates an expected call of Signer. -func (mr *MockSignerMockRecorder) Signer() *gomock.Call { +// Algorithm indicates an expected call of Algorithm. +func (mr *MockKeyMockRecorder) Algorithm() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Signer", reflect.TypeOf((*MockSigner)(nil).Signer)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Algorithm", reflect.TypeOf((*MockKey)(nil).Algorithm)) +} + +// ID mocks base method. +func (m *MockKey) ID() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ID") + ret0, _ := ret[0].(string) + return ret0 +} + +// ID indicates an expected call of ID. +func (mr *MockKeyMockRecorder) ID() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockKey)(nil).ID)) +} + +// Key mocks base method. +func (m *MockKey) Key() interface{} { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Key") + ret0, _ := ret[0].(interface{}) + return ret0 +} + +// Key indicates an expected call of Key. +func (mr *MockKeyMockRecorder) Key() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Key", reflect.TypeOf((*MockKey)(nil).Key)) +} + +// Use mocks base method. +func (m *MockKey) Use() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Use") + ret0, _ := ret[0].(string) + return ret0 +} + +// Use indicates an expected call of Use. +func (mr *MockKeyMockRecorder) Use() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Use", reflect.TypeOf((*MockKey)(nil).Use)) } diff --git a/pkg/op/mock/storage.mock.go b/pkg/op/mock/storage.mock.go index 785a643..58cc2a0 100644 --- a/pkg/op/mock/storage.mock.go +++ b/pkg/op/mock/storage.mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/zitadel/oidc/pkg/op (interfaces: Storage) +// Source: github.com/zitadel/oidc/v2/pkg/op (interfaces: Storage) // Package mock is a generated GoMock package. package mock @@ -10,8 +10,8 @@ import ( time "time" gomock "github.com/golang/mock/gomock" - oidc "github.com/zitadel/oidc/pkg/oidc" - op "github.com/zitadel/oidc/pkg/op" + oidc "github.com/zitadel/oidc/v2/pkg/oidc" + op "github.com/zitadel/oidc/v2/pkg/op" jose "gopkg.in/square/go-jose.v2" ) @@ -174,21 +174,6 @@ func (mr *MockStorageMockRecorder) GetKeyByIDAndUserID(arg0, arg1, arg2 interfac return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetKeyByIDAndUserID", reflect.TypeOf((*MockStorage)(nil).GetKeyByIDAndUserID), arg0, arg1, arg2) } -// GetKeySet mocks base method. -func (m *MockStorage) GetKeySet(arg0 context.Context) (*jose.JSONWebKeySet, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetKeySet", arg0) - ret0, _ := ret[0].(*jose.JSONWebKeySet) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetKeySet indicates an expected call of GetKeySet. -func (mr *MockStorageMockRecorder) GetKeySet(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetKeySet", reflect.TypeOf((*MockStorage)(nil).GetKeySet), arg0) -} - // GetPrivateClaimsFromScopes mocks base method. func (m *MockStorage) GetPrivateClaimsFromScopes(arg0 context.Context, arg1, arg2 string, arg3 []string) (map[string]interface{}, error) { m.ctrl.T.Helper() @@ -204,18 +189,6 @@ func (mr *MockStorageMockRecorder) GetPrivateClaimsFromScopes(arg0, arg1, arg2, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPrivateClaimsFromScopes", reflect.TypeOf((*MockStorage)(nil).GetPrivateClaimsFromScopes), arg0, arg1, arg2, arg3) } -// GetSigningKey mocks base method. -func (m *MockStorage) GetSigningKey(arg0 context.Context, arg1 chan<- jose.SigningKey) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "GetSigningKey", arg0, arg1) -} - -// GetSigningKey indicates an expected call of GetSigningKey. -func (mr *MockStorageMockRecorder) GetSigningKey(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSigningKey", reflect.TypeOf((*MockStorage)(nil).GetSigningKey), arg0, arg1) -} - // Health mocks base method. func (m *MockStorage) Health(arg0 context.Context) error { m.ctrl.T.Helper() @@ -230,6 +203,21 @@ func (mr *MockStorageMockRecorder) Health(arg0 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Health", reflect.TypeOf((*MockStorage)(nil).Health), arg0) } +// KeySet mocks base method. +func (m *MockStorage) KeySet(arg0 context.Context) ([]op.Key, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "KeySet", arg0) + ret0, _ := ret[0].([]op.Key) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// KeySet indicates an expected call of KeySet. +func (mr *MockStorageMockRecorder) KeySet(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeySet", reflect.TypeOf((*MockStorage)(nil).KeySet), arg0) +} + // RevokeToken mocks base method. func (m *MockStorage) RevokeToken(arg0 context.Context, arg1, arg2, arg3 string) *oidc.Error { m.ctrl.T.Helper() @@ -300,6 +288,36 @@ func (mr *MockStorageMockRecorder) SetUserinfoFromToken(arg0, arg1, arg2, arg3, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetUserinfoFromToken", reflect.TypeOf((*MockStorage)(nil).SetUserinfoFromToken), arg0, arg1, arg2, arg3, arg4) } +// SignatureAlgorithms mocks base method. +func (m *MockStorage) SignatureAlgorithms(arg0 context.Context) ([]jose.SignatureAlgorithm, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SignatureAlgorithms", arg0) + ret0, _ := ret[0].([]jose.SignatureAlgorithm) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SignatureAlgorithms indicates an expected call of SignatureAlgorithms. +func (mr *MockStorageMockRecorder) SignatureAlgorithms(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SignatureAlgorithms", reflect.TypeOf((*MockStorage)(nil).SignatureAlgorithms), arg0) +} + +// SigningKey mocks base method. +func (m *MockStorage) SigningKey(arg0 context.Context) (op.SigningKey, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SigningKey", arg0) + ret0, _ := ret[0].(op.SigningKey) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SigningKey indicates an expected call of SigningKey. +func (mr *MockStorageMockRecorder) SigningKey(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SigningKey", reflect.TypeOf((*MockStorage)(nil).SigningKey), arg0) +} + // TerminateSession mocks base method. func (m *MockStorage) TerminateSession(arg0 context.Context, arg1, arg2 string) error { m.ctrl.T.Helper() diff --git a/pkg/op/mock/storage.mock.impl.go b/pkg/op/mock/storage.mock.impl.go index 946cee0..9269f89 100644 --- a/pkg/op/mock/storage.mock.impl.go +++ b/pkg/op/mock/storage.mock.impl.go @@ -6,13 +6,10 @@ import ( "testing" "time" - "github.com/zitadel/oidc/pkg/oidc" - - "gopkg.in/square/go-jose.v2" - "github.com/golang/mock/gomock" - "github.com/zitadel/oidc/pkg/op" + "github.com/zitadel/oidc/v2/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/op" ) func NewStorage(t *testing.T) op.Storage { @@ -41,13 +38,13 @@ func NewMockStorageAny(t *testing.T) op.Storage { func NewMockStorageSigningKeyInvalid(t *testing.T) op.Storage { m := NewStorage(t) - ExpectSigningKeyInvalid(m) + //ExpectSigningKeyInvalid(m) return m } func NewMockStorageSigningKey(t *testing.T) op.Storage { m := NewStorage(t) - ExpectSigningKey(m) + //ExpectSigningKey(m) return m } @@ -85,24 +82,6 @@ func ExpectValidClientID(s op.Storage) { }) } -func ExpectSigningKeyInvalid(s op.Storage) { - mockS := s.(*MockStorage) - mockS.EXPECT().GetSigningKey(gomock.Any(), gomock.Any()).DoAndReturn( - func(_ context.Context, keyCh chan<- jose.SigningKey) { - keyCh <- jose.SigningKey{} - }, - ) -} - -func ExpectSigningKey(s op.Storage) { - mockS := s.(*MockStorage) - mockS.EXPECT().GetSigningKey(gomock.Any(), gomock.Any()).DoAndReturn( - func(_ context.Context, keyCh chan<- jose.SigningKey) { - keyCh <- jose.SigningKey{Algorithm: jose.HS256, Key: []byte("key")} - }, - ) -} - type ConfClient struct { id string appType op.ApplicationType diff --git a/pkg/op/op.go b/pkg/op/op.go index d85dcd6..acedcb6 100644 --- a/pkg/op/op.go +++ b/pkg/op/op.go @@ -12,8 +12,8 @@ import ( "golang.org/x/text/language" "gopkg.in/square/go-jose.v2" - httphelper "github.com/zitadel/oidc/pkg/http" - "github.com/zitadel/oidc/pkg/oidc" + httphelper "github.com/zitadel/oidc/v2/pkg/http" + "github.com/zitadel/oidc/v2/pkg/oidc" ) const ( @@ -29,78 +29,79 @@ const ( defaultKeysEndpoint = "keys" ) -var DefaultEndpoints = &endpoints{ - Authorization: NewEndpoint(defaultAuthorizationEndpoint), - Token: NewEndpoint(defaultTokenEndpoint), - Introspection: NewEndpoint(defaultIntrospectEndpoint), - Userinfo: NewEndpoint(defaultUserinfoEndpoint), - Revocation: NewEndpoint(defaultRevocationEndpoint), - EndSession: NewEndpoint(defaultEndSessionEndpoint), - JwksURI: NewEndpoint(defaultKeysEndpoint), -} +var ( + DefaultEndpoints = &endpoints{ + Authorization: NewEndpoint(defaultAuthorizationEndpoint), + Token: NewEndpoint(defaultTokenEndpoint), + Introspection: NewEndpoint(defaultIntrospectEndpoint), + Userinfo: NewEndpoint(defaultUserinfoEndpoint), + Revocation: NewEndpoint(defaultRevocationEndpoint), + EndSession: NewEndpoint(defaultEndSessionEndpoint), + JwksURI: NewEndpoint(defaultKeysEndpoint), + } + + defaultCORSOptions = cors.Options{ + AllowCredentials: true, + AllowedHeaders: []string{ + "Origin", + "Accept", + "Accept-Language", + "Authorization", + "Content-Type", + "X-Requested-With", + }, + AllowedMethods: []string{ + http.MethodGet, + http.MethodHead, + http.MethodPost, + }, + ExposedHeaders: []string{ + "Location", + "Content-Length", + }, + AllowOriginFunc: func(_ string) bool { + return true + }, + } +) type OpenIDProvider interface { Configuration Storage() Storage Decoder() httphelper.Decoder Encoder() httphelper.Encoder - IDTokenHintVerifier() IDTokenHintVerifier - AccessTokenVerifier() AccessTokenVerifier + IDTokenHintVerifier(context.Context) IDTokenHintVerifier + AccessTokenVerifier(context.Context) AccessTokenVerifier Crypto() Crypto DefaultLogoutRedirectURI() string - Signer() Signer Probes() []ProbesFn HttpHandler() http.Handler } type HttpInterceptor func(http.Handler) http.Handler -var defaultCORSOptions = cors.Options{ - AllowCredentials: true, - AllowedHeaders: []string{ - "Origin", - "Accept", - "Accept-Language", - "Authorization", - "Content-Type", - "X-Requested-With", - }, - AllowedMethods: []string{ - http.MethodGet, - http.MethodHead, - http.MethodPost, - }, - ExposedHeaders: []string{ - "Location", - "Content-Length", - }, - AllowOriginFunc: func(_ string) bool { - return true - }, -} - func CreateRouter(o OpenIDProvider, interceptors ...HttpInterceptor) *mux.Router { - intercept := buildInterceptor(interceptors...) router := mux.NewRouter() router.Use(cors.New(defaultCORSOptions).Handler) + router.Use(intercept(o.IssuerFromRequest, interceptors...)) 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))) - router.NewRoute().Path(authCallbackPath(o)).Queries("id", "{id}").Handler(intercept(authorizeCallbackHandler(o))) - router.Handle(o.TokenEndpoint().Relative(), intercept(tokenHandler(o))) + router.HandleFunc(oidc.DiscoveryEndpoint, discoveryHandler(o, o.Storage())) + router.HandleFunc(o.AuthorizationEndpoint().Relative(), authorizeHandler(o)) + router.NewRoute().Path(authCallbackPath(o)).Queries("id", "{id}").HandlerFunc(authorizeCallbackHandler(o)) + router.HandleFunc(o.TokenEndpoint().Relative(), tokenHandler(o)) router.HandleFunc(o.IntrospectionEndpoint().Relative(), introspectionHandler(o)) router.HandleFunc(o.UserinfoEndpoint().Relative(), userinfoHandler(o)) router.HandleFunc(o.RevocationEndpoint().Relative(), revocationHandler(o)) - router.Handle(o.EndSessionEndpoint().Relative(), intercept(endSessionHandler(o))) + router.HandleFunc(o.EndSessionEndpoint().Relative(), endSessionHandler(o)) router.HandleFunc(o.KeysEndpoint().Relative(), keysHandler(o.Storage())) 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 AuthCallbackURL(o OpenIDProvider) func(context.Context, string) string { + return func(ctx context.Context, requestID string) string { + return o.AuthorizationEndpoint().Absolute(IssuerFromContext(ctx)) + authCallbackPathSuffix + "?id=" + requestID } } @@ -109,7 +110,6 @@ func authCallbackPath(o OpenIDProvider) string { } type Config struct { - Issuer string CryptoKey [32]byte DefaultLogoutRedirectURI string CodeMethodS256 bool @@ -133,29 +133,34 @@ type endpoints struct { // NewOpenIDProvider creates a provider. The provider provides (with HttpHandler()) // a http.Router that handles a suite of endpoints (some paths can be overridden): -// /healthz -// /ready -// /.well-known/openid-configuration -// /oauth/token -// /oauth/introspect -// /callback -// /authorize -// /userinfo -// /revoke -// /end_session -// /keys +// +// /healthz +// /ready +// /.well-known/openid-configuration +// /oauth/token +// /oauth/introspect +// /callback +// /authorize +// /userinfo +// /revoke +// /end_session +// /keys +// // This does not include login. Login is handled with a redirect that includes the // request ID. The redirect for logins is specified per-client by Client.LoginURL(). // Successful logins should mark the request as authorized and redirect back to to // op.AuthCallbackURL(provider) which is probably /callback. On the redirect back // to the AuthCallbackURL, the request id should be passed as the "id" parameter. -func NewOpenIDProvider(ctx context.Context, config *Config, storage Storage, opOpts ...Option) (OpenIDProvider, error) { - err := ValidateIssuer(config.Issuer) - if err != nil { - return nil, err - } +func NewOpenIDProvider(ctx context.Context, issuer string, config *Config, storage Storage, opOpts ...Option) (*Provider, error) { + return newProvider(ctx, config, storage, StaticIssuer(issuer), opOpts...) +} - o := &openidProvider{ +func NewDynamicOpenIDProvider(ctx context.Context, path string, config *Config, storage Storage, opOpts ...Option) (*Provider, error) { + return newProvider(ctx, config, storage, IssuerFromHost(path), opOpts...) +} + +func newProvider(ctx context.Context, config *Config, storage Storage, issuer func(bool) (IssuerFromRequest, error), opOpts ...Option) (_ *Provider, err error) { + o := &Provider{ config: config, storage: storage, endpoints: DefaultEndpoints, @@ -168,9 +173,10 @@ func NewOpenIDProvider(ctx context.Context, config *Config, storage Storage, opO } } - keyCh := make(chan jose.SigningKey) - go storage.GetSigningKey(ctx, keyCh) - o.signer = NewSigner(ctx, storage, keyCh) + o.issuer, err = issuer(o.insecure) + if err != nil { + return nil, err + } o.httpHandler = CreateRouter(o, o.interceptors...) @@ -182,22 +188,17 @@ func NewOpenIDProvider(ctx context.Context, config *Config, storage Storage, opO o.crypto = NewAESCrypto(config.CryptoKey) // Avoid potential race conditions by calling these early - _ = o.AccessTokenVerifier() // sets accessTokenVerifier - _ = o.IDTokenHintVerifier() // sets idTokenHintVerifier - _ = o.JWTProfileVerifier() // sets jwtProfileVerifier - _ = o.openIDKeySet() // sets keySet + _ = o.openIDKeySet() // sets keySet return o, nil } -type openidProvider struct { +type Provider struct { config *Config + issuer IssuerFromRequest + insecure bool endpoints *endpoints storage Storage - signer Signer - idTokenHintVerifier IDTokenHintVerifier - jwtProfileVerifier JWTProfileVerifier - accessTokenVerifier AccessTokenVerifier keySet *openIDKeySet crypto Crypto httpHandler http.Handler @@ -209,159 +210,149 @@ type openidProvider struct { idTokenHintVerifierOpts []IDTokenHintVerifierOpt } -func (o *openidProvider) Issuer() string { - return o.config.Issuer +func (o *Provider) IssuerFromRequest(r *http.Request) string { + return o.issuer(r) } -func (o *openidProvider) AuthorizationEndpoint() Endpoint { +func (o *Provider) Insecure() bool { + return o.insecure +} + +func (o *Provider) AuthorizationEndpoint() Endpoint { return o.endpoints.Authorization } -func (o *openidProvider) TokenEndpoint() Endpoint { +func (o *Provider) TokenEndpoint() Endpoint { return o.endpoints.Token } -func (o *openidProvider) IntrospectionEndpoint() Endpoint { +func (o *Provider) IntrospectionEndpoint() Endpoint { return o.endpoints.Introspection } -func (o *openidProvider) UserinfoEndpoint() Endpoint { +func (o *Provider) UserinfoEndpoint() Endpoint { return o.endpoints.Userinfo } -func (o *openidProvider) RevocationEndpoint() Endpoint { +func (o *Provider) RevocationEndpoint() Endpoint { return o.endpoints.Revocation } -func (o *openidProvider) EndSessionEndpoint() Endpoint { +func (o *Provider) EndSessionEndpoint() Endpoint { return o.endpoints.EndSession } -func (o *openidProvider) KeysEndpoint() Endpoint { +func (o *Provider) KeysEndpoint() Endpoint { return o.endpoints.JwksURI } -func (o *openidProvider) AuthMethodPostSupported() bool { +func (o *Provider) AuthMethodPostSupported() bool { return o.config.AuthMethodPost } -func (o *openidProvider) CodeMethodS256Supported() bool { +func (o *Provider) CodeMethodS256Supported() bool { return o.config.CodeMethodS256 } -func (o *openidProvider) AuthMethodPrivateKeyJWTSupported() bool { +func (o *Provider) AuthMethodPrivateKeyJWTSupported() bool { return o.config.AuthMethodPrivateKeyJWT } -func (o *openidProvider) TokenEndpointSigningAlgorithmsSupported() []string { +func (o *Provider) TokenEndpointSigningAlgorithmsSupported() []string { return []string{"RS256"} } -func (o *openidProvider) GrantTypeRefreshTokenSupported() bool { +func (o *Provider) GrantTypeRefreshTokenSupported() bool { return o.config.GrantTypeRefreshToken } -func (o *openidProvider) GrantTypeTokenExchangeSupported() bool { +func (o *Provider) GrantTypeTokenExchangeSupported() bool { return false } -func (o *openidProvider) GrantTypeJWTAuthorizationSupported() bool { +func (o *Provider) GrantTypeJWTAuthorizationSupported() bool { return true } -func (o *openidProvider) GrantTypeClientCredentialsSupported() bool { +func (o *Provider) IntrospectionAuthMethodPrivateKeyJWTSupported() bool { + return true +} + +func (o *Provider) IntrospectionEndpointSigningAlgorithmsSupported() []string { + return []string{"RS256"} +} + +func (o *Provider) GrantTypeClientCredentialsSupported() bool { _, ok := o.storage.(ClientCredentialsStorage) return ok } -func (o *openidProvider) IntrospectionAuthMethodPrivateKeyJWTSupported() bool { +func (o *Provider) RevocationAuthMethodPrivateKeyJWTSupported() bool { return true } -func (o *openidProvider) IntrospectionEndpointSigningAlgorithmsSupported() []string { +func (o *Provider) RevocationEndpointSigningAlgorithmsSupported() []string { return []string{"RS256"} } -func (o *openidProvider) RevocationAuthMethodPrivateKeyJWTSupported() bool { - return true -} - -func (o *openidProvider) RevocationEndpointSigningAlgorithmsSupported() []string { - return []string{"RS256"} -} - -func (o *openidProvider) RequestObjectSupported() bool { +func (o *Provider) RequestObjectSupported() bool { return o.config.RequestObjectSupported } -func (o *openidProvider) RequestObjectSigningAlgorithmsSupported() []string { +func (o *Provider) RequestObjectSigningAlgorithmsSupported() []string { return []string{"RS256"} } -func (o *openidProvider) SupportedUILocales() []language.Tag { +func (o *Provider) SupportedUILocales() []language.Tag { return o.config.SupportedUILocales } -func (o *openidProvider) Storage() Storage { +func (o *Provider) Storage() Storage { return o.storage } -func (o *openidProvider) Decoder() httphelper.Decoder { +func (o *Provider) Decoder() httphelper.Decoder { return o.decoder } -func (o *openidProvider) Encoder() httphelper.Encoder { +func (o *Provider) Encoder() httphelper.Encoder { return o.encoder } -func (o *openidProvider) IDTokenHintVerifier() IDTokenHintVerifier { - if o.idTokenHintVerifier == nil { - o.idTokenHintVerifier = NewIDTokenHintVerifier(o.Issuer(), o.openIDKeySet(), o.idTokenHintVerifierOpts...) - } - return o.idTokenHintVerifier +func (o *Provider) IDTokenHintVerifier(ctx context.Context) IDTokenHintVerifier { + return NewIDTokenHintVerifier(IssuerFromContext(ctx), o.openIDKeySet(), o.idTokenHintVerifierOpts...) } -func (o *openidProvider) JWTProfileVerifier() JWTProfileVerifier { - if o.jwtProfileVerifier == nil { - o.jwtProfileVerifier = NewJWTProfileVerifier(o.Storage(), o.Issuer(), 1*time.Hour, time.Second) - } - return o.jwtProfileVerifier +func (o *Provider) JWTProfileVerifier(ctx context.Context) JWTProfileVerifier { + return NewJWTProfileVerifier(o.Storage(), IssuerFromContext(ctx), 1*time.Hour, time.Second) } -func (o *openidProvider) AccessTokenVerifier() AccessTokenVerifier { - if o.accessTokenVerifier == nil { - o.accessTokenVerifier = NewAccessTokenVerifier(o.Issuer(), o.openIDKeySet(), o.accessTokenVerifierOpts...) - } - return o.accessTokenVerifier +func (o *Provider) AccessTokenVerifier(ctx context.Context) AccessTokenVerifier { + return NewAccessTokenVerifier(IssuerFromContext(ctx), o.openIDKeySet(), o.accessTokenVerifierOpts...) } -func (o *openidProvider) openIDKeySet() oidc.KeySet { +func (o *Provider) openIDKeySet() oidc.KeySet { if o.keySet == nil { o.keySet = &openIDKeySet{o.Storage()} } return o.keySet } -func (o *openidProvider) Crypto() Crypto { +func (o *Provider) Crypto() Crypto { return o.crypto } -func (o *openidProvider) DefaultLogoutRedirectURI() string { +func (o *Provider) DefaultLogoutRedirectURI() string { return o.config.DefaultLogoutRedirectURI } -func (o *openidProvider) Signer() Signer { - return o.signer -} - -func (o *openidProvider) Probes() []ProbesFn { +func (o *Provider) Probes() []ProbesFn { return []ProbesFn{ - ReadySigner(o.Signer()), ReadyStorage(o.Storage()), } } -func (o *openidProvider) HttpHandler() http.Handler { +func (o *Provider) HttpHandler() http.Handler { return o.httpHandler } @@ -372,22 +363,31 @@ type openIDKeySet struct { // VerifySignature implements the oidc.KeySet interface // providing an implementation for the keys stored in the OP Storage interface func (o *openIDKeySet) VerifySignature(ctx context.Context, jws *jose.JSONWebSignature) ([]byte, error) { - keySet, err := o.Storage.GetKeySet(ctx) + keySet, err := o.Storage.KeySet(ctx) if err != nil { return nil, fmt.Errorf("error fetching keys: %w", err) } keyID, alg := oidc.GetKeyIDAndAlg(jws) - key, err := oidc.FindMatchingKey(keyID, oidc.KeyUseSignature, alg, keySet.Keys...) + key, err := oidc.FindMatchingKey(keyID, oidc.KeyUseSignature, alg, jsonWebKeySet(keySet).Keys...) if err != nil { return nil, fmt.Errorf("invalid signature: %w", err) } return jws.Verify(&key) } -type Option func(o *openidProvider) error +type Option func(o *Provider) error + +// WithAllowInsecure allows the use of http (instead of https) for issuers +// this is not recommended for production use and violates the OIDC specification +func WithAllowInsecure() Option { + return func(o *Provider) error { + o.insecure = true + return nil + } +} func WithCustomAuthEndpoint(endpoint Endpoint) Option { - return func(o *openidProvider) error { + return func(o *Provider) error { if err := endpoint.Validate(); err != nil { return err } @@ -397,7 +397,7 @@ func WithCustomAuthEndpoint(endpoint Endpoint) Option { } func WithCustomTokenEndpoint(endpoint Endpoint) Option { - return func(o *openidProvider) error { + return func(o *Provider) error { if err := endpoint.Validate(); err != nil { return err } @@ -407,7 +407,7 @@ func WithCustomTokenEndpoint(endpoint Endpoint) Option { } func WithCustomIntrospectionEndpoint(endpoint Endpoint) Option { - return func(o *openidProvider) error { + return func(o *Provider) error { if err := endpoint.Validate(); err != nil { return err } @@ -417,7 +417,7 @@ func WithCustomIntrospectionEndpoint(endpoint Endpoint) Option { } func WithCustomUserinfoEndpoint(endpoint Endpoint) Option { - return func(o *openidProvider) error { + return func(o *Provider) error { if err := endpoint.Validate(); err != nil { return err } @@ -427,7 +427,7 @@ func WithCustomUserinfoEndpoint(endpoint Endpoint) Option { } func WithCustomRevocationEndpoint(endpoint Endpoint) Option { - return func(o *openidProvider) error { + return func(o *Provider) error { if err := endpoint.Validate(); err != nil { return err } @@ -437,7 +437,7 @@ func WithCustomRevocationEndpoint(endpoint Endpoint) Option { } func WithCustomEndSessionEndpoint(endpoint Endpoint) Option { - return func(o *openidProvider) error { + return func(o *Provider) error { if err := endpoint.Validate(); err != nil { return err } @@ -447,7 +447,7 @@ func WithCustomEndSessionEndpoint(endpoint Endpoint) Option { } func WithCustomKeysEndpoint(endpoint Endpoint) Option { - return func(o *openidProvider) error { + return func(o *Provider) error { if err := endpoint.Validate(); err != nil { return err } @@ -457,7 +457,7 @@ func WithCustomKeysEndpoint(endpoint Endpoint) Option { } func WithCustomEndpoints(auth, token, userInfo, revocation, endSession, keys Endpoint) Option { - return func(o *openidProvider) error { + return func(o *Provider) error { o.endpoints.Authorization = auth o.endpoints.Token = token o.endpoints.Userinfo = userInfo @@ -469,38 +469,32 @@ func WithCustomEndpoints(auth, token, userInfo, revocation, endSession, keys End } func WithHttpInterceptors(interceptors ...HttpInterceptor) Option { - return func(o *openidProvider) error { + return func(o *Provider) error { o.interceptors = append(o.interceptors, interceptors...) return nil } } func WithAccessTokenVerifierOpts(opts ...AccessTokenVerifierOpt) Option { - return func(o *openidProvider) error { + return func(o *Provider) error { o.accessTokenVerifierOpts = opts return nil } } func WithIDTokenHintVerifierOpts(opts ...IDTokenHintVerifierOpt) Option { - return func(o *openidProvider) error { + return func(o *Provider) error { o.idTokenHintVerifierOpts = opts return nil } } -func buildInterceptor(interceptors ...HttpInterceptor) func(http.HandlerFunc) http.Handler { - return func(handlerFunc http.HandlerFunc) http.Handler { - handler := handlerFuncToHandler(handlerFunc) +func intercept(i IssuerFromRequest, interceptors ...HttpInterceptor) func(handler http.Handler) http.Handler { + issuerInterceptor := NewIssuerInterceptor(i) + return func(handler http.Handler) http.Handler { for i := len(interceptors) - 1; i >= 0; i-- { handler = interceptors[i](handler) } - return handler + return cors.New(defaultCORSOptions).Handler(issuerInterceptor.Handler(handler)) } } - -func handlerFuncToHandler(handlerFunc http.HandlerFunc) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - handlerFunc(w, r) - }) -} diff --git a/pkg/op/probes.go b/pkg/op/probes.go index 7b80fb4..a56c92b 100644 --- a/pkg/op/probes.go +++ b/pkg/op/probes.go @@ -5,7 +5,7 @@ import ( "errors" "net/http" - httphelper "github.com/zitadel/oidc/pkg/http" + httphelper "github.com/zitadel/oidc/v2/pkg/http" ) type ProbesFn func(context.Context) error @@ -31,15 +31,6 @@ func Readiness(w http.ResponseWriter, r *http.Request, probes ...ProbesFn) { ok(w) } -func ReadySigner(s Signer) ProbesFn { - return func(ctx context.Context) error { - if s == nil { - return errors.New("no signer") - } - return s.Health(ctx) - } -} - func ReadyStorage(s Storage) ProbesFn { return func(ctx context.Context) error { if s == nil { diff --git a/pkg/op/session.go b/pkg/op/session.go index c4984fc..e1cc595 100644 --- a/pkg/op/session.go +++ b/pkg/op/session.go @@ -5,14 +5,14 @@ import ( "net/http" "net/url" - httphelper "github.com/zitadel/oidc/pkg/http" - "github.com/zitadel/oidc/pkg/oidc" + httphelper "github.com/zitadel/oidc/v2/pkg/http" + "github.com/zitadel/oidc/v2/pkg/oidc" ) type SessionEnder interface { Decoder() httphelper.Decoder Storage() Storage - IDTokenHintVerifier() IDTokenHintVerifier + IDTokenHintVerifier(context.Context) IDTokenHintVerifier DefaultLogoutRedirectURI() string } @@ -59,7 +59,7 @@ func ValidateEndSessionRequest(ctx context.Context, req *oidc.EndSessionRequest, RedirectURI: ender.DefaultLogoutRedirectURI(), } if req.IdTokenHint != "" { - claims, err := VerifyIDTokenHint(ctx, req.IdTokenHint, ender.IDTokenHintVerifier()) + claims, err := VerifyIDTokenHint(ctx, req.IdTokenHint, ender.IDTokenHintVerifier(ctx)) if err != nil { return nil, oidc.ErrInvalidRequest().WithDescription("id_token_hint invalid").WithParent(err) } diff --git a/pkg/op/signer.go b/pkg/op/signer.go index 828876e..22ef8ca 100644 --- a/pkg/op/signer.go +++ b/pkg/op/signer.go @@ -1,88 +1,38 @@ package op import ( - "context" "errors" - "sync" - "github.com/zitadel/logging" "gopkg.in/square/go-jose.v2" ) -type Signer interface { - Health(ctx context.Context) error - Signer() jose.Signer +var ( + ErrSignerCreationFailed = errors.New("signer creation failed") +) + +type SigningKey interface { SignatureAlgorithm() jose.SignatureAlgorithm + Key() interface{} + ID() string } -type tokenSigner struct { - signer jose.Signer - storage AuthStorage - alg jose.SignatureAlgorithm - lock sync.RWMutex -} - -func NewSigner(ctx context.Context, storage AuthStorage, keyCh <-chan jose.SigningKey) Signer { - s := &tokenSigner{ - storage: storage, - } - - select { - case <-ctx.Done(): - return nil - case key := <-keyCh: - s.exchangeSigningKey(key) - } - go s.refreshSigningKey(ctx, keyCh) - - return s -} - -func (s *tokenSigner) Health(_ context.Context) error { - if s.signer == nil { - return errors.New("no signer") - } - if string(s.alg) == "" { - return errors.New("no signing algorithm") - } - return nil -} - -func (s *tokenSigner) Signer() jose.Signer { - s.lock.RLock() - defer s.lock.RUnlock() - return s.signer -} - -func (s *tokenSigner) refreshSigningKey(ctx context.Context, keyCh <-chan jose.SigningKey) { - for { - select { - case <-ctx.Done(): - return - case key := <-keyCh: - s.exchangeSigningKey(key) - } - } -} - -func (s *tokenSigner) exchangeSigningKey(key jose.SigningKey) { - s.lock.Lock() - defer s.lock.Unlock() - 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{}) +func SignerFromKey(key SigningKey) (jose.Signer, error) { + signer, err := jose.NewSigner(jose.SigningKey{ + Algorithm: key.SignatureAlgorithm(), + Key: &jose.JSONWebKey{ + Key: key.Key(), + KeyID: key.ID(), + }, + }, &jose.SignerOptions{}) if err != nil { - logging.New().WithError(err).Error("error creating signer") - return + return nil, ErrSignerCreationFailed //TODO: log / wrap error? } - logging.Info("signer exchanged signing key") + return signer, nil } -func (s *tokenSigner) SignatureAlgorithm() jose.SignatureAlgorithm { - return s.alg +type Key interface { + ID() string + Algorithm() jose.SignatureAlgorithm + Use() string + Key() interface{} } diff --git a/pkg/op/storage.go b/pkg/op/storage.go index 153cd21..b040b72 100644 --- a/pkg/op/storage.go +++ b/pkg/op/storage.go @@ -7,7 +7,7 @@ import ( "gopkg.in/square/go-jose.v2" - "github.com/zitadel/oidc/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/oidc" ) type AuthStorage interface { @@ -47,8 +47,14 @@ type AuthStorage interface { // tokenOrTokenID will be the refresh token, not its ID. RevokeToken(ctx context.Context, tokenOrTokenID string, userID string, clientID string) *oidc.Error - GetSigningKey(context.Context, chan<- jose.SigningKey) - GetKeySet(context.Context) (*jose.JSONWebKeySet, error) + SigningKey(context.Context) (SigningKey, error) + SignatureAlgorithms(context.Context) ([]jose.SignatureAlgorithm, error) + KeySet(context.Context) ([]Key, error) +} + +type ClientCredentialsStorage interface { + ClientCredentials(ctx context.Context, clientID, clientSecret string) (Client, error) + ClientCredentialsTokenRequest(ctx context.Context, clientID string, scopes []string) (TokenRequest, error) } // CanRefreshTokenInfo is an optional additional interface that Storage can support. @@ -62,10 +68,6 @@ type CanRefreshTokenInfo interface { var ErrInvalidRefreshToken = errors.New("invalid_refresh_token") -type ClientCredentialsStorage interface { - ClientCredentialsTokenRequest(ctx context.Context, clientID string, scopes []string) (TokenRequest, error) -} - type OPStorage interface { GetClientByClientID(ctx context.Context, clientID string) (Client, error) AuthorizeClientIDSecret(ctx context.Context, clientID, clientSecret string) error @@ -80,6 +82,12 @@ type OPStorage interface { ValidateJWTProfileScopes(ctx context.Context, userID string, scopes []string) ([]string, error) } +// JWTProfileTokenStorage is an additional, optional storage to implement +// implementing it, allows specifying the [AccessTokenType] of the access_token returned form the JWT Profile TokenRequest +type JWTProfileTokenStorage interface { + JWTProfileTokenType(ctx context.Context, request TokenRequest) (AccessTokenType, error) +} + // Storage is a required parameter for NewOpenIDProvider(). In addition to the // embedded interfaces below, if the passed Storage implements ClientCredentialsStorage // then the grant type "client_credentials" will be supported. In that case, the access diff --git a/pkg/op/token.go b/pkg/op/token.go index 3a72261..4d3e620 100644 --- a/pkg/op/token.go +++ b/pkg/op/token.go @@ -4,14 +4,12 @@ import ( "context" "time" - "github.com/zitadel/oidc/pkg/crypto" - "github.com/zitadel/oidc/pkg/oidc" - "github.com/zitadel/oidc/pkg/strings" + "github.com/zitadel/oidc/v2/pkg/crypto" + "github.com/zitadel/oidc/v2/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/strings" ) type TokenCreator interface { - Issuer() string - Signer() Signer Storage() Storage Crypto() Crypto } @@ -22,6 +20,13 @@ type TokenRequest interface { GetScopes() []string } +type AccessTokenClient interface { + GetID() string + ClockSkew() time.Duration + RestrictAdditionalAccessTokenScopes() func(scopes []string) []string + GrantTypes() []oidc.GrantType +} + func CreateTokenResponse(ctx context.Context, request IDTokenRequest, client Client, creator TokenCreator, createAccessToken bool, code, refreshToken string) (*oidc.AccessTokenResponse, error) { var accessToken, newRefreshToken string var validity time.Duration @@ -32,7 +37,7 @@ func CreateTokenResponse(ctx context.Context, request IDTokenRequest, client Cli return nil, err } } - idToken, err := CreateIDToken(ctx, creator.Issuer(), request, client.IDTokenLifetime(), accessToken, code, creator.Storage(), creator.Signer(), client) + idToken, err := CreateIDToken(ctx, IssuerFromContext(ctx), request, client.IDTokenLifetime(), accessToken, code, creator.Storage(), client) if err != nil { return nil, err } @@ -57,7 +62,7 @@ func CreateTokenResponse(ctx context.Context, request IDTokenRequest, client Cli }, nil } -func createTokens(ctx context.Context, tokenRequest TokenRequest, storage Storage, refreshToken string, client Client) (id, newRefreshToken string, exp time.Time, err error) { +func createTokens(ctx context.Context, tokenRequest TokenRequest, storage Storage, refreshToken string, client AccessTokenClient) (id, newRefreshToken string, exp time.Time, err error) { if needsRefreshToken(tokenRequest, client) { return storage.CreateAccessAndRefreshTokens(ctx, tokenRequest, refreshToken) } @@ -65,7 +70,7 @@ func createTokens(ctx context.Context, tokenRequest TokenRequest, storage Storag return } -func needsRefreshToken(tokenRequest TokenRequest, client Client) bool { +func needsRefreshToken(tokenRequest TokenRequest, client AccessTokenClient) bool { switch req := tokenRequest.(type) { case AuthRequest: return strings.Contains(req.GetScopes(), oidc.ScopeOfflineAccess) && req.GetResponseType() == oidc.ResponseTypeCode && ValidateGrantType(client, oidc.GrantTypeRefreshToken) @@ -76,7 +81,7 @@ func needsRefreshToken(tokenRequest TokenRequest, client Client) bool { } } -func CreateAccessToken(ctx context.Context, tokenRequest TokenRequest, accessTokenType AccessTokenType, creator TokenCreator, client Client, refreshToken string) (accessToken, newRefreshToken string, validity time.Duration, err error) { +func CreateAccessToken(ctx context.Context, tokenRequest TokenRequest, accessTokenType AccessTokenType, creator TokenCreator, client AccessTokenClient, refreshToken string) (accessToken, newRefreshToken string, validity time.Duration, err error) { id, newRefreshToken, exp, err := createTokens(ctx, tokenRequest, creator.Storage(), refreshToken, client) if err != nil { return "", "", 0, err @@ -87,7 +92,7 @@ func CreateAccessToken(ctx context.Context, tokenRequest TokenRequest, accessTok } validity = exp.Add(clockSkew).Sub(time.Now().UTC()) if accessTokenType == AccessTokenTypeJWT { - accessToken, err = CreateJWT(ctx, creator.Issuer(), tokenRequest, exp, id, creator.Signer(), client, creator.Storage()) + accessToken, err = CreateJWT(ctx, IssuerFromContext(ctx), tokenRequest, exp, id, client, creator.Storage()) return } accessToken, err = CreateBearerToken(id, tokenRequest.GetSubject(), creator.Crypto()) @@ -98,7 +103,7 @@ func CreateBearerToken(tokenID, subject string, crypto Crypto) (string, error) { return crypto.Encrypt(tokenID + ":" + subject) } -func CreateJWT(ctx context.Context, issuer string, tokenRequest TokenRequest, exp time.Time, id string, signer Signer, client Client, storage Storage) (string, error) { +func CreateJWT(ctx context.Context, issuer string, tokenRequest TokenRequest, exp time.Time, id string, client AccessTokenClient, storage Storage) (string, error) { claims := oidc.NewAccessTokenClaims(issuer, tokenRequest.GetSubject(), tokenRequest.GetAudience(), exp, id, client.GetID(), client.ClockSkew()) if client != nil { restrictedScopes := client.RestrictAdditionalAccessTokenScopes()(tokenRequest.GetScopes()) @@ -108,7 +113,15 @@ func CreateJWT(ctx context.Context, issuer string, tokenRequest TokenRequest, ex } claims.SetPrivateClaims(privateClaims) } - return crypto.Sign(claims, signer.Signer()) + signingKey, err := storage.SigningKey(ctx) + if err != nil { + return "", err + } + signer, err := SignerFromKey(signingKey) + if err != nil { + return "", err + } + return crypto.Sign(claims, signer) } type IDTokenRequest interface { @@ -120,7 +133,7 @@ type IDTokenRequest interface { GetSubject() string } -func CreateIDToken(ctx context.Context, issuer string, request IDTokenRequest, validity time.Duration, accessToken, code string, storage Storage, signer Signer, client Client) (string, error) { +func CreateIDToken(ctx context.Context, issuer string, request IDTokenRequest, validity time.Duration, accessToken, code string, storage Storage, client Client) (string, error) { exp := time.Now().UTC().Add(client.ClockSkew()).Add(validity) var acr, nonce string if authRequest, ok := request.(AuthRequest); ok { @@ -129,8 +142,12 @@ func CreateIDToken(ctx context.Context, issuer string, request IDTokenRequest, v } claims := oidc.NewIDTokenClaims(issuer, request.GetSubject(), request.GetAudience(), exp, request.GetAuthTime(), nonce, acr, request.GetAMR(), request.GetClientID(), client.ClockSkew()) scopes := client.RestrictAdditionalIdTokenScopes()(request.GetScopes()) + signingKey, err := storage.SigningKey(ctx) + if err != nil { + return "", err + } if accessToken != "" { - atHash, err := oidc.ClaimHash(accessToken, signer.SignatureAlgorithm()) + atHash, err := oidc.ClaimHash(accessToken, signingKey.SignatureAlgorithm()) if err != nil { return "", err } @@ -148,14 +165,17 @@ func CreateIDToken(ctx context.Context, issuer string, request IDTokenRequest, v claims.SetUserinfo(userInfo) } if code != "" { - codeHash, err := oidc.ClaimHash(code, signer.SignatureAlgorithm()) + codeHash, err := oidc.ClaimHash(code, signingKey.SignatureAlgorithm()) if err != nil { return "", err } claims.SetCodeHash(codeHash) } - - return crypto.Sign(claims, signer.Signer()) + signer, err := SignerFromKey(signingKey) + if err != nil { + return "", err + } + return crypto.Sign(claims, signer) } func removeUserinfoScopes(scopes []string) []string { diff --git a/pkg/op/token_client_credentials.go b/pkg/op/token_client_credentials.go index 3787667..fc31d57 100644 --- a/pkg/op/token_client_credentials.go +++ b/pkg/op/token_client_credentials.go @@ -5,8 +5,8 @@ import ( "net/http" "net/url" - httphelper "github.com/zitadel/oidc/pkg/http" - "github.com/zitadel/oidc/pkg/oidc" + httphelper "github.com/zitadel/oidc/v2/pkg/http" + "github.com/zitadel/oidc/v2/pkg/oidc" ) // ClientCredentialsExchange handles the OAuth 2.0 client_credentials grant, including @@ -63,15 +63,15 @@ func ParseClientCredentialsRequest(r *http.Request, decoder httphelper.Decoder) return request, nil } -// ValidateClientCredentialsRequest validates the refresh_token request parameters including authorization check of the client -// and returns the data representing the original auth request corresponding to the refresh_token +// ValidateClientCredentialsRequest validates the client_credentials request parameters including authorization check of the client +// and returns a TokenRequest and Client implementation to be used in the client_credentials response, resp. creation of the corresponding access_token. func ValidateClientCredentialsRequest(ctx context.Context, request *oidc.ClientCredentialsRequest, exchanger Exchanger) (TokenRequest, Client, error) { storage, ok := exchanger.Storage().(ClientCredentialsStorage) if !ok { return nil, nil, oidc.ErrUnsupportedGrantType().WithDescription("client_credentials grant not supported") } - client, err := AuthorizeClientCredentialsClient(ctx, request, exchanger) + client, err := AuthorizeClientCredentialsClient(ctx, request, storage) if err != nil { return nil, nil, err } @@ -84,12 +84,8 @@ func ValidateClientCredentialsRequest(ctx context.Context, request *oidc.ClientC return tokenRequest, client, nil } -func AuthorizeClientCredentialsClient(ctx context.Context, request *oidc.ClientCredentialsRequest, exchanger Exchanger) (Client, error) { - if err := AuthorizeClientIDSecret(ctx, request.ClientID, request.ClientSecret, exchanger.Storage()); err != nil { - return nil, err - } - - client, err := exchanger.Storage().GetClientByClientID(ctx, request.ClientID) +func AuthorizeClientCredentialsClient(ctx context.Context, request *oidc.ClientCredentialsRequest, storage ClientCredentialsStorage) (Client, error) { + client, err := storage.ClientCredentials(ctx, request.ClientID, request.ClientSecret) if err != nil { return nil, oidc.ErrInvalidClient().WithParent(err) } @@ -102,7 +98,7 @@ func AuthorizeClientCredentialsClient(ctx context.Context, request *oidc.ClientC } func CreateClientCredentialsTokenResponse(ctx context.Context, tokenRequest TokenRequest, creator TokenCreator, client Client) (*oidc.AccessTokenResponse, error) { - accessToken, _, validity, err := CreateAccessToken(ctx, tokenRequest, AccessTokenTypeJWT, creator, client, "") + accessToken, _, validity, err := CreateAccessToken(ctx, tokenRequest, client.AccessTokenType(), creator, client, "") if err != nil { return nil, err } diff --git a/pkg/op/token_code.go b/pkg/op/token_code.go index ec48233..565a477 100644 --- a/pkg/op/token_code.go +++ b/pkg/op/token_code.go @@ -4,8 +4,8 @@ import ( "context" "net/http" - httphelper "github.com/zitadel/oidc/pkg/http" - "github.com/zitadel/oidc/pkg/oidc" + httphelper "github.com/zitadel/oidc/v2/pkg/http" + "github.com/zitadel/oidc/v2/pkg/oidc" ) // CodeExchange handles the OAuth 2.0 authorization_code grant, including diff --git a/pkg/op/token_intospection.go b/pkg/op/token_intospection.go index f402c8b..dfc8954 100644 --- a/pkg/op/token_intospection.go +++ b/pkg/op/token_intospection.go @@ -1,24 +1,25 @@ package op import ( + "context" "errors" "net/http" "net/url" - httphelper "github.com/zitadel/oidc/pkg/http" - "github.com/zitadel/oidc/pkg/oidc" + httphelper "github.com/zitadel/oidc/v2/pkg/http" + "github.com/zitadel/oidc/v2/pkg/oidc" ) type Introspector interface { Decoder() httphelper.Decoder Crypto() Crypto Storage() Storage - AccessTokenVerifier() AccessTokenVerifier + AccessTokenVerifier(context.Context) AccessTokenVerifier } type IntrospectorJWTProfile interface { Introspector - JWTProfileVerifier() JWTProfileVerifier + JWTProfileVerifier(context.Context) JWTProfileVerifier } func introspectionHandler(introspector Introspector) func(http.ResponseWriter, *http.Request) { @@ -62,7 +63,7 @@ func ParseTokenIntrospectionRequest(r *http.Request, introspector Introspector) return "", "", errors.New("unable to parse request") } if introspectorJWTProfile, ok := introspector.(IntrospectorJWTProfile); ok && req.ClientAssertion != "" { - profile, err := VerifyJWTAssertion(r.Context(), req.ClientAssertion, introspectorJWTProfile.JWTProfileVerifier()) + profile, err := VerifyJWTAssertion(r.Context(), req.ClientAssertion, introspectorJWTProfile.JWTProfileVerifier(r.Context())) if err == nil { return req.Token, profile.Issuer, nil } diff --git a/pkg/op/token_jwt_profile.go b/pkg/op/token_jwt_profile.go index eb21517..23bac9a 100644 --- a/pkg/op/token_jwt_profile.go +++ b/pkg/op/token_jwt_profile.go @@ -5,13 +5,13 @@ import ( "net/http" "time" - httphelper "github.com/zitadel/oidc/pkg/http" - "github.com/zitadel/oidc/pkg/oidc" + httphelper "github.com/zitadel/oidc/v2/pkg/http" + "github.com/zitadel/oidc/v2/pkg/oidc" ) type JWTAuthorizationGrantExchanger interface { Exchanger - JWTProfileVerifier() JWTProfileVerifier + JWTProfileVerifier(context.Context) JWTProfileVerifier } // JWTProfile handles the OAuth 2.0 JWT Profile Authorization Grant https://tools.ietf.org/html/rfc7523#section-2.1 @@ -21,7 +21,7 @@ func JWTProfile(w http.ResponseWriter, r *http.Request, exchanger JWTAuthorizati RequestError(w, r, err) } - tokenRequest, err := VerifyJWTAssertion(r.Context(), profileRequest.Assertion, exchanger.JWTProfileVerifier()) + tokenRequest, err := VerifyJWTAssertion(r.Context(), profileRequest.Assertion, exchanger.JWTProfileVerifier(r.Context())) if err != nil { RequestError(w, r, err) return @@ -53,27 +53,65 @@ func ParseJWTProfileGrantRequest(r *http.Request, decoder httphelper.Decoder) (* return tokenReq, nil } -// CreateJWTTokenResponse creates +// CreateJWTTokenResponse creates an access_token response for a JWT Profile Grant request +// by default the access_token is an opaque string, but can be specified by implementing the JWTProfileTokenStorage interface func CreateJWTTokenResponse(ctx context.Context, tokenRequest TokenRequest, creator TokenCreator) (*oidc.AccessTokenResponse, error) { - id, exp, err := creator.Storage().CreateAccessToken(ctx, tokenRequest) - if err != nil { - return nil, err - } - accessToken, err := CreateBearerToken(id, tokenRequest.GetSubject(), creator.Crypto()) - if err != nil { - return nil, err + // return an opaque token as default to not break current implementations + tokenType := AccessTokenTypeBearer + + // the current CreateAccessToken function, esp. CreateJWT requires an implementation of an AccessTokenClient + client := &jwtProfileClient{ + id: tokenRequest.GetSubject(), } + // by implementing the JWTProfileTokenStorage the storage can specify the AccessTokenType to be returned + tokenStorage, ok := creator.Storage().(JWTProfileTokenStorage) + if ok { + var err error + tokenType, err = tokenStorage.JWTProfileTokenType(ctx, tokenRequest) + if err != nil { + return nil, err + } + } + + accessToken, _, validity, err := CreateAccessToken(ctx, tokenRequest, tokenType, creator, client, "") + if err != nil { + return nil, err + } return &oidc.AccessTokenResponse{ AccessToken: accessToken, TokenType: oidc.BearerToken, - ExpiresIn: uint64(exp.Sub(time.Now().UTC()).Seconds()), + ExpiresIn: uint64(validity.Seconds()), }, nil } // ParseJWTProfileRequest has been renamed to ParseJWTProfileGrantRequest // -//deprecated: use ParseJWTProfileGrantRequest +// deprecated: use ParseJWTProfileGrantRequest func ParseJWTProfileRequest(r *http.Request, decoder httphelper.Decoder) (*oidc.JWTProfileGrantRequest, error) { return ParseJWTProfileGrantRequest(r, decoder) } + +type jwtProfileClient struct { + id string +} + +func (j *jwtProfileClient) GetID() string { + return j.id +} + +func (j *jwtProfileClient) ClockSkew() time.Duration { + return 0 +} + +func (j *jwtProfileClient) RestrictAdditionalAccessTokenScopes() func(scopes []string) []string { + return func(scopes []string) []string { + return scopes + } +} + +func (j *jwtProfileClient) GrantTypes() []oidc.GrantType { + return []oidc.GrantType{ + oidc.GrantTypeBearer, + } +} diff --git a/pkg/op/token_refresh.go b/pkg/op/token_refresh.go index 7251eeb..148d2a4 100644 --- a/pkg/op/token_refresh.go +++ b/pkg/op/token_refresh.go @@ -6,9 +6,9 @@ import ( "net/http" "time" - httphelper "github.com/zitadel/oidc/pkg/http" - "github.com/zitadel/oidc/pkg/oidc" - "github.com/zitadel/oidc/pkg/strings" + httphelper "github.com/zitadel/oidc/v2/pkg/http" + "github.com/zitadel/oidc/v2/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/strings" ) type RefreshTokenRequest interface { diff --git a/pkg/op/token_request.go b/pkg/op/token_request.go index 6ccd489..190e812 100644 --- a/pkg/op/token_request.go +++ b/pkg/op/token_request.go @@ -5,15 +5,13 @@ import ( "net/http" "net/url" - httphelper "github.com/zitadel/oidc/pkg/http" - "github.com/zitadel/oidc/pkg/oidc" + httphelper "github.com/zitadel/oidc/v2/pkg/http" + "github.com/zitadel/oidc/v2/pkg/oidc" ) type Exchanger interface { - Issuer() string Storage() Storage Decoder() httphelper.Decoder - Signer() Signer Crypto() Crypto AuthMethodPostSupported() bool AuthMethodPrivateKeyJWTSupported() bool @@ -122,7 +120,7 @@ func AuthorizeCodeChallenge(tokenReq *oidc.AccessTokenRequest, challenge *oidc.C // AuthorizePrivateJWTKey authorizes a client by validating the client_assertion's signature with a previously // registered public key (JWT Profile) func AuthorizePrivateJWTKey(ctx context.Context, clientAssertion string, exchanger JWTAuthorizationGrantExchanger) (Client, error) { - jwtReq, err := VerifyJWTAssertion(ctx, clientAssertion, exchanger.JWTProfileVerifier()) + jwtReq, err := VerifyJWTAssertion(ctx, clientAssertion, exchanger.JWTProfileVerifier(ctx)) if err != nil { return nil, err } @@ -136,8 +134,8 @@ func AuthorizePrivateJWTKey(ctx context.Context, clientAssertion string, exchang return client, nil } -// ValidateGrantType ensures that the requested grant_type is allowed by the Client -func ValidateGrantType(client Client, grantType oidc.GrantType) bool { +// ValidateGrantType ensures that the requested grant_type is allowed by the client +func ValidateGrantType(client interface{ GrantTypes() []oidc.GrantType }, grantType oidc.GrantType) bool { if client == nil { return false } diff --git a/pkg/op/token_revocation.go b/pkg/op/token_revocation.go index 9dd0295..7dbd4a7 100644 --- a/pkg/op/token_revocation.go +++ b/pkg/op/token_revocation.go @@ -7,22 +7,22 @@ import ( "net/url" "strings" - httphelper "github.com/zitadel/oidc/pkg/http" - "github.com/zitadel/oidc/pkg/oidc" + httphelper "github.com/zitadel/oidc/v2/pkg/http" + "github.com/zitadel/oidc/v2/pkg/oidc" ) type Revoker interface { Decoder() httphelper.Decoder Crypto() Crypto Storage() Storage - AccessTokenVerifier() AccessTokenVerifier + AccessTokenVerifier(context.Context) AccessTokenVerifier AuthMethodPrivateKeyJWTSupported() bool AuthMethodPostSupported() bool } type RevokerJWTProfile interface { Revoker - JWTProfileVerifier() JWTProfileVerifier + JWTProfileVerifier(context.Context) JWTProfileVerifier } func revocationHandler(revoker Revoker) func(http.ResponseWriter, *http.Request) { @@ -87,7 +87,7 @@ func ParseTokenRevocationRequest(r *http.Request, revoker Revoker) (token, token if !ok || !revoker.AuthMethodPrivateKeyJWTSupported() { return "", "", "", oidc.ErrInvalidClient().WithDescription("auth_method private_key_jwt not supported") } - profile, err := VerifyJWTAssertion(r.Context(), req.ClientAssertion, revokerJWTProfile.JWTProfileVerifier()) + profile, err := VerifyJWTAssertion(r.Context(), req.ClientAssertion, revokerJWTProfile.JWTProfileVerifier(r.Context())) if err == nil { return req.Token, req.TokenTypeHint, profile.Issuer, nil } @@ -151,7 +151,7 @@ func getTokenIDAndSubjectForRevocation(ctx context.Context, userinfoProvider Use } return splitToken[0], splitToken[1], true } - accessTokenClaims, err := VerifyAccessToken(ctx, accessToken, userinfoProvider.AccessTokenVerifier()) + accessTokenClaims, err := VerifyAccessToken(ctx, accessToken, userinfoProvider.AccessTokenVerifier(ctx)) if err != nil { return "", "", false } diff --git a/pkg/op/userinfo.go b/pkg/op/userinfo.go index 4bd03e2..cb8f0ae 100644 --- a/pkg/op/userinfo.go +++ b/pkg/op/userinfo.go @@ -6,15 +6,15 @@ import ( "net/http" "strings" - httphelper "github.com/zitadel/oidc/pkg/http" - "github.com/zitadel/oidc/pkg/oidc" + httphelper "github.com/zitadel/oidc/v2/pkg/http" + "github.com/zitadel/oidc/v2/pkg/oidc" ) type UserinfoProvider interface { Decoder() httphelper.Decoder Crypto() Crypto Storage() Storage - AccessTokenVerifier() AccessTokenVerifier + AccessTokenVerifier(context.Context) AccessTokenVerifier } func userinfoHandler(userinfoProvider UserinfoProvider) func(http.ResponseWriter, *http.Request) { @@ -81,7 +81,7 @@ func getTokenIDAndSubject(ctx context.Context, userinfoProvider UserinfoProvider } return splitToken[0], splitToken[1], true } - accessTokenClaims, err := VerifyAccessToken(ctx, accessToken, userinfoProvider.AccessTokenVerifier()) + accessTokenClaims, err := VerifyAccessToken(ctx, accessToken, userinfoProvider.AccessTokenVerifier(ctx)) if err != nil { return "", "", false } diff --git a/pkg/op/verifier_access_token.go b/pkg/op/verifier_access_token.go index 1729c23..1d53adb 100644 --- a/pkg/op/verifier_access_token.go +++ b/pkg/op/verifier_access_token.go @@ -4,7 +4,7 @@ import ( "context" "time" - "github.com/zitadel/oidc/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/oidc" ) type AccessTokenVerifier interface { diff --git a/pkg/op/verifier_id_token_hint.go b/pkg/op/verifier_id_token_hint.go index d36bbd8..9320106 100644 --- a/pkg/op/verifier_id_token_hint.go +++ b/pkg/op/verifier_id_token_hint.go @@ -4,7 +4,7 @@ import ( "context" "time" - "github.com/zitadel/oidc/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/oidc" ) type IDTokenHintVerifier interface { @@ -73,7 +73,7 @@ func NewIDTokenHintVerifier(issuer string, keySet oidc.KeySet, opts ...IDTokenHi } // VerifyIDTokenHint validates the id token according to -//https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation +// https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation func VerifyIDTokenHint(ctx context.Context, token string, v IDTokenHintVerifier) (oidc.IDTokenClaims, error) { claims := oidc.EmptyIDTokenClaims() diff --git a/pkg/op/verifier_jwt_profile.go b/pkg/op/verifier_jwt_profile.go index 0215e84..9befb64 100644 --- a/pkg/op/verifier_jwt_profile.go +++ b/pkg/op/verifier_jwt_profile.go @@ -8,7 +8,7 @@ import ( "gopkg.in/square/go-jose.v2" - "github.com/zitadel/oidc/pkg/oidc" + "github.com/zitadel/oidc/v2/pkg/oidc" ) type JWTProfileVerifier interface {