Merge branch 'next' into fix/acrvalues-format
This commit is contained in:
commit
95e2b7ec6c
90 changed files with 1300 additions and 691 deletions
|
@ -44,9 +44,9 @@ 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/v2/example/server
|
||||
go run github.com/zitadel/oidc/v3/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/v2/example/client/app
|
||||
CLIENT_ID=web CLIENT_SECRET=secret ISSUER=http://localhost:9998/ SCOPES="openid profile" PORT=9999 go run github.com/zitadel/oidc/v3/example/client/app
|
||||
```
|
||||
|
||||
- open http://localhost:9999/login in your browser
|
||||
|
@ -56,11 +56,11 @@ CLIENT_ID=web CLIENT_SECRET=secret ISSUER=http://localhost:9998/ SCOPES="openid
|
|||
|
||||
for the dynamic issuer, just start it with:
|
||||
```bash
|
||||
go run github.com/zitadel/oidc/v2/example/server/dynamic
|
||||
go run github.com/zitadel/oidc/v3/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
|
||||
CLIENT_ID=web CLIENT_SECRET=secret ISSUER=http://oidc.local:9998/ SCOPES="openid profile" PORT=9999 go run github.com/zitadel/oidc/v3/example/client/app
|
||||
```
|
||||
|
||||
> Note: Usernames are suffixed with the hostname (`test-user@localhost` or `test-user@oidc.local`)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
|
@ -9,11 +10,11 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/client/rs"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/client/rs"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -27,12 +28,12 @@ func main() {
|
|||
port := os.Getenv("PORT")
|
||||
issuer := os.Getenv("ISSUER")
|
||||
|
||||
provider, err := rs.NewResourceServerFromKeyFile(issuer, keyPath)
|
||||
provider, err := rs.NewResourceServerFromKeyFile(context.TODO(), issuer, keyPath)
|
||||
if err != nil {
|
||||
logrus.Fatalf("error creating provider %s", err.Error())
|
||||
}
|
||||
|
||||
router := mux.NewRouter()
|
||||
router := chi.NewRouter()
|
||||
|
||||
// public url accessible without any authorization
|
||||
// will print `OK` and current timestamp
|
||||
|
@ -73,9 +74,9 @@ func main() {
|
|||
http.Error(w, err.Error(), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
params := mux.Vars(r)
|
||||
requestedClaim := params["claim"]
|
||||
requestedValue := params["value"]
|
||||
requestedClaim := chi.URLParam(r, "claim")
|
||||
requestedValue := chi.URLParam(r, "value")
|
||||
|
||||
value, ok := resp.Claims[requestedClaim].(string)
|
||||
if !ok || value == "" || value != requestedValue {
|
||||
http.Error(w, "claim does not match", http.StatusForbidden)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
@ -11,9 +12,9 @@ import (
|
|||
"github.com/google/uuid"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/client/rp"
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/client/rp"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -43,7 +44,7 @@ func main() {
|
|||
options = append(options, rp.WithJWTProfile(rp.SignerFromKeyPath(keyPath)))
|
||||
}
|
||||
|
||||
provider, err := rp.NewRelyingPartyOIDC(issuer, clientID, clientSecret, redirectURI, scopes, options...)
|
||||
provider, err := rp.NewRelyingPartyOIDC(context.TODO(), issuer, clientID, clientSecret, redirectURI, scopes, options...)
|
||||
if err != nil {
|
||||
logrus.Fatalf("error creating provider %s", err.Error())
|
||||
}
|
||||
|
|
|
@ -11,8 +11,8 @@ import (
|
|||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/client/rp"
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/client/rp"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -39,13 +39,13 @@ func main() {
|
|||
options = append(options, rp.WithJWTProfile(rp.SignerFromKeyPath(keyPath)))
|
||||
}
|
||||
|
||||
provider, err := rp.NewRelyingPartyOIDC(issuer, clientID, clientSecret, "", scopes, options...)
|
||||
provider, err := rp.NewRelyingPartyOIDC(ctx, issuer, clientID, clientSecret, "", scopes, options...)
|
||||
if err != nil {
|
||||
logrus.Fatalf("error creating provider %s", err.Error())
|
||||
}
|
||||
|
||||
logrus.Info("starting device authorization flow")
|
||||
resp, err := rp.DeviceAuthorization(scopes, provider)
|
||||
resp, err := rp.DeviceAuthorization(ctx, scopes, provider, nil)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -10,10 +10,10 @@ import (
|
|||
"golang.org/x/oauth2"
|
||||
githubOAuth "golang.org/x/oauth2/github"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/client/rp"
|
||||
"github.com/zitadel/oidc/v2/pkg/client/rp/cli"
|
||||
"github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/client/rp"
|
||||
"github.com/zitadel/oidc/v3/pkg/client/rp/cli"
|
||||
"github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/client/profile"
|
||||
"github.com/zitadel/oidc/v3/pkg/client/profile"
|
||||
)
|
||||
|
||||
var client = http.DefaultClient
|
||||
|
@ -25,7 +25,7 @@ func main() {
|
|||
scopes := strings.Split(os.Getenv("SCOPES"), " ")
|
||||
|
||||
if keyPath != "" {
|
||||
ts, err := profile.NewJWTProfileTokenSourceFromKeyFile(issuer, keyPath, scopes)
|
||||
ts, err := profile.NewJWTProfileTokenSourceFromKeyFile(context.TODO(), issuer, keyPath, scopes)
|
||||
if err != nil {
|
||||
logrus.Fatalf("error creating token source %s", err.Error())
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ func main() {
|
|||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
ts, err := profile.NewJWTProfileTokenSourceFromKeyFileData(issuer, key, scopes)
|
||||
ts, err := profile.NewJWTProfileTokenSourceFromKeyFileData(context.TODO(), issuer, key, scopes)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
|
|
@ -6,9 +6,9 @@ import (
|
|||
"html/template"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/go-chi/chi"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/op"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -43,7 +43,7 @@ var (
|
|||
|
||||
type login struct {
|
||||
authenticate authenticate
|
||||
router *mux.Router
|
||||
router chi.Router
|
||||
callback func(context.Context, string) string
|
||||
}
|
||||
|
||||
|
@ -57,9 +57,9 @@ func NewLogin(authenticate authenticate, callback func(context.Context, string)
|
|||
}
|
||||
|
||||
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))
|
||||
l.router = chi.NewRouter()
|
||||
l.router.Get("/username", l.loginHandler)
|
||||
l.router.With(issuerInterceptor.Handler).Post("/username", l.checkLoginHandler)
|
||||
}
|
||||
|
||||
type authenticate interface {
|
||||
|
|
|
@ -7,11 +7,11 @@ import (
|
|||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/go-chi/chi"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/oidc/v2/example/server/storage"
|
||||
"github.com/zitadel/oidc/v2/pkg/op"
|
||||
"github.com/zitadel/oidc/v3/example/server/storage"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -47,7 +47,7 @@ func main() {
|
|||
//be sure to create a proper crypto random key and manage it securely!
|
||||
key := sha256.Sum256([]byte("test"))
|
||||
|
||||
router := mux.NewRouter()
|
||||
router := chi.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) {
|
||||
|
@ -76,7 +76,7 @@ func main() {
|
|||
|
||||
//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))
|
||||
router.Mount("/login/", 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
|
||||
|
@ -84,7 +84,7 @@ func main() {
|
|||
//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())
|
||||
router.Mount("/", provider)
|
||||
|
||||
server := &http.Server{
|
||||
Addr: ":" + port,
|
||||
|
|
|
@ -7,10 +7,10 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/gorilla/securecookie"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/zitadel/oidc/v2/pkg/op"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
)
|
||||
|
||||
type deviceAuthenticate interface {
|
||||
|
@ -23,14 +23,14 @@ type deviceLogin struct {
|
|||
cookie *securecookie.SecureCookie
|
||||
}
|
||||
|
||||
func registerDeviceAuth(storage deviceAuthenticate, router *mux.Router) {
|
||||
func registerDeviceAuth(storage deviceAuthenticate, router chi.Router) {
|
||||
l := &deviceLogin{
|
||||
storage: storage,
|
||||
cookie: securecookie.New(securecookie.GenerateRandomKey(32), nil),
|
||||
}
|
||||
|
||||
router.HandleFunc("", l.userCodeHandler)
|
||||
router.Path("/login").Methods(http.MethodPost).HandlerFunc(l.loginHandler)
|
||||
router.HandleFunc("/", l.userCodeHandler)
|
||||
router.Post("/login", l.loginHandler)
|
||||
router.HandleFunc("/confirm", l.confirmHandler)
|
||||
}
|
||||
|
||||
|
|
|
@ -5,12 +5,12 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
type login struct {
|
||||
authenticate authenticate
|
||||
router *mux.Router
|
||||
router chi.Router
|
||||
callback func(context.Context, string) string
|
||||
}
|
||||
|
||||
|
@ -24,9 +24,9 @@ func NewLogin(authenticate authenticate, callback func(context.Context, string)
|
|||
}
|
||||
|
||||
func (l *login) createRouter() {
|
||||
l.router = mux.NewRouter()
|
||||
l.router.Path("/username").Methods("GET").HandlerFunc(l.loginHandler)
|
||||
l.router.Path("/username").Methods("POST").HandlerFunc(l.checkLoginHandler)
|
||||
l.router = chi.NewRouter()
|
||||
l.router.Get("/username", l.loginHandler)
|
||||
l.router.Post("/username", l.checkLoginHandler)
|
||||
}
|
||||
|
||||
type authenticate interface {
|
||||
|
|
|
@ -6,11 +6,11 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/go-chi/chi"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/oidc/v2/example/server/storage"
|
||||
"github.com/zitadel/oidc/v2/pkg/op"
|
||||
"github.com/zitadel/oidc/v3/example/server/storage"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -34,12 +34,12 @@ type Storage interface {
|
|||
// SetupServer creates an OIDC server with Issuer=http://localhost:<port>
|
||||
//
|
||||
// Use one of the pre-made clients in storage/clients.go or register a new one.
|
||||
func SetupServer(issuer string, storage Storage) *mux.Router {
|
||||
func SetupServer(issuer string, storage Storage) chi.Router {
|
||||
// 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()
|
||||
router := chi.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) {
|
||||
|
@ -61,17 +61,18 @@ func SetupServer(issuer string, storage Storage) *mux.Router {
|
|||
|
||||
// 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))
|
||||
router.Mount("/login/", http.StripPrefix("/login", l.router))
|
||||
|
||||
router.PathPrefix("/device").Subrouter()
|
||||
registerDeviceAuth(storage, router.PathPrefix("/device").Subrouter())
|
||||
router.Route("/device", func(r chi.Router) {
|
||||
registerDeviceAuth(storage, r)
|
||||
})
|
||||
|
||||
// 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("/").Handler(provider.HttpHandler())
|
||||
router.Mount("/", provider)
|
||||
|
||||
return router
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ import (
|
|||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/zitadel/oidc/v2/example/server/exampleop"
|
||||
"github.com/zitadel/oidc/v2/example/server/storage"
|
||||
"github.com/zitadel/oidc/v3/example/server/exampleop"
|
||||
"github.com/zitadel/oidc/v3/example/server/storage"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
|
|
@ -3,8 +3,8 @@ package storage
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v2/pkg/op"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -5,8 +5,8 @@ import (
|
|||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v2/pkg/op"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -14,8 +14,8 @@ import (
|
|||
"github.com/google/uuid"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v2/pkg/op"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
)
|
||||
|
||||
// serviceKey1 is a public key which will be used for the JWT Profile Authorization Grant
|
||||
|
|
|
@ -6,8 +6,8 @@ import (
|
|||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v2/pkg/op"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
)
|
||||
|
||||
type multiStorage struct {
|
||||
|
|
8
go.mod
8
go.mod
|
@ -1,13 +1,12 @@
|
|||
module github.com/zitadel/oidc/v2
|
||||
module github.com/zitadel/oidc/v3
|
||||
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/go-chi/chi v1.5.4
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/google/go-github/v31 v31.0.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gorilla/schema v1.2.0
|
||||
github.com/gorilla/securecookie v1.1.1
|
||||
github.com/jeremija/gosubmit v0.2.7
|
||||
github.com/muhlemmer/gu v0.3.1
|
||||
|
@ -16,6 +15,9 @@ require (
|
|||
github.com/stretchr/testify v1.8.2
|
||||
golang.org/x/oauth2 v0.7.0
|
||||
golang.org/x/text v0.9.0
|
||||
github.com/zitadel/schema v1.3.0
|
||||
golang.org/x/oauth2 v0.6.0
|
||||
golang.org/x/text v0.8.0
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
)
|
||||
|
||||
|
|
12
go.sum
12
go.sum
|
@ -1,6 +1,8 @@
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs=
|
||||
github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
|
@ -19,10 +21,6 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD
|
|||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
|
||||
github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
|
||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/jeremija/gosubmit v0.2.7 h1:At0OhGCFGPXyjPYAsCchoBUhE099pcBXmsb4iZqROIc=
|
||||
|
@ -49,6 +47,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
|||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/zitadel/schema v1.3.0 h1:kQ9W9tvIwZICCKWcMvCEweXET1OcOyGEuFbHs4o5kg0=
|
||||
github.com/zitadel/schema v1.3.0/go.mod h1:NptN6mkBDFvERUCvZHlvWmmME+gmZ44xzwRXwhzsbtc=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||
|
@ -79,8 +79,8 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
|
|||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
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.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
|
|
|
@ -8,8 +8,8 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
|
||||
tu "github.com/zitadel/oidc/v2/internal/testutil"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
tu "github.com/zitadel/oidc/v3/internal/testutil"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
var custom = map[string]any{
|
||||
|
|
|
@ -8,7 +8,8 @@ import (
|
|||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
|
@ -17,7 +18,7 @@ type KeySet struct{}
|
|||
|
||||
// VerifySignature implments op.KeySet.
|
||||
func (KeySet) VerifySignature(ctx context.Context, jws *jose.JSONWebSignature) (payload []byte, err error) {
|
||||
if ctx.Err() != nil {
|
||||
if err = ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -45,6 +46,16 @@ func init() {
|
|||
}
|
||||
}
|
||||
|
||||
type JWTProfileKeyStorage struct{}
|
||||
|
||||
func (JWTProfileKeyStorage) GetKeyByIDAndClientID(ctx context.Context, keyID string, clientID string) (*jose.JSONWebKey, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return gu.Ptr(WebKey.Public()), nil
|
||||
}
|
||||
|
||||
func signEncodeTokenClaims(claims any) string {
|
||||
payload, err := json.Marshal(claims)
|
||||
if err != nil {
|
||||
|
@ -106,6 +117,25 @@ func NewAccessToken(issuer, subject string, audience []string, expiration time.T
|
|||
return NewAccessTokenCustom(issuer, subject, audience, expiration, jwtid, clientID, skew, nil)
|
||||
}
|
||||
|
||||
func NewJWTProfileAssertion(issuer, clientID string, audience []string, issuedAt, expiration time.Time) (string, *oidc.JWTTokenRequest) {
|
||||
req := &oidc.JWTTokenRequest{
|
||||
Issuer: issuer,
|
||||
Subject: clientID,
|
||||
Audience: audience,
|
||||
ExpiresAt: oidc.FromTime(expiration),
|
||||
IssuedAt: oidc.FromTime(issuedAt),
|
||||
}
|
||||
// make sure the private claim map is set correctly
|
||||
data, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err = json.Unmarshal(data, req); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return signEncodeTokenClaims(req), req
|
||||
}
|
||||
|
||||
const InvalidSignatureToken = `eyJhbGciOiJQUzUxMiJ9.eyJpc3MiOiJsb2NhbC5jb20iLCJzdWIiOiJ0aW1AbG9jYWwuY29tIiwiYXVkIjpbInVuaXQiLCJ0ZXN0IiwiNTU1NjY2Il0sImV4cCI6MTY3Nzg0MDQzMSwiaWF0IjoxNjc3ODQwMzcwLCJhdXRoX3RpbWUiOjE2Nzc4NDAzMTAsIm5vbmNlIjoiMTIzNDUiLCJhY3IiOiJzb21ldGhpbmciLCJhbXIiOlsiZm9vIiwiYmFyIl0sImF6cCI6IjU1NTY2NiJ9.DtZmvVkuE4Hw48ijBMhRJbxEWCr_WEYuPQBMY73J9TP6MmfeNFkjVJf4nh4omjB9gVLnQ-xhEkNOe62FS5P0BB2VOxPuHZUj34dNspCgG3h98fGxyiMb5vlIYAHDF9T-w_LntlYItohv63MmdYR-hPpAqjXE7KOfErf-wUDGE9R3bfiQ4HpTdyFJB1nsToYrZ9lhP2mzjTCTs58ckZfQ28DFHn_lfHWpR4rJBgvLx7IH4rMrUayr09Ap-PxQLbv0lYMtmgG1z3JK8MXnuYR0UJdZnEIezOzUTlThhCXB-nvuAXYjYxZZTR0FtlgZUHhIpYK0V2abf_Q_Or36akNCUg`
|
||||
|
||||
// These variables always result in a valid token
|
||||
|
@ -137,6 +167,10 @@ func ValidAccessToken() (string, *oidc.AccessTokenClaims) {
|
|||
return NewAccessToken(ValidIssuer, ValidSubject, ValidAudience, ValidExpiration, ValidJWTID, ValidClientID, ValidSkew)
|
||||
}
|
||||
|
||||
func ValidJWTProfileAssertion() (string, *oidc.JWTTokenRequest) {
|
||||
return NewJWTProfileAssertion(ValidClientID, ValidClientID, []string{ValidIssuer}, time.Now(), ValidExpiration)
|
||||
}
|
||||
|
||||
// ACRVerify is a oidc.ACRVerifier func.
|
||||
func ACRVerify(acr string) error {
|
||||
if acr != ValidACR {
|
||||
|
|
|
@ -14,21 +14,21 @@ import (
|
|||
"golang.org/x/oauth2"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/crypto"
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/crypto"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
var Encoder = httphelper.Encoder(oidc.NewEncoder())
|
||||
|
||||
// Discover calls the discovery endpoint of the provided issuer and returns its configuration
|
||||
// It accepts an optional argument "wellknownUrl" which can be used to overide the dicovery endpoint url
|
||||
func Discover(issuer string, httpClient *http.Client, wellKnownUrl ...string) (*oidc.DiscoveryConfiguration, error) {
|
||||
func Discover(ctx context.Context, issuer string, httpClient *http.Client, wellKnownUrl ...string) (*oidc.DiscoveryConfiguration, error) {
|
||||
wellKnown := strings.TrimSuffix(issuer, "/") + oidc.DiscoveryEndpoint
|
||||
if len(wellKnownUrl) == 1 && wellKnownUrl[0] != "" {
|
||||
wellKnown = wellKnownUrl[0]
|
||||
}
|
||||
req, err := http.NewRequest("GET", wellKnown, nil)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, wellKnown, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -48,12 +48,12 @@ type TokenEndpointCaller interface {
|
|||
HttpClient() *http.Client
|
||||
}
|
||||
|
||||
func CallTokenEndpoint(request interface{}, caller TokenEndpointCaller) (newToken *oauth2.Token, err error) {
|
||||
return callTokenEndpoint(request, nil, caller)
|
||||
func CallTokenEndpoint(ctx context.Context, request interface{}, caller TokenEndpointCaller) (newToken *oauth2.Token, err error) {
|
||||
return callTokenEndpoint(ctx, request, nil, caller)
|
||||
}
|
||||
|
||||
func callTokenEndpoint(request interface{}, authFn interface{}, caller TokenEndpointCaller) (newToken *oauth2.Token, err error) {
|
||||
req, err := httphelper.FormRequest(caller.TokenEndpoint(), request, Encoder, authFn)
|
||||
func callTokenEndpoint(ctx context.Context, request interface{}, authFn interface{}, caller TokenEndpointCaller) (newToken *oauth2.Token, err error) {
|
||||
req, err := httphelper.FormRequest(ctx, caller.TokenEndpoint(), request, Encoder, authFn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -80,8 +80,8 @@ type EndSessionCaller interface {
|
|||
HttpClient() *http.Client
|
||||
}
|
||||
|
||||
func CallEndSessionEndpoint(request interface{}, authFn interface{}, caller EndSessionCaller) (*url.URL, error) {
|
||||
req, err := httphelper.FormRequest(caller.GetEndSessionEndpoint(), request, Encoder, authFn)
|
||||
func CallEndSessionEndpoint(ctx context.Context, request interface{}, authFn interface{}, caller EndSessionCaller) (*url.URL, error) {
|
||||
req, err := httphelper.FormRequest(ctx, caller.GetEndSessionEndpoint(), request, Encoder, authFn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -123,8 +123,8 @@ type RevokeRequest struct {
|
|||
ClientSecret string `schema:"client_secret"`
|
||||
}
|
||||
|
||||
func CallRevokeEndpoint(request interface{}, authFn interface{}, caller RevokeCaller) error {
|
||||
req, err := httphelper.FormRequest(caller.GetRevokeEndpoint(), request, Encoder, authFn)
|
||||
func CallRevokeEndpoint(ctx context.Context, request interface{}, authFn interface{}, caller RevokeCaller) error {
|
||||
req, err := httphelper.FormRequest(ctx, caller.GetRevokeEndpoint(), request, Encoder, authFn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -151,8 +151,8 @@ func CallRevokeEndpoint(request interface{}, authFn interface{}, caller RevokeCa
|
|||
return nil
|
||||
}
|
||||
|
||||
func CallTokenExchangeEndpoint(request interface{}, authFn interface{}, caller TokenEndpointCaller) (resp *oidc.TokenExchangeResponse, err error) {
|
||||
req, err := httphelper.FormRequest(caller.TokenEndpoint(), request, Encoder, authFn)
|
||||
func CallTokenExchangeEndpoint(ctx context.Context, request interface{}, authFn interface{}, caller TokenEndpointCaller) (resp *oidc.TokenExchangeResponse, err error) {
|
||||
req, err := httphelper.FormRequest(ctx, caller.TokenEndpoint(), request, Encoder, authFn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -192,8 +192,8 @@ type DeviceAuthorizationCaller interface {
|
|||
HttpClient() *http.Client
|
||||
}
|
||||
|
||||
func CallDeviceAuthorizationEndpoint(request *oidc.ClientCredentialsRequest, caller DeviceAuthorizationCaller) (*oidc.DeviceAuthorizationResponse, error) {
|
||||
req, err := httphelper.FormRequest(caller.GetDeviceAuthorizationEndpoint(), request, Encoder, nil)
|
||||
func CallDeviceAuthorizationEndpoint(ctx context.Context, request *oidc.ClientCredentialsRequest, caller DeviceAuthorizationCaller, authFn any) (*oidc.DeviceAuthorizationResponse, error) {
|
||||
req, err := httphelper.FormRequest(ctx, caller.GetDeviceAuthorizationEndpoint(), request, Encoder, authFn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -214,7 +214,7 @@ type DeviceAccessTokenRequest struct {
|
|||
}
|
||||
|
||||
func CallDeviceAccessTokenEndpoint(ctx context.Context, request *DeviceAccessTokenRequest, caller TokenEndpointCaller) (*oidc.AccessTokenResponse, error) {
|
||||
req, err := httphelper.FormRequest(caller.TokenEndpoint(), request, Encoder, nil)
|
||||
req, err := httphelper.FormRequest(ctx, caller.TokenEndpoint(), request, Encoder, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package client_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
|
@ -10,7 +11,9 @@ import (
|
|||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -18,15 +21,27 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/oidc/v2/example/server/exampleop"
|
||||
"github.com/zitadel/oidc/v2/example/server/storage"
|
||||
"github.com/zitadel/oidc/v2/pkg/client/rp"
|
||||
"github.com/zitadel/oidc/v2/pkg/client/rs"
|
||||
"github.com/zitadel/oidc/v2/pkg/client/tokenexchange"
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/example/server/exampleop"
|
||||
"github.com/zitadel/oidc/v3/example/server/storage"
|
||||
"github.com/zitadel/oidc/v3/pkg/client/rp"
|
||||
"github.com/zitadel/oidc/v3/pkg/client/rs"
|
||||
"github.com/zitadel/oidc/v3/pkg/client/tokenexchange"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
var CTX context.Context
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(func() int {
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGINT)
|
||||
defer cancel()
|
||||
CTX, cancel = context.WithTimeout(ctx, time.Minute)
|
||||
defer cancel()
|
||||
return m.Run()
|
||||
}())
|
||||
}
|
||||
|
||||
func TestRelyingPartySession(t *testing.T) {
|
||||
t.Log("------- start example OP ------")
|
||||
targetURL := "http://local-site"
|
||||
|
@ -45,7 +60,7 @@ func TestRelyingPartySession(t *testing.T) {
|
|||
|
||||
t.Log("------- refresh tokens ------")
|
||||
|
||||
newTokens, err := rp.RefreshAccessToken(provider, refreshToken, "", "")
|
||||
newTokens, err := rp.RefreshAccessToken(CTX, provider, refreshToken, "", "")
|
||||
require.NoError(t, err, "refresh token")
|
||||
assert.NotNil(t, newTokens, "access token")
|
||||
t.Logf("new access token %s", newTokens.AccessToken)
|
||||
|
@ -57,7 +72,7 @@ func TestRelyingPartySession(t *testing.T) {
|
|||
|
||||
t.Log("------ end session (logout) ------")
|
||||
|
||||
newLoc, err := rp.EndSession(provider, idToken, "", "")
|
||||
newLoc, err := rp.EndSession(CTX, provider, idToken, "", "")
|
||||
require.NoError(t, err, "logout")
|
||||
if newLoc != nil {
|
||||
t.Logf("redirect to %s", newLoc)
|
||||
|
@ -67,11 +82,11 @@ func TestRelyingPartySession(t *testing.T) {
|
|||
|
||||
t.Log("------ attempt refresh again (should fail) ------")
|
||||
t.Log("trying original refresh token", refreshToken)
|
||||
_, err = rp.RefreshAccessToken(provider, refreshToken, "", "")
|
||||
_, err = rp.RefreshAccessToken(CTX, provider, refreshToken, "", "")
|
||||
assert.Errorf(t, err, "refresh with original")
|
||||
if newTokens.RefreshToken != "" {
|
||||
t.Log("trying replacement refresh token", newTokens.RefreshToken)
|
||||
_, err = rp.RefreshAccessToken(provider, newTokens.RefreshToken, "", "")
|
||||
_, err = rp.RefreshAccessToken(CTX, provider, newTokens.RefreshToken, "", "")
|
||||
assert.Errorf(t, err, "refresh with replacement")
|
||||
}
|
||||
}
|
||||
|
@ -93,12 +108,13 @@ func TestResourceServerTokenExchange(t *testing.T) {
|
|||
t.Log("------- run authorization code flow ------")
|
||||
provider, _, refreshToken, idToken := RunAuthorizationCodeFlow(t, opServer, clientID, clientSecret)
|
||||
|
||||
resourceServer, err := rs.NewResourceServerClientCredentials(opServer.URL, clientID, clientSecret)
|
||||
resourceServer, err := rs.NewResourceServerClientCredentials(CTX, opServer.URL, clientID, clientSecret)
|
||||
require.NoError(t, err, "new resource server")
|
||||
|
||||
t.Log("------- exchage refresh tokens (impersonation) ------")
|
||||
|
||||
tokenExchangeResponse, err := tokenexchange.ExchangeToken(
|
||||
CTX,
|
||||
resourceServer,
|
||||
refreshToken,
|
||||
oidc.RefreshTokenType,
|
||||
|
@ -118,7 +134,7 @@ func TestResourceServerTokenExchange(t *testing.T) {
|
|||
|
||||
t.Log("------ end session (logout) ------")
|
||||
|
||||
newLoc, err := rp.EndSession(provider, idToken, "", "")
|
||||
newLoc, err := rp.EndSession(CTX, provider, idToken, "", "")
|
||||
require.NoError(t, err, "logout")
|
||||
if newLoc != nil {
|
||||
t.Logf("redirect to %s", newLoc)
|
||||
|
@ -129,6 +145,7 @@ func TestResourceServerTokenExchange(t *testing.T) {
|
|||
t.Log("------- attempt exchage again (should fail) ------")
|
||||
|
||||
tokenExchangeResponse, err = tokenexchange.ExchangeToken(
|
||||
CTX,
|
||||
resourceServer,
|
||||
refreshToken,
|
||||
oidc.RefreshTokenType,
|
||||
|
@ -166,6 +183,7 @@ func RunAuthorizationCodeFlow(t *testing.T, opServer *httptest.Server, clientID,
|
|||
key := []byte("test1234test1234")
|
||||
cookieHandler := httphelper.NewCookieHandler(key, key, httphelper.WithUnsecure())
|
||||
provider, err = rp.NewRelyingPartyOIDC(
|
||||
CTX,
|
||||
opServer.URL,
|
||||
clientID,
|
||||
clientSecret,
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
// JWTProfileExchange handles the oauth2 jwt profile exchange
|
||||
func JWTProfileExchange(jwtProfileGrantRequest *oidc.JWTProfileGrantRequest, caller TokenEndpointCaller) (*oauth2.Token, error) {
|
||||
return CallTokenEndpoint(jwtProfileGrantRequest, caller)
|
||||
func JWTProfileExchange(ctx context.Context, jwtProfileGrantRequest *oidc.JWTProfileGrantRequest, caller TokenEndpointCaller) (*oauth2.Token, error) {
|
||||
return CallTokenEndpoint(ctx, jwtProfileGrantRequest, caller)
|
||||
}
|
||||
|
||||
func ClientAssertionCodeOptions(assertion string) []oauth2.AuthCodeOption {
|
||||
|
|
|
@ -10,7 +10,7 @@ const (
|
|||
applicationKey = "application"
|
||||
)
|
||||
|
||||
type keyFile struct {
|
||||
type KeyFile struct {
|
||||
Type string `json:"type"` // serviceaccount or application
|
||||
KeyID string `json:"keyId"`
|
||||
Key string `json:"key"`
|
||||
|
@ -23,7 +23,7 @@ type keyFile struct {
|
|||
ClientID string `json:"clientId"`
|
||||
}
|
||||
|
||||
func ConfigFromKeyFile(path string) (*keyFile, error) {
|
||||
func ConfigFromKeyFile(path string) (*KeyFile, error) {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -31,8 +31,8 @@ func ConfigFromKeyFile(path string) (*keyFile, error) {
|
|||
return ConfigFromKeyFileData(data)
|
||||
}
|
||||
|
||||
func ConfigFromKeyFileData(data []byte) (*keyFile, error) {
|
||||
var f keyFile
|
||||
func ConfigFromKeyFileData(data []byte) (*KeyFile, error) {
|
||||
var f KeyFile
|
||||
if err := json.Unmarshal(data, &f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
package profile
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/client"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/client"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
// jwtProfileTokenSource implement the oauth2.TokenSource
|
||||
// it will request a token using the OAuth2 JWT Profile Grant
|
||||
// therefore sending an `assertion` by signing a JWT with the provided private key
|
||||
type TokenSource interface {
|
||||
oauth2.TokenSource
|
||||
TokenCtx(context.Context) (*oauth2.Token, error)
|
||||
}
|
||||
|
||||
type jwtProfileTokenSource struct {
|
||||
clientID string
|
||||
audience []string
|
||||
|
@ -23,23 +26,38 @@ type jwtProfileTokenSource struct {
|
|||
tokenEndpoint string
|
||||
}
|
||||
|
||||
func NewJWTProfileTokenSourceFromKeyFile(issuer, keyPath string, scopes []string, options ...func(source *jwtProfileTokenSource)) (oauth2.TokenSource, error) {
|
||||
keyData, err := client.ConfigFromKeyFile(keyPath)
|
||||
// NewJWTProfileTokenSourceFromKeyFile returns an implementation of TokenSource
|
||||
// It will request a token using the OAuth2 JWT Profile Grant,
|
||||
// therefore sending an `assertion` by singing a JWT with the provided private key from jsonFile.
|
||||
//
|
||||
// The passed context is only used for the call to the Discover endpoint.
|
||||
func NewJWTProfileTokenSourceFromKeyFile(ctx context.Context, issuer, jsonFile string, scopes []string, options ...func(source *jwtProfileTokenSource)) (TokenSource, error) {
|
||||
keyData, err := client.ConfigFromKeyFile(jsonFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewJWTProfileTokenSource(issuer, keyData.UserID, keyData.KeyID, []byte(keyData.Key), scopes, options...)
|
||||
return NewJWTProfileTokenSource(ctx, issuer, keyData.UserID, keyData.KeyID, []byte(keyData.Key), scopes, options...)
|
||||
}
|
||||
|
||||
func NewJWTProfileTokenSourceFromKeyFileData(issuer string, data []byte, scopes []string, options ...func(source *jwtProfileTokenSource)) (oauth2.TokenSource, error) {
|
||||
keyData, err := client.ConfigFromKeyFileData(data)
|
||||
// NewJWTProfileTokenSourceFromKeyFileData returns an implementation of oauth2.TokenSource
|
||||
// It will request a token using the OAuth2 JWT Profile Grant,
|
||||
// therefore sending an `assertion` by singing a JWT with the provided private key in jsonData.
|
||||
//
|
||||
// The passed context is only used for the call to the Discover endpoint.
|
||||
func NewJWTProfileTokenSourceFromKeyFileData(ctx context.Context, issuer string, jsonData []byte, scopes []string, options ...func(source *jwtProfileTokenSource)) (TokenSource, error) {
|
||||
keyData, err := client.ConfigFromKeyFileData(jsonData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewJWTProfileTokenSource(issuer, keyData.UserID, keyData.KeyID, []byte(keyData.Key), scopes, options...)
|
||||
return NewJWTProfileTokenSource(ctx, issuer, keyData.UserID, keyData.KeyID, []byte(keyData.Key), scopes, options...)
|
||||
}
|
||||
|
||||
func NewJWTProfileTokenSource(issuer, clientID, keyID string, key []byte, scopes []string, options ...func(source *jwtProfileTokenSource)) (oauth2.TokenSource, error) {
|
||||
// NewJWTProfileSource returns an implementation of oauth2.TokenSource
|
||||
// It will request a token using the OAuth2 JWT Profile Grant,
|
||||
// therefore sending an `assertion` by singing a JWT with the provided private key.
|
||||
//
|
||||
// The passed context is only used for the call to the Discover endpoint.
|
||||
func NewJWTProfileTokenSource(ctx context.Context, issuer, clientID, keyID string, key []byte, scopes []string, options ...func(source *jwtProfileTokenSource)) (TokenSource, error) {
|
||||
signer, err := client.NewSignerFromPrivateKeyByte(key, keyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -55,7 +73,7 @@ func NewJWTProfileTokenSource(issuer, clientID, keyID string, key []byte, scopes
|
|||
opt(source)
|
||||
}
|
||||
if source.tokenEndpoint == "" {
|
||||
config, err := client.Discover(issuer, source.httpClient)
|
||||
config, err := client.Discover(ctx, issuer, source.httpClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -64,13 +82,13 @@ func NewJWTProfileTokenSource(issuer, clientID, keyID string, key []byte, scopes
|
|||
return source, nil
|
||||
}
|
||||
|
||||
func WithHTTPClient(client *http.Client) func(*jwtProfileTokenSource) {
|
||||
func WithHTTPClient(client *http.Client) func(source *jwtProfileTokenSource) {
|
||||
return func(source *jwtProfileTokenSource) {
|
||||
source.httpClient = client
|
||||
}
|
||||
}
|
||||
|
||||
func WithStaticTokenEndpoint(issuer, tokenEndpoint string) func(*jwtProfileTokenSource) {
|
||||
func WithStaticTokenEndpoint(issuer, tokenEndpoint string) func(source *jwtProfileTokenSource) {
|
||||
return func(source *jwtProfileTokenSource) {
|
||||
source.tokenEndpoint = tokenEndpoint
|
||||
}
|
||||
|
@ -85,9 +103,13 @@ func (j *jwtProfileTokenSource) HttpClient() *http.Client {
|
|||
}
|
||||
|
||||
func (j *jwtProfileTokenSource) Token() (*oauth2.Token, error) {
|
||||
return j.TokenCtx(context.Background())
|
||||
}
|
||||
|
||||
func (j *jwtProfileTokenSource) TokenCtx(ctx context.Context) (*oauth2.Token, error) {
|
||||
assertion, err := client.SignedJWTProfileAssertion(j.clientID, j.audience, time.Hour, j.signer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client.JWTProfileExchange(oidc.NewJWTProfileGrantRequest(assertion, j.scopes...), j)
|
||||
return client.JWTProfileExchange(ctx, oidc.NewJWTProfileGrantRequest(assertion, j.scopes...), j)
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@ import (
|
|||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/client/rp"
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/client/rp"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package rp
|
||||
|
||||
import (
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc/grants/tokenexchange"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc/grants/tokenexchange"
|
||||
)
|
||||
|
||||
// DelegationTokenRequest is an implementation of TokenExchangeRequest
|
||||
|
|
|
@ -5,8 +5,8 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/client"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/client"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
func newDeviceClientCredentialsRequest(scopes []string, rp RelyingParty) (*oidc.ClientCredentialsRequest, error) {
|
||||
|
@ -32,13 +32,13 @@ func newDeviceClientCredentialsRequest(scopes []string, rp RelyingParty) (*oidc.
|
|||
// DeviceAuthorization starts a new Device Authorization flow as defined
|
||||
// in RFC 8628, section 3.1 and 3.2:
|
||||
// https://www.rfc-editor.org/rfc/rfc8628#section-3.1
|
||||
func DeviceAuthorization(scopes []string, rp RelyingParty) (*oidc.DeviceAuthorizationResponse, error) {
|
||||
func DeviceAuthorization(ctx context.Context, scopes []string, rp RelyingParty, authFn any) (*oidc.DeviceAuthorizationResponse, error) {
|
||||
req, err := newDeviceClientCredentialsRequest(scopes, rp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client.CallDeviceAuthorizationEndpoint(req, rp)
|
||||
return client.CallDeviceAuthorizationEndpoint(ctx, req, rp, authFn)
|
||||
}
|
||||
|
||||
// DeviceAccessToken attempts to obtain tokens from a Device Authorization,
|
||||
|
|
|
@ -9,8 +9,8 @@ import (
|
|||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
func NewRemoteKeySet(client *http.Client, jwksURL string, opts ...func(*remoteKeySet)) oidc.KeySet {
|
||||
|
|
|
@ -7,16 +7,15 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/oauth2"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/client"
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/client"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -63,8 +62,8 @@ type RelyingParty interface {
|
|||
// be used to start a DeviceAuthorization flow.
|
||||
GetDeviceAuthorizationEndpoint() string
|
||||
|
||||
// IDTokenVerifier returns the verifier interface used for oidc id_token verification
|
||||
IDTokenVerifier() IDTokenVerifier
|
||||
// IDTokenVerifier returns the verifier used for oidc id_token verification
|
||||
IDTokenVerifier() *IDTokenVerifier
|
||||
// ErrorHandler returns the handler used for callback errors
|
||||
|
||||
ErrorHandler() func(http.ResponseWriter, *http.Request, string, string, string)
|
||||
|
@ -88,7 +87,7 @@ type relyingParty struct {
|
|||
cookieHandler *httphelper.CookieHandler
|
||||
|
||||
errorHandler func(http.ResponseWriter, *http.Request, string, string, string)
|
||||
idTokenVerifier IDTokenVerifier
|
||||
idTokenVerifier *IDTokenVerifier
|
||||
verifierOpts []VerifierOption
|
||||
signer jose.Signer
|
||||
}
|
||||
|
@ -137,7 +136,7 @@ func (rp *relyingParty) GetRevokeEndpoint() string {
|
|||
return rp.endpoints.RevokeURL
|
||||
}
|
||||
|
||||
func (rp *relyingParty) IDTokenVerifier() IDTokenVerifier {
|
||||
func (rp *relyingParty) IDTokenVerifier() *IDTokenVerifier {
|
||||
if rp.idTokenVerifier == nil {
|
||||
rp.idTokenVerifier = NewIDTokenVerifier(rp.issuer, rp.oauthConfig.ClientID, NewRemoteKeySet(rp.httpClient, rp.endpoints.JKWsURL), rp.verifierOpts...)
|
||||
}
|
||||
|
@ -177,7 +176,7 @@ func NewRelyingPartyOAuth(config *oauth2.Config, options ...Option) (RelyingPart
|
|||
// NewRelyingPartyOIDC creates an (OIDC) RelyingParty with the given
|
||||
// issuer, clientID, clientSecret, redirectURI, scopes and possible configOptions
|
||||
// it will run discovery on the provided issuer and use the found endpoints
|
||||
func NewRelyingPartyOIDC(issuer, clientID, clientSecret, redirectURI string, scopes []string, options ...Option) (RelyingParty, error) {
|
||||
func NewRelyingPartyOIDC(ctx context.Context, issuer, clientID, clientSecret, redirectURI string, scopes []string, options ...Option) (RelyingParty, error) {
|
||||
rp := &relyingParty{
|
||||
issuer: issuer,
|
||||
oauthConfig: &oauth2.Config{
|
||||
|
@ -195,7 +194,7 @@ func NewRelyingPartyOIDC(issuer, clientID, clientSecret, redirectURI string, sco
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
discoveryConfiguration, err := client.Discover(rp.issuer, rp.httpClient, rp.DiscoveryEndpoint)
|
||||
discoveryConfiguration, err := client.Discover(ctx, rp.issuer, rp.httpClient, rp.DiscoveryEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -310,26 +309,6 @@ 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
|
||||
func Discover(issuer string, httpClient *http.Client) (Endpoints, error) {
|
||||
wellKnown := strings.TrimSuffix(issuer, "/") + oidc.DiscoveryEndpoint
|
||||
req, err := http.NewRequest("GET", wellKnown, nil)
|
||||
if err != nil {
|
||||
return Endpoints{}, err
|
||||
}
|
||||
discoveryConfig := new(oidc.DiscoveryConfiguration)
|
||||
err = httphelper.HttpRequest(httpClient, req, &discoveryConfig)
|
||||
if err != nil {
|
||||
return Endpoints{}, err
|
||||
}
|
||||
if discoveryConfig.Issuer != issuer {
|
||||
return Endpoints{}, oidc.ErrIssuerInvalid
|
||||
}
|
||||
return GetEndpoints(discoveryConfig), nil
|
||||
}
|
||||
|
||||
// AuthURL returns the auth request url
|
||||
// (wrapping the oauth2 `AuthCodeURL`)
|
||||
func AuthURL(state string, rp RelyingParty, opts ...AuthURLOpt) string {
|
||||
|
@ -463,7 +442,7 @@ type CodeExchangeUserinfoCallback[C oidc.IDClaims] func(w http.ResponseWriter, r
|
|||
// on success it will pass the userinfo into its callback function as well
|
||||
func UserinfoCallback[C oidc.IDClaims](f CodeExchangeUserinfoCallback[C]) CodeExchangeCallback[C] {
|
||||
return func(w http.ResponseWriter, r *http.Request, tokens *oidc.Tokens[C], state string, rp RelyingParty) {
|
||||
info, err := Userinfo(tokens.AccessToken, tokens.TokenType, tokens.IDTokenClaims.GetSubject(), rp)
|
||||
info, err := Userinfo(r.Context(), tokens.AccessToken, tokens.TokenType, tokens.IDTokenClaims.GetSubject(), rp)
|
||||
if err != nil {
|
||||
http.Error(w, "userinfo failed: "+err.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
|
@ -473,8 +452,8 @@ func UserinfoCallback[C oidc.IDClaims](f CodeExchangeUserinfoCallback[C]) CodeEx
|
|||
}
|
||||
|
||||
// Userinfo will call the OIDC Userinfo Endpoint with the provided token
|
||||
func Userinfo(token, tokenType, subject string, rp RelyingParty) (*oidc.UserInfo, error) {
|
||||
req, err := http.NewRequest("GET", rp.UserinfoEndpoint(), nil)
|
||||
func Userinfo(ctx context.Context, token, tokenType, subject string, rp RelyingParty) (*oidc.UserInfo, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, rp.UserinfoEndpoint(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -620,11 +599,7 @@ type RefreshTokenRequest struct {
|
|||
GrantType oidc.GrantType `schema:"grant_type"`
|
||||
}
|
||||
|
||||
// RefreshAccessToken performs a token refresh. If it doesn't error, it will always
|
||||
// provide a new AccessToken. It may provide a new RefreshToken, and if it does, then
|
||||
// the old one should be considered invalid. It may also provide a new IDToken. The
|
||||
// new IDToken can be retrieved with token.Extra("id_token").
|
||||
func RefreshAccessToken(rp RelyingParty, refreshToken, clientAssertion, clientAssertionType string) (*oauth2.Token, error) {
|
||||
func RefreshAccessToken(ctx context.Context, rp RelyingParty, refreshToken, clientAssertion, clientAssertionType string) (*oauth2.Token, error) {
|
||||
request := RefreshTokenRequest{
|
||||
RefreshToken: refreshToken,
|
||||
Scopes: rp.OAuthConfig().Scopes,
|
||||
|
@ -634,17 +609,17 @@ func RefreshAccessToken(rp RelyingParty, refreshToken, clientAssertion, clientAs
|
|||
ClientAssertionType: clientAssertionType,
|
||||
GrantType: oidc.GrantTypeRefreshToken,
|
||||
}
|
||||
return client.CallTokenEndpoint(request, tokenEndpointCaller{RelyingParty: rp})
|
||||
return client.CallTokenEndpoint(ctx, request, tokenEndpointCaller{RelyingParty: rp})
|
||||
}
|
||||
|
||||
func EndSession(rp RelyingParty, idToken, optionalRedirectURI, optionalState string) (*url.URL, error) {
|
||||
func EndSession(ctx context.Context, rp RelyingParty, idToken, optionalRedirectURI, optionalState string) (*url.URL, error) {
|
||||
request := oidc.EndSessionRequest{
|
||||
IdTokenHint: idToken,
|
||||
ClientID: rp.OAuthConfig().ClientID,
|
||||
PostLogoutRedirectURI: optionalRedirectURI,
|
||||
State: optionalState,
|
||||
}
|
||||
return client.CallEndSessionEndpoint(request, nil, rp)
|
||||
return client.CallEndSessionEndpoint(ctx, request, nil, rp)
|
||||
}
|
||||
|
||||
// RevokeToken requires a RelyingParty that is also a client.RevokeCaller. The RelyingParty
|
||||
|
@ -652,7 +627,7 @@ func EndSession(rp RelyingParty, idToken, optionalRedirectURI, optionalState str
|
|||
// NewRelyingPartyOAuth() does not.
|
||||
//
|
||||
// tokenTypeHint should be either "id_token" or "refresh_token".
|
||||
func RevokeToken(rp RelyingParty, token string, tokenTypeHint string) error {
|
||||
func RevokeToken(ctx context.Context, rp RelyingParty, token string, tokenTypeHint string) error {
|
||||
request := client.RevokeRequest{
|
||||
Token: token,
|
||||
TokenTypeHint: tokenTypeHint,
|
||||
|
@ -660,7 +635,7 @@ func RevokeToken(rp RelyingParty, token string, tokenTypeHint string) error {
|
|||
ClientSecret: rp.OAuthConfig().ClientSecret,
|
||||
}
|
||||
if rc, ok := rp.(client.RevokeCaller); ok && rc.GetRevokeEndpoint() != "" {
|
||||
return client.CallRevokeEndpoint(request, nil, rc)
|
||||
return client.CallRevokeEndpoint(ctx, request, nil, rc)
|
||||
}
|
||||
return fmt.Errorf("RelyingParty does not support RevokeCaller")
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc/grants/tokenexchange"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc/grants/tokenexchange"
|
||||
)
|
||||
|
||||
// TokenExchangeRP extends the `RelyingParty` interface for the *draft* oauth2 `Token Exchange`
|
||||
|
|
|
@ -6,22 +6,12 @@ import (
|
|||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
type IDTokenVerifier interface {
|
||||
oidc.Verifier
|
||||
ClientID() string
|
||||
SupportedSignAlgs() []string
|
||||
KeySet() oidc.KeySet
|
||||
Nonce(context.Context) string
|
||||
ACR() oidc.ACRVerifier
|
||||
MaxAge() time.Duration
|
||||
}
|
||||
|
||||
// VerifyTokens implement the Token Response Validation as defined in OIDC specification
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#TokenResponseValidation
|
||||
func VerifyTokens[C oidc.IDClaims](ctx context.Context, accessToken, idToken string, v IDTokenVerifier) (claims C, err error) {
|
||||
func VerifyTokens[C oidc.IDClaims](ctx context.Context, accessToken, idToken string, v *IDTokenVerifier) (claims C, err error) {
|
||||
var nilClaims C
|
||||
|
||||
claims, err = VerifyIDToken[C](ctx, idToken, v)
|
||||
|
@ -36,7 +26,7 @@ func VerifyTokens[C oidc.IDClaims](ctx context.Context, accessToken, idToken str
|
|||
|
||||
// VerifyIDToken validates the id token according to
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
|
||||
func VerifyIDToken[C oidc.Claims](ctx context.Context, token string, v IDTokenVerifier) (claims C, err error) {
|
||||
func VerifyIDToken[C oidc.Claims](ctx context.Context, token string, v *IDTokenVerifier) (claims C, err error) {
|
||||
var nilClaims C
|
||||
|
||||
decrypted, err := oidc.DecryptToken(token)
|
||||
|
@ -52,27 +42,27 @@ func VerifyIDToken[C oidc.Claims](ctx context.Context, token string, v IDTokenVe
|
|||
return nilClaims, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckIssuer(claims, v.Issuer()); err != nil {
|
||||
if err = oidc.CheckIssuer(claims, v.Issuer); err != nil {
|
||||
return nilClaims, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckAudience(claims, v.ClientID()); err != nil {
|
||||
if err = oidc.CheckAudience(claims, v.ClientID); err != nil {
|
||||
return nilClaims, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckAuthorizedParty(claims, v.ClientID()); err != nil {
|
||||
if err = oidc.CheckAuthorizedParty(claims, v.ClientID); err != nil {
|
||||
return nilClaims, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckSignature(ctx, decrypted, payload, claims, v.SupportedSignAlgs(), v.KeySet()); err != nil {
|
||||
if err = oidc.CheckSignature(ctx, decrypted, payload, claims, v.SupportedSignAlgs, v.KeySet); err != nil {
|
||||
return nilClaims, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckExpiration(claims, v.Offset()); err != nil {
|
||||
if err = oidc.CheckExpiration(claims, v.Offset); err != nil {
|
||||
return nilClaims, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckIssuedAt(claims, v.MaxAgeIAT(), v.Offset()); err != nil {
|
||||
if err = oidc.CheckIssuedAt(claims, v.MaxAgeIAT, v.Offset); err != nil {
|
||||
return nilClaims, err
|
||||
}
|
||||
|
||||
|
@ -80,16 +70,18 @@ func VerifyIDToken[C oidc.Claims](ctx context.Context, token string, v IDTokenVe
|
|||
return nilClaims, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckAuthorizationContextClassReference(claims, v.ACR()); err != nil {
|
||||
if err = oidc.CheckAuthorizationContextClassReference(claims, v.ACR); err != nil {
|
||||
return nilClaims, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckAuthTime(claims, v.MaxAge()); err != nil {
|
||||
if err = oidc.CheckAuthTime(claims, v.MaxAge); err != nil {
|
||||
return nilClaims, err
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
type IDTokenVerifier oidc.Verifier
|
||||
|
||||
// VerifyAccessToken validates the access token according to
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowTokenValidation
|
||||
func VerifyAccessToken(accessToken, atHash string, sigAlgorithm jose.SignatureAlgorithm) error {
|
||||
|
@ -107,15 +99,14 @@ func VerifyAccessToken(accessToken, atHash string, sigAlgorithm jose.SignatureAl
|
|||
return nil
|
||||
}
|
||||
|
||||
// NewIDTokenVerifier returns an implementation of `IDTokenVerifier`
|
||||
// for `VerifyTokens` and `VerifyIDToken`
|
||||
func NewIDTokenVerifier(issuer, clientID string, keySet oidc.KeySet, options ...VerifierOption) IDTokenVerifier {
|
||||
v := &idTokenVerifier{
|
||||
issuer: issuer,
|
||||
clientID: clientID,
|
||||
keySet: keySet,
|
||||
offset: time.Second,
|
||||
nonce: func(_ context.Context) string {
|
||||
// NewIDTokenVerifier returns a oidc.Verifier suitable for ID token verification.
|
||||
func NewIDTokenVerifier(issuer, clientID string, keySet oidc.KeySet, options ...VerifierOption) *IDTokenVerifier {
|
||||
v := &IDTokenVerifier{
|
||||
Issuer: issuer,
|
||||
ClientID: clientID,
|
||||
KeySet: keySet,
|
||||
Offset: time.Second,
|
||||
Nonce: func(_ context.Context) string {
|
||||
return ""
|
||||
},
|
||||
}
|
||||
|
@ -128,95 +119,47 @@ func NewIDTokenVerifier(issuer, clientID string, keySet oidc.KeySet, options ...
|
|||
}
|
||||
|
||||
// VerifierOption is the type for providing dynamic options to the IDTokenVerifier
|
||||
type VerifierOption func(*idTokenVerifier)
|
||||
type VerifierOption func(*IDTokenVerifier)
|
||||
|
||||
// WithIssuedAtOffset mitigates the risk of iat to be in the future
|
||||
// because of clock skews with the ability to add an offset to the current time
|
||||
func WithIssuedAtOffset(offset time.Duration) func(*idTokenVerifier) {
|
||||
return func(v *idTokenVerifier) {
|
||||
v.offset = offset
|
||||
func WithIssuedAtOffset(offset time.Duration) VerifierOption {
|
||||
return func(v *IDTokenVerifier) {
|
||||
v.Offset = offset
|
||||
}
|
||||
}
|
||||
|
||||
// WithIssuedAtMaxAge provides the ability to define the maximum duration between iat and now
|
||||
func WithIssuedAtMaxAge(maxAge time.Duration) func(*idTokenVerifier) {
|
||||
return func(v *idTokenVerifier) {
|
||||
v.maxAgeIAT = maxAge
|
||||
func WithIssuedAtMaxAge(maxAge time.Duration) VerifierOption {
|
||||
return func(v *IDTokenVerifier) {
|
||||
v.MaxAgeIAT = maxAge
|
||||
}
|
||||
}
|
||||
|
||||
// WithNonce sets the function to check the nonce
|
||||
func WithNonce(nonce func(context.Context) string) VerifierOption {
|
||||
return func(v *idTokenVerifier) {
|
||||
v.nonce = nonce
|
||||
return func(v *IDTokenVerifier) {
|
||||
v.Nonce = nonce
|
||||
}
|
||||
}
|
||||
|
||||
// WithACRVerifier sets the verifier for the acr claim
|
||||
func WithACRVerifier(verifier oidc.ACRVerifier) VerifierOption {
|
||||
return func(v *idTokenVerifier) {
|
||||
v.acr = verifier
|
||||
return func(v *IDTokenVerifier) {
|
||||
v.ACR = verifier
|
||||
}
|
||||
}
|
||||
|
||||
// WithAuthTimeMaxAge provides the ability to define the maximum duration between auth_time and now
|
||||
func WithAuthTimeMaxAge(maxAge time.Duration) VerifierOption {
|
||||
return func(v *idTokenVerifier) {
|
||||
v.maxAge = maxAge
|
||||
return func(v *IDTokenVerifier) {
|
||||
v.MaxAge = maxAge
|
||||
}
|
||||
}
|
||||
|
||||
// WithSupportedSigningAlgorithms overwrites the default RS256 signing algorithm
|
||||
func WithSupportedSigningAlgorithms(algs ...string) VerifierOption {
|
||||
return func(v *idTokenVerifier) {
|
||||
v.supportedSignAlgs = algs
|
||||
return func(v *IDTokenVerifier) {
|
||||
v.SupportedSignAlgs = algs
|
||||
}
|
||||
}
|
||||
|
||||
type idTokenVerifier struct {
|
||||
issuer string
|
||||
maxAgeIAT time.Duration
|
||||
offset time.Duration
|
||||
clientID string
|
||||
supportedSignAlgs []string
|
||||
keySet oidc.KeySet
|
||||
acr oidc.ACRVerifier
|
||||
maxAge time.Duration
|
||||
nonce func(ctx context.Context) string
|
||||
}
|
||||
|
||||
func (i *idTokenVerifier) Issuer() string {
|
||||
return i.issuer
|
||||
}
|
||||
|
||||
func (i *idTokenVerifier) MaxAgeIAT() time.Duration {
|
||||
return i.maxAgeIAT
|
||||
}
|
||||
|
||||
func (i *idTokenVerifier) Offset() time.Duration {
|
||||
return i.offset
|
||||
}
|
||||
|
||||
func (i *idTokenVerifier) ClientID() string {
|
||||
return i.clientID
|
||||
}
|
||||
|
||||
func (i *idTokenVerifier) SupportedSignAlgs() []string {
|
||||
return i.supportedSignAlgs
|
||||
}
|
||||
|
||||
func (i *idTokenVerifier) KeySet() oidc.KeySet {
|
||||
return i.keySet
|
||||
}
|
||||
|
||||
func (i *idTokenVerifier) Nonce(ctx context.Context) string {
|
||||
return i.nonce(ctx)
|
||||
}
|
||||
|
||||
func (i *idTokenVerifier) ACR() oidc.ACRVerifier {
|
||||
return i.acr
|
||||
}
|
||||
|
||||
func (i *idTokenVerifier) MaxAge() time.Duration {
|
||||
return i.maxAge
|
||||
}
|
||||
|
|
|
@ -7,22 +7,22 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
tu "github.com/zitadel/oidc/v2/internal/testutil"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
tu "github.com/zitadel/oidc/v3/internal/testutil"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
func TestVerifyTokens(t *testing.T) {
|
||||
verifier := &idTokenVerifier{
|
||||
issuer: tu.ValidIssuer,
|
||||
maxAgeIAT: 2 * time.Minute,
|
||||
offset: time.Second,
|
||||
supportedSignAlgs: []string{string(tu.SignatureAlgorithm)},
|
||||
keySet: tu.KeySet{},
|
||||
maxAge: 2 * time.Minute,
|
||||
acr: tu.ACRVerify,
|
||||
nonce: func(context.Context) string { return tu.ValidNonce },
|
||||
clientID: tu.ValidClientID,
|
||||
verifier := &IDTokenVerifier{
|
||||
Issuer: tu.ValidIssuer,
|
||||
MaxAgeIAT: 2 * time.Minute,
|
||||
Offset: time.Second,
|
||||
SupportedSignAlgs: []string{string(tu.SignatureAlgorithm)},
|
||||
KeySet: tu.KeySet{},
|
||||
MaxAge: 2 * time.Minute,
|
||||
ACR: tu.ACRVerify,
|
||||
Nonce: func(context.Context) string { return tu.ValidNonce },
|
||||
ClientID: tu.ValidClientID,
|
||||
}
|
||||
accessToken, _ := tu.ValidAccessToken()
|
||||
atHash, err := oidc.ClaimHash(accessToken, tu.SignatureAlgorithm)
|
||||
|
@ -91,15 +91,15 @@ func TestVerifyTokens(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestVerifyIDToken(t *testing.T) {
|
||||
verifier := &idTokenVerifier{
|
||||
issuer: tu.ValidIssuer,
|
||||
maxAgeIAT: 2 * time.Minute,
|
||||
offset: time.Second,
|
||||
supportedSignAlgs: []string{string(tu.SignatureAlgorithm)},
|
||||
keySet: tu.KeySet{},
|
||||
maxAge: 2 * time.Minute,
|
||||
acr: tu.ACRVerify,
|
||||
nonce: func(context.Context) string { return tu.ValidNonce },
|
||||
verifier := &IDTokenVerifier{
|
||||
Issuer: tu.ValidIssuer,
|
||||
MaxAgeIAT: 2 * time.Minute,
|
||||
Offset: time.Second,
|
||||
SupportedSignAlgs: []string{string(tu.SignatureAlgorithm)},
|
||||
KeySet: tu.KeySet{},
|
||||
MaxAge: 2 * time.Minute,
|
||||
ACR: tu.ACRVerify,
|
||||
Nonce: func(context.Context) string { return tu.ValidNonce },
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
|
@ -219,7 +219,7 @@ func TestVerifyIDToken(t *testing.T) {
|
|||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
token, want := tt.tokenClaims()
|
||||
verifier.clientID = tt.clientID
|
||||
verifier.ClientID = tt.clientID
|
||||
got, err := VerifyIDToken[*oidc.IDTokenClaims](context.Background(), token, verifier)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
|
@ -300,7 +300,7 @@ func TestNewIDTokenVerifier(t *testing.T) {
|
|||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want IDTokenVerifier
|
||||
want *IDTokenVerifier
|
||||
}{
|
||||
{
|
||||
name: "nil nonce", // otherwise assert.Equal will fail on the function
|
||||
|
@ -317,16 +317,16 @@ func TestNewIDTokenVerifier(t *testing.T) {
|
|||
WithSupportedSigningAlgorithms("ABC", "DEF"),
|
||||
},
|
||||
},
|
||||
want: &idTokenVerifier{
|
||||
issuer: tu.ValidIssuer,
|
||||
offset: time.Minute,
|
||||
maxAgeIAT: time.Hour,
|
||||
clientID: tu.ValidClientID,
|
||||
keySet: tu.KeySet{},
|
||||
nonce: nil,
|
||||
acr: nil,
|
||||
maxAge: 2 * time.Hour,
|
||||
supportedSignAlgs: []string{"ABC", "DEF"},
|
||||
want: &IDTokenVerifier{
|
||||
Issuer: tu.ValidIssuer,
|
||||
Offset: time.Minute,
|
||||
MaxAgeIAT: time.Hour,
|
||||
ClientID: tu.ValidClientID,
|
||||
KeySet: tu.KeySet{},
|
||||
Nonce: nil,
|
||||
ACR: nil,
|
||||
MaxAge: 2 * time.Hour,
|
||||
SupportedSignAlgs: []string{"ABC", "DEF"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
tu "github.com/zitadel/oidc/v2/internal/testutil"
|
||||
"github.com/zitadel/oidc/v2/pkg/client/rp"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
tu "github.com/zitadel/oidc/v3/internal/testutil"
|
||||
"github.com/zitadel/oidc/v3/pkg/client/rp"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
// MyCustomClaims extends the TokenClaims base,
|
||||
|
|
|
@ -6,9 +6,9 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/client"
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/client"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
type ResourceServer interface {
|
||||
|
@ -42,14 +42,14 @@ func (r *resourceServer) AuthFn() (interface{}, error) {
|
|||
return r.authFn()
|
||||
}
|
||||
|
||||
func NewResourceServerClientCredentials(issuer, clientID, clientSecret string, option ...Option) (ResourceServer, error) {
|
||||
func NewResourceServerClientCredentials(ctx context.Context, issuer, clientID, clientSecret string, option ...Option) (ResourceServer, error) {
|
||||
authorizer := func() (interface{}, error) {
|
||||
return httphelper.AuthorizeBasic(clientID, clientSecret), nil
|
||||
}
|
||||
return newResourceServer(issuer, authorizer, option...)
|
||||
return newResourceServer(ctx, issuer, authorizer, option...)
|
||||
}
|
||||
|
||||
func NewResourceServerJWTProfile(issuer, clientID, keyID string, key []byte, options ...Option) (ResourceServer, error) {
|
||||
func NewResourceServerJWTProfile(ctx context.Context, issuer, clientID, keyID string, key []byte, options ...Option) (ResourceServer, error) {
|
||||
signer, err := client.NewSignerFromPrivateKeyByte(key, keyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -61,10 +61,10 @@ func NewResourceServerJWTProfile(issuer, clientID, keyID string, key []byte, opt
|
|||
}
|
||||
return client.ClientAssertionFormAuthorization(assertion), nil
|
||||
}
|
||||
return newResourceServer(issuer, authorizer, options...)
|
||||
return newResourceServer(ctx, issuer, authorizer, options...)
|
||||
}
|
||||
|
||||
func newResourceServer(issuer string, authorizer func() (interface{}, error), options ...Option) (*resourceServer, error) {
|
||||
func newResourceServer(ctx context.Context, issuer string, authorizer func() (interface{}, error), options ...Option) (*resourceServer, error) {
|
||||
rs := &resourceServer{
|
||||
issuer: issuer,
|
||||
httpClient: httphelper.DefaultHTTPClient,
|
||||
|
@ -73,7 +73,7 @@ func newResourceServer(issuer string, authorizer func() (interface{}, error), op
|
|||
optFunc(rs)
|
||||
}
|
||||
if rs.introspectURL == "" || rs.tokenURL == "" {
|
||||
config, err := client.Discover(rs.issuer, rs.httpClient)
|
||||
config, err := client.Discover(ctx, rs.issuer, rs.httpClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -87,12 +87,12 @@ func newResourceServer(issuer string, authorizer func() (interface{}, error), op
|
|||
return rs, nil
|
||||
}
|
||||
|
||||
func NewResourceServerFromKeyFile(issuer, path string, options ...Option) (ResourceServer, error) {
|
||||
func NewResourceServerFromKeyFile(ctx context.Context, issuer, path string, options ...Option) (ResourceServer, error) {
|
||||
c, err := client.ConfigFromKeyFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewResourceServerJWTProfile(issuer, c.ClientID, c.KeyID, []byte(c.Key), options...)
|
||||
return NewResourceServerJWTProfile(ctx, issuer, c.ClientID, c.KeyID, []byte(c.Key), options...)
|
||||
}
|
||||
|
||||
type Option func(*resourceServer)
|
||||
|
@ -117,7 +117,7 @@ func Introspect(ctx context.Context, rp ResourceServer, token string) (*oidc.Int
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := httphelper.FormRequest(rp.IntrospectionURL(), &oidc.IntrospectionRequest{Token: token}, client.Encoder, authFn)
|
||||
req, err := httphelper.FormRequest(ctx, rp.IntrospectionURL(), &oidc.IntrospectionRequest{Token: token}, client.Encoder, authFn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
package tokenexchange
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/client"
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/client"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
type TokenExchanger interface {
|
||||
|
@ -21,18 +22,18 @@ type OAuthTokenExchange struct {
|
|||
authFn func() (interface{}, error)
|
||||
}
|
||||
|
||||
func NewTokenExchanger(issuer string, options ...func(source *OAuthTokenExchange)) (TokenExchanger, error) {
|
||||
return newOAuthTokenExchange(issuer, nil, options...)
|
||||
func NewTokenExchanger(ctx context.Context, issuer string, options ...func(source *OAuthTokenExchange)) (TokenExchanger, error) {
|
||||
return newOAuthTokenExchange(ctx, issuer, nil, options...)
|
||||
}
|
||||
|
||||
func NewTokenExchangerClientCredentials(issuer, clientID, clientSecret string, options ...func(source *OAuthTokenExchange)) (TokenExchanger, error) {
|
||||
func NewTokenExchangerClientCredentials(ctx context.Context, issuer, clientID, clientSecret string, options ...func(source *OAuthTokenExchange)) (TokenExchanger, error) {
|
||||
authorizer := func() (interface{}, error) {
|
||||
return httphelper.AuthorizeBasic(clientID, clientSecret), nil
|
||||
}
|
||||
return newOAuthTokenExchange(issuer, authorizer, options...)
|
||||
return newOAuthTokenExchange(ctx, issuer, authorizer, options...)
|
||||
}
|
||||
|
||||
func newOAuthTokenExchange(issuer string, authorizer func() (interface{}, error), options ...func(source *OAuthTokenExchange)) (*OAuthTokenExchange, error) {
|
||||
func newOAuthTokenExchange(ctx context.Context, issuer string, authorizer func() (interface{}, error), options ...func(source *OAuthTokenExchange)) (*OAuthTokenExchange, error) {
|
||||
te := &OAuthTokenExchange{
|
||||
httpClient: httphelper.DefaultHTTPClient,
|
||||
}
|
||||
|
@ -41,7 +42,7 @@ func newOAuthTokenExchange(issuer string, authorizer func() (interface{}, error)
|
|||
}
|
||||
|
||||
if te.tokenEndpoint == "" {
|
||||
config, err := client.Discover(issuer, te.httpClient)
|
||||
config, err := client.Discover(ctx, issuer, te.httpClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -89,6 +90,7 @@ func (te *OAuthTokenExchange) AuthFn() (interface{}, error) {
|
|||
// ExchangeToken sends a token exchange request (rfc 8693) to te's token endpoint.
|
||||
// SubjectToken and SubjectTokenType are required parameters.
|
||||
func ExchangeToken(
|
||||
ctx context.Context,
|
||||
te TokenExchanger,
|
||||
SubjectToken string,
|
||||
SubjectTokenType oidc.TokenType,
|
||||
|
@ -123,5 +125,5 @@ func ExchangeToken(
|
|||
RequestedTokenType: RequestedTokenType,
|
||||
}
|
||||
|
||||
return client.CallTokenExchangeEndpoint(request, authFn, te)
|
||||
return client.CallTokenExchangeEndpoint(ctx, request, authFn, te)
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ func AuthorizeBasic(user, password string) RequestAuthorization {
|
|||
}
|
||||
}
|
||||
|
||||
func FormRequest(endpoint string, request interface{}, encoder Encoder, authFn interface{}) (*http.Request, error) {
|
||||
func FormRequest(ctx context.Context, endpoint string, request interface{}, encoder Encoder, authFn interface{}) (*http.Request, error) {
|
||||
form := url.Values{}
|
||||
if err := encoder.Encode(request, form); err != nil {
|
||||
return nil, err
|
||||
|
@ -42,7 +42,7 @@ func FormRequest(endpoint string, request interface{}, encoder Encoder, authFn i
|
|||
fn(form)
|
||||
}
|
||||
body := strings.NewReader(form.Encode())
|
||||
req, err := http.NewRequest("POST", endpoint, body)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package oidc
|
|||
import (
|
||||
"crypto/sha256"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/crypto"
|
||||
"github.com/zitadel/oidc/v3/pkg/crypto"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -8,8 +8,7 @@ import (
|
|||
"golang.org/x/oauth2"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/zitadel/oidc/v2/pkg/crypto"
|
||||
"github.com/zitadel/oidc/v3/pkg/crypto"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/schema"
|
||||
"github.com/zitadel/schema"
|
||||
"golang.org/x/text/language"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
|
|
@ -9,9 +9,9 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/schema"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/zitadel/schema"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
str "github.com/zitadel/oidc/v2/pkg/strings"
|
||||
str "github.com/zitadel/oidc/v3/pkg/strings"
|
||||
)
|
||||
|
||||
type Claims interface {
|
||||
|
@ -61,10 +61,19 @@ var (
|
|||
ErrAtHash = errors.New("at_hash does not correspond to access token")
|
||||
)
|
||||
|
||||
type Verifier interface {
|
||||
Issuer() string
|
||||
MaxAgeIAT() time.Duration
|
||||
Offset() time.Duration
|
||||
// Verifier caries configuration for the various token verification
|
||||
// functions. Use package specific constructor functions to know
|
||||
// which values need to be set.
|
||||
type Verifier struct {
|
||||
Issuer string
|
||||
MaxAgeIAT time.Duration
|
||||
Offset time.Duration
|
||||
ClientID string
|
||||
SupportedSignAlgs []string
|
||||
MaxAge time.Duration
|
||||
ACR ACRVerifier
|
||||
KeySet KeySet
|
||||
Nonce func(ctx context.Context) string
|
||||
}
|
||||
|
||||
// ACRVerifier specifies the function to be used by the `DefaultVerifier` for validating the acr claim
|
||||
|
@ -121,6 +130,11 @@ func CheckAudience(claims Claims, clientID string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// CheckAuthorizedParty checks azp (authorized party) claim requirements.
|
||||
//
|
||||
// If the ID Token contains multiple audiences, the Client SHOULD verify that an azp Claim is present.
|
||||
// If an azp Claim is present, the Client SHOULD verify that its client_id is the Claim Value.
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
|
||||
func CheckAuthorizedParty(claims Claims, clientID string) error {
|
||||
if len(claims.GetAudience()) > 1 {
|
||||
if claims.GetAuthorizedParty() == "" {
|
||||
|
@ -167,26 +181,26 @@ func CheckSignature(ctx context.Context, token string, payload []byte, claims Cl
|
|||
}
|
||||
|
||||
func CheckExpiration(claims Claims, offset time.Duration) error {
|
||||
expiration := claims.GetExpiration().Round(time.Second)
|
||||
if !time.Now().UTC().Add(offset).Before(expiration) {
|
||||
expiration := claims.GetExpiration()
|
||||
if !time.Now().Add(offset).Before(expiration) {
|
||||
return ErrExpired
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CheckIssuedAt(claims Claims, maxAgeIAT, offset time.Duration) error {
|
||||
issuedAt := claims.GetIssuedAt().Round(time.Second)
|
||||
issuedAt := claims.GetIssuedAt()
|
||||
if issuedAt.IsZero() {
|
||||
return ErrIatMissing
|
||||
}
|
||||
nowWithOffset := time.Now().UTC().Add(offset).Round(time.Second)
|
||||
nowWithOffset := time.Now().Add(offset).Round(time.Second)
|
||||
if issuedAt.After(nowWithOffset) {
|
||||
return fmt.Errorf("%w: (iat: %v, now with offset: %v)", ErrIatInFuture, issuedAt, nowWithOffset)
|
||||
}
|
||||
if maxAgeIAT == 0 {
|
||||
return nil
|
||||
}
|
||||
maxAge := time.Now().UTC().Add(-maxAgeIAT).Round(time.Second)
|
||||
maxAge := time.Now().Add(-maxAgeIAT).Round(time.Second)
|
||||
if issuedAt.Before(maxAge) {
|
||||
return fmt.Errorf("%w: must not be older than %v, but was %v (%v to old)", ErrIatToOld, maxAge, issuedAt, maxAge.Sub(issuedAt))
|
||||
}
|
||||
|
@ -216,8 +230,8 @@ func CheckAuthTime(claims Claims, maxAge time.Duration) error {
|
|||
if claims.GetAuthTime().IsZero() {
|
||||
return ErrAuthTimeNotPresent
|
||||
}
|
||||
authTime := claims.GetAuthTime().Round(time.Second)
|
||||
maxAuthTime := time.Now().UTC().Add(-maxAge).Round(time.Second)
|
||||
authTime := claims.GetAuthTime()
|
||||
maxAuthTime := time.Now().Add(-maxAge).Round(time.Second)
|
||||
if authTime.Before(maxAuthTime) {
|
||||
return fmt.Errorf("%w: must not be older than %v, but was %v (%v to old)", ErrAuthTimeToOld, maxAge, authTime, maxAuthTime.Sub(authTime))
|
||||
}
|
||||
|
|
128
pkg/oidc/verifier_parse_test.go
Normal file
128
pkg/oidc/verifier_parse_test.go
Normal file
|
@ -0,0 +1,128 @@
|
|||
package oidc_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
tu "github.com/zitadel/oidc/v3/internal/testutil"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
func TestParseToken(t *testing.T) {
|
||||
token, wantClaims := tu.ValidIDToken()
|
||||
wantClaims.SignatureAlg = "" // unset, because is not part of the JSON payload
|
||||
|
||||
wantPayload, err := json.Marshal(wantClaims)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
tokenString string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "split error",
|
||||
tokenString: "nope",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "base64 error",
|
||||
tokenString: "foo.~.bar",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "success",
|
||||
tokenString: token,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotClaims := new(oidc.IDTokenClaims)
|
||||
gotPayload, err := oidc.ParseToken(tt.tokenString, gotClaims)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, wantClaims, gotClaims)
|
||||
assert.JSONEq(t, string(wantPayload), string(gotPayload))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckSignature(t *testing.T) {
|
||||
errCtx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
token, _ := tu.ValidIDToken()
|
||||
payload, err := oidc.ParseToken(token, &oidc.IDTokenClaims{})
|
||||
require.NoError(t, err)
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
token string
|
||||
payload []byte
|
||||
supportedSigAlgs []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "parse error",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
token: "~",
|
||||
payload: payload,
|
||||
},
|
||||
wantErr: oidc.ErrParse,
|
||||
},
|
||||
{
|
||||
name: "default sigAlg",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
token: token,
|
||||
payload: payload,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unsupported sigAlg",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
token: token,
|
||||
payload: payload,
|
||||
supportedSigAlgs: []string{"foo", "bar"},
|
||||
},
|
||||
wantErr: oidc.ErrSignatureUnsupportedAlg,
|
||||
},
|
||||
{
|
||||
name: "verify error",
|
||||
args: args{
|
||||
ctx: errCtx,
|
||||
token: token,
|
||||
payload: payload,
|
||||
},
|
||||
wantErr: oidc.ErrSignatureInvalid,
|
||||
},
|
||||
{
|
||||
name: "inequal payloads",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
token: token,
|
||||
payload: []byte{0, 1, 2},
|
||||
},
|
||||
wantErr: oidc.ErrSignatureInvalidPayload,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
claims := new(oidc.TokenClaims)
|
||||
err := oidc.CheckSignature(tt.args.ctx, tt.args.token, tt.args.payload, claims, tt.args.supportedSigAlgs, tu.KeySet{})
|
||||
assert.ErrorIs(t, err, tt.wantErr)
|
||||
})
|
||||
}
|
||||
}
|
374
pkg/oidc/verifier_test.go
Normal file
374
pkg/oidc/verifier_test.go
Normal file
|
@ -0,0 +1,374 @@
|
|||
package oidc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDecryptToken(t *testing.T) {
|
||||
const tokenString = "ABC"
|
||||
got, err := DecryptToken(tokenString)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tokenString, got)
|
||||
}
|
||||
|
||||
func TestDefaultACRVerifier(t *testing.T) {
|
||||
acrVerfier := DefaultACRVerifier([]string{"foo", "bar"})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
acr string
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "ok",
|
||||
acr: "bar",
|
||||
},
|
||||
{
|
||||
name: "error",
|
||||
acr: "hello",
|
||||
wantErr: "expected one of: [foo bar], got: \"hello\"",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := acrVerfier(tt.acr)
|
||||
if tt.wantErr != "" {
|
||||
assert.EqualError(t, err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckSubject(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
claims Claims
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "missing",
|
||||
claims: &TokenClaims{},
|
||||
wantErr: ErrSubjectMissing,
|
||||
},
|
||||
{
|
||||
name: "ok",
|
||||
claims: &TokenClaims{
|
||||
Subject: "foo",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := CheckSubject(tt.claims)
|
||||
assert.ErrorIs(t, err, tt.wantErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckIssuer(t *testing.T) {
|
||||
const issuer = "foo.bar"
|
||||
tests := []struct {
|
||||
name string
|
||||
claims Claims
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "missing",
|
||||
claims: &TokenClaims{},
|
||||
wantErr: ErrIssuerInvalid,
|
||||
},
|
||||
{
|
||||
name: "wrong",
|
||||
claims: &TokenClaims{
|
||||
Issuer: "wrong",
|
||||
},
|
||||
wantErr: ErrIssuerInvalid,
|
||||
},
|
||||
{
|
||||
name: "ok",
|
||||
claims: &TokenClaims{
|
||||
Issuer: issuer,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := CheckIssuer(tt.claims, issuer)
|
||||
assert.ErrorIs(t, err, tt.wantErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckAudience(t *testing.T) {
|
||||
const clientID = "foo.bar"
|
||||
tests := []struct {
|
||||
name string
|
||||
claims Claims
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "missing",
|
||||
claims: &TokenClaims{},
|
||||
wantErr: ErrAudience,
|
||||
},
|
||||
{
|
||||
name: "wrong",
|
||||
claims: &TokenClaims{
|
||||
Audience: []string{"wrong"},
|
||||
},
|
||||
wantErr: ErrAudience,
|
||||
},
|
||||
{
|
||||
name: "ok",
|
||||
claims: &TokenClaims{
|
||||
Audience: []string{clientID},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := CheckAudience(tt.claims, clientID)
|
||||
assert.ErrorIs(t, err, tt.wantErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckAuthorizedParty(t *testing.T) {
|
||||
const clientID = "foo.bar"
|
||||
tests := []struct {
|
||||
name string
|
||||
claims Claims
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "single audience, no azp",
|
||||
claims: &TokenClaims{
|
||||
Audience: []string{clientID},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple audience, no azp",
|
||||
claims: &TokenClaims{
|
||||
Audience: []string{clientID, "other"},
|
||||
},
|
||||
wantErr: ErrAzpMissing,
|
||||
},
|
||||
{
|
||||
name: "single audience, with azp",
|
||||
claims: &TokenClaims{
|
||||
Audience: []string{clientID},
|
||||
AuthorizedParty: clientID,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple audience, with azp",
|
||||
claims: &TokenClaims{
|
||||
Audience: []string{clientID, "other"},
|
||||
AuthorizedParty: clientID,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "wrong azp",
|
||||
claims: &TokenClaims{
|
||||
AuthorizedParty: "wrong",
|
||||
},
|
||||
wantErr: ErrAzpInvalid,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := CheckAuthorizedParty(tt.claims, clientID)
|
||||
assert.ErrorIs(t, err, tt.wantErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckExpiration(t *testing.T) {
|
||||
const offset = time.Minute
|
||||
tests := []struct {
|
||||
name string
|
||||
claims Claims
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "missing",
|
||||
claims: &TokenClaims{},
|
||||
wantErr: ErrExpired,
|
||||
},
|
||||
{
|
||||
name: "expired",
|
||||
claims: &TokenClaims{
|
||||
Expiration: FromTime(time.Now().Add(-2 * offset)),
|
||||
},
|
||||
wantErr: ErrExpired,
|
||||
},
|
||||
{
|
||||
name: "valid",
|
||||
claims: &TokenClaims{
|
||||
Expiration: FromTime(time.Now().Add(2 * offset)),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := CheckExpiration(tt.claims, offset)
|
||||
assert.ErrorIs(t, err, tt.wantErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckIssuedAt(t *testing.T) {
|
||||
const offset = time.Minute
|
||||
tests := []struct {
|
||||
name string
|
||||
maxAgeIAT time.Duration
|
||||
claims Claims
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "missing",
|
||||
claims: &TokenClaims{},
|
||||
wantErr: ErrIatMissing,
|
||||
},
|
||||
{
|
||||
name: "future",
|
||||
claims: &TokenClaims{
|
||||
IssuedAt: FromTime(time.Now().Add(time.Hour)),
|
||||
},
|
||||
wantErr: ErrIatInFuture,
|
||||
},
|
||||
{
|
||||
name: "no max",
|
||||
claims: &TokenClaims{
|
||||
IssuedAt: FromTime(time.Now()),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "past max",
|
||||
maxAgeIAT: time.Minute,
|
||||
claims: &TokenClaims{
|
||||
IssuedAt: FromTime(time.Now().Add(-time.Hour)),
|
||||
},
|
||||
wantErr: ErrIatToOld,
|
||||
},
|
||||
{
|
||||
name: "within max",
|
||||
maxAgeIAT: time.Hour,
|
||||
claims: &TokenClaims{
|
||||
IssuedAt: FromTime(time.Now()),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := CheckIssuedAt(tt.claims, tt.maxAgeIAT, offset)
|
||||
assert.ErrorIs(t, err, tt.wantErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckNonce(t *testing.T) {
|
||||
const nonce = "123"
|
||||
tests := []struct {
|
||||
name string
|
||||
claims Claims
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "missing",
|
||||
claims: &TokenClaims{},
|
||||
wantErr: ErrNonceInvalid,
|
||||
},
|
||||
{
|
||||
name: "wrong",
|
||||
claims: &TokenClaims{
|
||||
Nonce: "wrong",
|
||||
},
|
||||
wantErr: ErrNonceInvalid,
|
||||
},
|
||||
{
|
||||
name: "ok",
|
||||
claims: &TokenClaims{
|
||||
Nonce: nonce,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := CheckNonce(tt.claims, nonce)
|
||||
assert.ErrorIs(t, err, tt.wantErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckAuthorizationContextClassReference(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
acr ACRVerifier
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "error",
|
||||
acr: func(s string) error { return errors.New("oops") },
|
||||
wantErr: ErrAcrInvalid,
|
||||
},
|
||||
{
|
||||
name: "ok",
|
||||
acr: func(s string) error { return nil },
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := CheckAuthorizationContextClassReference(&IDTokenClaims{}, tt.acr)
|
||||
assert.ErrorIs(t, err, tt.wantErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckAuthTime(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
claims Claims
|
||||
maxAge time.Duration
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "no max age",
|
||||
claims: &TokenClaims{},
|
||||
},
|
||||
{
|
||||
name: "missing",
|
||||
claims: &TokenClaims{},
|
||||
maxAge: time.Minute,
|
||||
wantErr: ErrAuthTimeNotPresent,
|
||||
},
|
||||
{
|
||||
name: "expired",
|
||||
maxAge: time.Minute,
|
||||
claims: &TokenClaims{
|
||||
AuthTime: FromTime(time.Now().Add(-time.Hour)),
|
||||
},
|
||||
wantErr: ErrAuthTimeToOld,
|
||||
},
|
||||
{
|
||||
name: "ok",
|
||||
maxAge: time.Minute,
|
||||
claims: &TokenClaims{
|
||||
AuthTime: NowTime(),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := CheckAuthTime(tt.claims, tt.maxAge)
|
||||
assert.ErrorIs(t, err, tt.wantErr)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ package op
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
@ -10,11 +11,9 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
str "github.com/zitadel/oidc/v2/pkg/strings"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
str "github.com/zitadel/oidc/v3/pkg/strings"
|
||||
)
|
||||
|
||||
type AuthRequest interface {
|
||||
|
@ -39,7 +38,7 @@ type Authorizer interface {
|
|||
Storage() Storage
|
||||
Decoder() httphelper.Decoder
|
||||
Encoder() httphelper.Encoder
|
||||
IDTokenHintVerifier(context.Context) IDTokenHintVerifier
|
||||
IDTokenHintVerifier(context.Context) *IDTokenHintVerifier
|
||||
Crypto() Crypto
|
||||
RequestObjectSupported() bool
|
||||
}
|
||||
|
@ -48,7 +47,7 @@ type Authorizer interface {
|
|||
// implementing its own validation mechanism for the auth request
|
||||
type AuthorizeValidator interface {
|
||||
Authorizer
|
||||
ValidateAuthRequest(context.Context, *oidc.AuthRequest, Storage, IDTokenHintVerifier) (string, error)
|
||||
ValidateAuthRequest(context.Context, *oidc.AuthRequest, Storage, *IDTokenHintVerifier) (string, error)
|
||||
}
|
||||
|
||||
func authorizeHandler(authorizer Authorizer) func(http.ResponseWriter, *http.Request) {
|
||||
|
@ -205,7 +204,7 @@ func CopyRequestObjectToAuthRequest(authReq *oidc.AuthRequest, requestObject *oi
|
|||
}
|
||||
|
||||
// ValidateAuthRequest validates the authorize parameters and returns the userID of the id_token_hint if passed
|
||||
func ValidateAuthRequest(ctx context.Context, authReq *oidc.AuthRequest, storage Storage, verifier IDTokenHintVerifier) (sub string, err error) {
|
||||
func ValidateAuthRequest(ctx context.Context, authReq *oidc.AuthRequest, storage Storage, verifier *IDTokenHintVerifier) (sub string, err error) {
|
||||
authReq.MaxAge, err = ValidateAuthReqPrompt(authReq.Prompt, authReq.MaxAge)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -385,7 +384,7 @@ func ValidateAuthReqResponseType(client Client, responseType oidc.ResponseType)
|
|||
|
||||
// ValidateAuthReqIDTokenHint validates the id_token_hint (if passed as parameter in the request)
|
||||
// and returns the `sub` claim
|
||||
func ValidateAuthReqIDTokenHint(ctx context.Context, idTokenHint string, verifier IDTokenHintVerifier) (string, error) {
|
||||
func ValidateAuthReqIDTokenHint(ctx context.Context, idTokenHint string, verifier *IDTokenHintVerifier) (string, error) {
|
||||
if idTokenHint == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
@ -405,13 +404,11 @@ func RedirectToLogin(authReqID string, client Client, w http.ResponseWriter, r *
|
|||
|
||||
// AuthorizeCallback handles the callback after authentication in the Login UI
|
||||
func AuthorizeCallback(w http.ResponseWriter, r *http.Request, authorizer Authorizer) {
|
||||
params := mux.Vars(r)
|
||||
id := params["id"]
|
||||
if id == "" {
|
||||
AuthRequestError(w, r, nil, fmt.Errorf("auth request callback is missing id"), authorizer.Encoder())
|
||||
id, err := ParseAuthorizeCallbackRequest(r)
|
||||
if err != nil {
|
||||
AuthRequestError(w, r, nil, err, authorizer.Encoder())
|
||||
return
|
||||
}
|
||||
|
||||
authReq, err := authorizer.Storage().AuthRequestByID(r.Context(), id)
|
||||
if err != nil {
|
||||
AuthRequestError(w, r, nil, err, authorizer.Encoder())
|
||||
|
@ -426,6 +423,17 @@ func AuthorizeCallback(w http.ResponseWriter, r *http.Request, authorizer Author
|
|||
AuthResponse(authReq, authorizer, w, r)
|
||||
}
|
||||
|
||||
func ParseAuthorizeCallbackRequest(r *http.Request) (id string, err error) {
|
||||
if err = r.ParseForm(); err != nil {
|
||||
return "", fmt.Errorf("cannot parse form: %w", err)
|
||||
}
|
||||
id = r.Form.Get("id")
|
||||
if id == "" {
|
||||
return "", errors.New("auth request callback is missing id")
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// AuthResponse creates the successful authentication response (either code or tokens)
|
||||
func AuthResponse(authReq AuthRequest, authorizer Authorizer, w http.ResponseWriter, r *http.Request) {
|
||||
client, err := authorizer.Storage().GetClientByClientID(r.Context(), authReq.GetClientID())
|
||||
|
|
|
@ -9,15 +9,14 @@ import (
|
|||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/gorilla/schema"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
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"
|
||||
tu "github.com/zitadel/oidc/v3/internal/testutil"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
"github.com/zitadel/oidc/v3/pkg/op/mock"
|
||||
"github.com/zitadel/schema"
|
||||
)
|
||||
|
||||
func TestAuthorize(t *testing.T) {
|
||||
|
@ -122,7 +121,7 @@ func TestValidateAuthRequest(t *testing.T) {
|
|||
type args struct {
|
||||
authRequest *oidc.AuthRequest
|
||||
storage op.Storage
|
||||
verifier op.IDTokenHintVerifier
|
||||
verifier *op.IDTokenHintVerifier
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -942,3 +941,71 @@ func (m *mockEncoder) Encode(src interface{}, dst map[string][]string) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Test_parseAuthorizeCallbackRequest(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
url string
|
||||
wantId string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "parse error",
|
||||
url: "/?id;=99",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing id",
|
||||
url: "/",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "ok",
|
||||
url: "/?id=99",
|
||||
wantId: "99",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := httptest.NewRequest(http.MethodGet, tt.url, nil)
|
||||
gotId, err := op.ParseAuthorizeCallbackRequest(r)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, tt.wantId, gotId)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateAuthReqIDTokenHint(t *testing.T) {
|
||||
token, _ := tu.ValidIDToken()
|
||||
tests := []struct {
|
||||
name string
|
||||
idTokenHint string
|
||||
want string
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
},
|
||||
{
|
||||
name: "verify err",
|
||||
idTokenHint: "foo",
|
||||
wantErr: oidc.ErrLoginRequired(),
|
||||
},
|
||||
{
|
||||
name: "ok",
|
||||
idTokenHint: token,
|
||||
want: tu.ValidSubject,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := op.ValidateAuthReqIDTokenHint(context.Background(), tt.idTokenHint, op.NewIDTokenHintVerifier(tu.ValidIssuer, tu.KeySet{}))
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,8 @@ import (
|
|||
"net/url"
|
||||
"time"
|
||||
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
//go:generate go get github.com/dmarkham/enumer
|
||||
|
@ -87,7 +87,7 @@ var (
|
|||
)
|
||||
|
||||
type ClientJWTProfile interface {
|
||||
JWTProfileVerifier(context.Context) JWTProfileVerifier
|
||||
JWTProfileVerifier(context.Context) *JWTProfileVerifier
|
||||
}
|
||||
|
||||
func ClientJWTAuth(ctx context.Context, ca oidc.ClientAssertionParams, verifier ClientJWTProfile) (clientID string, err error) {
|
||||
|
|
|
@ -11,18 +11,18 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/gorilla/schema"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
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"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
"github.com/zitadel/oidc/v3/pkg/op/mock"
|
||||
"github.com/zitadel/schema"
|
||||
)
|
||||
|
||||
type testClientJWTProfile struct{}
|
||||
|
||||
func (testClientJWTProfile) JWTProfileVerifier(context.Context) op.JWTProfileVerifier { return nil }
|
||||
func (testClientJWTProfile) JWTProfileVerifier(context.Context) *op.JWTProfileVerifier { return nil }
|
||||
|
||||
func TestClientJWTAuth(t *testing.T) {
|
||||
type args struct {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package op
|
||||
|
||||
import (
|
||||
"github.com/zitadel/oidc/v2/pkg/crypto"
|
||||
"github.com/zitadel/oidc/v3/pkg/crypto"
|
||||
)
|
||||
|
||||
type Crypto interface {
|
||||
|
|
|
@ -12,8 +12,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
type DeviceAuthorizationConfig struct {
|
||||
|
|
|
@ -16,8 +16,8 @@ import (
|
|||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v2/pkg/op"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
)
|
||||
|
||||
func Test_deviceAuthorizationHandler(t *testing.T) {
|
||||
|
|
|
@ -6,8 +6,8 @@ import (
|
|||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
type DiscoverStorage interface {
|
||||
|
|
|
@ -11,9 +11,9 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v2/pkg/op"
|
||||
"github.com/zitadel/oidc/v2/pkg/op/mock"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
"github.com/zitadel/oidc/v3/pkg/op/mock"
|
||||
)
|
||||
|
||||
func TestDiscover(t *testing.T) {
|
||||
|
|
|
@ -3,7 +3,7 @@ package op_test
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/op"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
)
|
||||
|
||||
func TestEndpoint_Path(t *testing.T) {
|
||||
|
|
|
@ -3,8 +3,8 @@ package op
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
type ErrAuthRequest interface {
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
)
|
||||
|
||||
type KeyProvider interface {
|
||||
|
|
|
@ -11,9 +11,9 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v2/pkg/op"
|
||||
"github.com/zitadel/oidc/v2/pkg/op/mock"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
"github.com/zitadel/oidc/v3/pkg/op/mock"
|
||||
)
|
||||
|
||||
func TestKeys(t *testing.T) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/zitadel/oidc/v2/pkg/op (interfaces: Authorizer)
|
||||
// Source: github.com/zitadel/oidc/v3/pkg/op (interfaces: Authorizer)
|
||||
|
||||
// Package mock is a generated GoMock package.
|
||||
package mock
|
||||
|
@ -9,8 +9,8 @@ import (
|
|||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
http "github.com/zitadel/oidc/v2/pkg/http"
|
||||
op "github.com/zitadel/oidc/v2/pkg/op"
|
||||
http "github.com/zitadel/oidc/v3/pkg/http"
|
||||
op "github.com/zitadel/oidc/v3/pkg/op"
|
||||
)
|
||||
|
||||
// MockAuthorizer is a mock of Authorizer interface.
|
||||
|
@ -79,10 +79,10 @@ func (mr *MockAuthorizerMockRecorder) Encoder() *gomock.Call {
|
|||
}
|
||||
|
||||
// IDTokenHintVerifier mocks base method.
|
||||
func (m *MockAuthorizer) IDTokenHintVerifier(arg0 context.Context) op.IDTokenHintVerifier {
|
||||
func (m *MockAuthorizer) IDTokenHintVerifier(arg0 context.Context) *op.IDTokenHintVerifier {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "IDTokenHintVerifier", arg0)
|
||||
ret0, _ := ret[0].(op.IDTokenHintVerifier)
|
||||
ret0, _ := ret[0].(*op.IDTokenHintVerifier)
|
||||
return ret0
|
||||
}
|
||||
|
||||
|
|
|
@ -5,11 +5,11 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/gorilla/schema"
|
||||
"github.com/zitadel/schema"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v2/pkg/op"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
)
|
||||
|
||||
func NewAuthorizer(t *testing.T) op.Authorizer {
|
||||
|
@ -49,7 +49,7 @@ func ExpectEncoder(a op.Authorizer) {
|
|||
func ExpectVerifier(a op.Authorizer, t *testing.T) {
|
||||
mockA := a.(*MockAuthorizer)
|
||||
mockA.EXPECT().IDTokenHintVerifier(gomock.Any()).DoAndReturn(
|
||||
func() op.IDTokenHintVerifier {
|
||||
func() *op.IDTokenHintVerifier {
|
||||
return op.NewIDTokenHintVerifier("", nil)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ import (
|
|||
|
||||
"github.com/golang/mock/gomock"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v2/pkg/op"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
)
|
||||
|
||||
func NewClient(t *testing.T) op.Client {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/zitadel/oidc/v2/pkg/op (interfaces: Client)
|
||||
// Source: github.com/zitadel/oidc/v3/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/v2/pkg/oidc"
|
||||
op "github.com/zitadel/oidc/v2/pkg/op"
|
||||
oidc "github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
op "github.com/zitadel/oidc/v3/pkg/op"
|
||||
)
|
||||
|
||||
// MockClient is a mock of Client interface.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/zitadel/oidc/v2/pkg/op (interfaces: Configuration)
|
||||
// Source: github.com/zitadel/oidc/v3/pkg/op (interfaces: Configuration)
|
||||
|
||||
// Package mock is a generated GoMock package.
|
||||
package mock
|
||||
|
@ -9,7 +9,7 @@ import (
|
|||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
op "github.com/zitadel/oidc/v2/pkg/op"
|
||||
op "github.com/zitadel/oidc/v3/pkg/op"
|
||||
language "golang.org/x/text/language"
|
||||
)
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/zitadel/oidc/v2/pkg/op (interfaces: DiscoverStorage)
|
||||
// Source: github.com/zitadel/oidc/v3/pkg/op (interfaces: DiscoverStorage)
|
||||
|
||||
// Package mock is a generated GoMock package.
|
||||
package mock
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package mock
|
||||
|
||||
//go:generate go install github.com/golang/mock/mockgen@v1.6.0
|
||||
//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
|
||||
//go:generate mockgen -package mock -destination ./storage.mock.go github.com/zitadel/oidc/v3/pkg/op Storage
|
||||
//go:generate mockgen -package mock -destination ./authorizer.mock.go github.com/zitadel/oidc/v3/pkg/op Authorizer
|
||||
//go:generate mockgen -package mock -destination ./client.mock.go github.com/zitadel/oidc/v3/pkg/op Client
|
||||
//go:generate mockgen -package mock -destination ./configuration.mock.go github.com/zitadel/oidc/v3/pkg/op Configuration
|
||||
//go:generate mockgen -package mock -destination ./discovery.mock.go github.com/zitadel/oidc/v3/pkg/op DiscoverStorage
|
||||
//go:generate mockgen -package mock -destination ./signer.mock.go github.com/zitadel/oidc/v3/pkg/op SigningKey,Key
|
||||
//go:generate mockgen -package mock -destination ./key.mock.go github.com/zitadel/oidc/v3/pkg/op KeyProvider
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/zitadel/oidc/v2/pkg/op (interfaces: KeyProvider)
|
||||
// Source: github.com/zitadel/oidc/v3/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"
|
||||
op "github.com/zitadel/oidc/v2/pkg/op"
|
||||
op "github.com/zitadel/oidc/v3/pkg/op"
|
||||
)
|
||||
|
||||
// MockKeyProvider is a mock of KeyProvider interface.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/zitadel/oidc/v2/pkg/op (interfaces: SigningKey,Key)
|
||||
// Source: github.com/zitadel/oidc/v3/pkg/op (interfaces: SigningKey,Key)
|
||||
|
||||
// Package mock is a generated GoMock package.
|
||||
package mock
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/zitadel/oidc/v2/pkg/op (interfaces: Storage)
|
||||
// Source: github.com/zitadel/oidc/v3/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/v2/pkg/oidc"
|
||||
op "github.com/zitadel/oidc/v2/pkg/op"
|
||||
oidc "github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
op "github.com/zitadel/oidc/v3/pkg/op"
|
||||
jose "gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@ import (
|
|||
|
||||
"github.com/golang/mock/gomock"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v2/pkg/op"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
)
|
||||
|
||||
func NewStorage(t *testing.T) op.Storage {
|
||||
|
|
34
pkg/op/op.go
34
pkg/op/op.go
|
@ -6,14 +6,14 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/gorilla/schema"
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/rs/cors"
|
||||
"github.com/zitadel/schema"
|
||||
"golang.org/x/text/language"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -68,29 +68,32 @@ var (
|
|||
)
|
||||
|
||||
type OpenIDProvider interface {
|
||||
http.Handler
|
||||
Configuration
|
||||
Storage() Storage
|
||||
Decoder() httphelper.Decoder
|
||||
Encoder() httphelper.Encoder
|
||||
IDTokenHintVerifier(context.Context) IDTokenHintVerifier
|
||||
AccessTokenVerifier(context.Context) AccessTokenVerifier
|
||||
IDTokenHintVerifier(context.Context) *IDTokenHintVerifier
|
||||
AccessTokenVerifier(context.Context) *AccessTokenVerifier
|
||||
Crypto() Crypto
|
||||
DefaultLogoutRedirectURI() string
|
||||
Probes() []ProbesFn
|
||||
|
||||
// Deprecated: Provider now implements http.Handler directly.
|
||||
HttpHandler() http.Handler
|
||||
}
|
||||
|
||||
type HttpInterceptor func(http.Handler) http.Handler
|
||||
|
||||
func CreateRouter(o OpenIDProvider, interceptors ...HttpInterceptor) *mux.Router {
|
||||
router := mux.NewRouter()
|
||||
func CreateRouter(o OpenIDProvider, interceptors ...HttpInterceptor) chi.Router {
|
||||
router := chi.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.Storage()))
|
||||
router.HandleFunc(o.AuthorizationEndpoint().Relative(), authorizeHandler(o))
|
||||
router.NewRoute().Path(authCallbackPath(o)).Queries("id", "{id}").HandlerFunc(authorizeCallbackHandler(o))
|
||||
router.HandleFunc(authCallbackPath(o), authorizeCallbackHandler(o))
|
||||
router.HandleFunc(o.TokenEndpoint().Relative(), tokenHandler(o))
|
||||
router.HandleFunc(o.IntrospectionEndpoint().Relative(), introspectionHandler(o))
|
||||
router.HandleFunc(o.UserinfoEndpoint().Relative(), userinfoHandler(o))
|
||||
|
@ -184,7 +187,7 @@ func newProvider(config *Config, storage Storage, issuer func(bool) (IssuerFromR
|
|||
return nil, err
|
||||
}
|
||||
|
||||
o.httpHandler = CreateRouter(o, o.interceptors...)
|
||||
o.Handler = CreateRouter(o, o.interceptors...)
|
||||
|
||||
o.decoder = schema.NewDecoder()
|
||||
o.decoder.IgnoreUnknownKeys(true)
|
||||
|
@ -200,6 +203,7 @@ func newProvider(config *Config, storage Storage, issuer func(bool) (IssuerFromR
|
|||
}
|
||||
|
||||
type Provider struct {
|
||||
http.Handler
|
||||
config *Config
|
||||
issuer IssuerFromRequest
|
||||
insecure bool
|
||||
|
@ -207,7 +211,6 @@ type Provider struct {
|
|||
storage Storage
|
||||
keySet *openIDKeySet
|
||||
crypto Crypto
|
||||
httpHandler http.Handler
|
||||
decoder *schema.Decoder
|
||||
encoder *schema.Encoder
|
||||
interceptors []HttpInterceptor
|
||||
|
@ -339,15 +342,15 @@ func (o *Provider) Encoder() httphelper.Encoder {
|
|||
return o.encoder
|
||||
}
|
||||
|
||||
func (o *Provider) IDTokenHintVerifier(ctx context.Context) IDTokenHintVerifier {
|
||||
func (o *Provider) IDTokenHintVerifier(ctx context.Context) *IDTokenHintVerifier {
|
||||
return NewIDTokenHintVerifier(IssuerFromContext(ctx), o.openIDKeySet(), o.idTokenHintVerifierOpts...)
|
||||
}
|
||||
|
||||
func (o *Provider) JWTProfileVerifier(ctx context.Context) JWTProfileVerifier {
|
||||
func (o *Provider) JWTProfileVerifier(ctx context.Context) *JWTProfileVerifier {
|
||||
return NewJWTProfileVerifier(o.Storage(), IssuerFromContext(ctx), 1*time.Hour, time.Second)
|
||||
}
|
||||
|
||||
func (o *Provider) AccessTokenVerifier(ctx context.Context) AccessTokenVerifier {
|
||||
func (o *Provider) AccessTokenVerifier(ctx context.Context) *AccessTokenVerifier {
|
||||
return NewAccessTokenVerifier(IssuerFromContext(ctx), o.openIDKeySet(), o.accessTokenVerifierOpts...)
|
||||
}
|
||||
|
||||
|
@ -372,8 +375,9 @@ func (o *Provider) Probes() []ProbesFn {
|
|||
}
|
||||
}
|
||||
|
||||
// Deprecated: Provider now implements http.Handler directly.
|
||||
func (o *Provider) HttpHandler() http.Handler {
|
||||
return o.httpHandler
|
||||
return o
|
||||
}
|
||||
|
||||
type openIDKeySet struct {
|
||||
|
|
|
@ -14,9 +14,9 @@ import (
|
|||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/zitadel/oidc/v2/example/server/storage"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v2/pkg/op"
|
||||
"github.com/zitadel/oidc/v3/example/server/storage"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
|
@ -370,7 +370,7 @@ func TestRoutes(t *testing.T) {
|
|||
}
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
testProvider.HttpHandler().ServeHTTP(rec, req)
|
||||
testProvider.ServeHTTP(rec, req)
|
||||
|
||||
resp := rec.Result()
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"errors"
|
||||
"net/http"
|
||||
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
)
|
||||
|
||||
type ProbesFn func(context.Context) error
|
||||
|
|
|
@ -6,14 +6,14 @@ import (
|
|||
"net/url"
|
||||
"path"
|
||||
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
type SessionEnder interface {
|
||||
Decoder() httphelper.Decoder
|
||||
Storage() Storage
|
||||
IDTokenHintVerifier(context.Context) IDTokenHintVerifier
|
||||
IDTokenHintVerifier(context.Context) *IDTokenHintVerifier
|
||||
DefaultLogoutRedirectURI() string
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
type AuthStorage interface {
|
||||
|
|
|
@ -4,9 +4,9 @@ import (
|
|||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/crypto"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v2/pkg/strings"
|
||||
"github.com/zitadel/oidc/v3/pkg/crypto"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/strings"
|
||||
)
|
||||
|
||||
type TokenCreator interface {
|
||||
|
|
|
@ -5,8 +5,8 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
// ClientCredentialsExchange handles the OAuth 2.0 client_credentials grant, including
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"context"
|
||||
"net/http"
|
||||
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
// CodeExchange handles the OAuth 2.0 authorization_code grant, including
|
||||
|
|
|
@ -7,8 +7,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
type TokenExchangeRequest interface {
|
||||
|
|
|
@ -5,15 +5,15 @@ import (
|
|||
"errors"
|
||||
"net/http"
|
||||
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
type Introspector interface {
|
||||
Decoder() httphelper.Decoder
|
||||
Crypto() Crypto
|
||||
Storage() Storage
|
||||
AccessTokenVerifier(context.Context) AccessTokenVerifier
|
||||
AccessTokenVerifier(context.Context) *AccessTokenVerifier
|
||||
}
|
||||
|
||||
type IntrospectorJWTProfile interface {
|
||||
|
|
|
@ -5,13 +5,13 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
type JWTAuthorizationGrantExchanger interface {
|
||||
Exchanger
|
||||
JWTProfileVerifier(context.Context) JWTProfileVerifier
|
||||
JWTProfileVerifier(context.Context) *JWTProfileVerifier
|
||||
}
|
||||
|
||||
// JWTProfile handles the OAuth 2.0 JWT Profile Authorization Grant https://tools.ietf.org/html/rfc7523#section-2.1
|
||||
|
|
|
@ -6,9 +6,9 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v2/pkg/strings"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/strings"
|
||||
)
|
||||
|
||||
type RefreshTokenRequest interface {
|
||||
|
|
|
@ -5,8 +5,8 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
type Exchanger interface {
|
||||
|
@ -20,8 +20,8 @@ type Exchanger interface {
|
|||
GrantTypeJWTAuthorizationSupported() bool
|
||||
GrantTypeClientCredentialsSupported() bool
|
||||
GrantTypeDeviceCodeSupported() bool
|
||||
AccessTokenVerifier(context.Context) AccessTokenVerifier
|
||||
IDTokenHintVerifier(context.Context) IDTokenHintVerifier
|
||||
AccessTokenVerifier(context.Context) *AccessTokenVerifier
|
||||
IDTokenHintVerifier(context.Context) *IDTokenHintVerifier
|
||||
}
|
||||
|
||||
func tokenHandler(exchanger Exchanger) func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
@ -7,22 +7,22 @@ import (
|
|||
"net/url"
|
||||
"strings"
|
||||
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
type Revoker interface {
|
||||
Decoder() httphelper.Decoder
|
||||
Crypto() Crypto
|
||||
Storage() Storage
|
||||
AccessTokenVerifier(context.Context) AccessTokenVerifier
|
||||
AccessTokenVerifier(context.Context) *AccessTokenVerifier
|
||||
AuthMethodPrivateKeyJWTSupported() bool
|
||||
AuthMethodPostSupported() bool
|
||||
}
|
||||
|
||||
type RevokerJWTProfile interface {
|
||||
Revoker
|
||||
JWTProfileVerifier(context.Context) JWTProfileVerifier
|
||||
JWTProfileVerifier(context.Context) *JWTProfileVerifier
|
||||
}
|
||||
|
||||
func revocationHandler(revoker Revoker) func(http.ResponseWriter, *http.Request) {
|
||||
|
|
|
@ -6,15 +6,15 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
type UserinfoProvider interface {
|
||||
Decoder() httphelper.Decoder
|
||||
Crypto() Crypto
|
||||
Storage() Storage
|
||||
AccessTokenVerifier(context.Context) AccessTokenVerifier
|
||||
AccessTokenVerifier(context.Context) *AccessTokenVerifier
|
||||
}
|
||||
|
||||
func userinfoHandler(userinfoProvider UserinfoProvider) func(http.ResponseWriter, *http.Request) {
|
||||
|
|
|
@ -2,62 +2,25 @@ package op
|
|||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
type AccessTokenVerifier interface {
|
||||
oidc.Verifier
|
||||
SupportedSignAlgs() []string
|
||||
KeySet() oidc.KeySet
|
||||
}
|
||||
type AccessTokenVerifier oidc.Verifier
|
||||
|
||||
type accessTokenVerifier struct {
|
||||
issuer string
|
||||
maxAgeIAT time.Duration
|
||||
offset time.Duration
|
||||
supportedSignAlgs []string
|
||||
keySet oidc.KeySet
|
||||
}
|
||||
|
||||
// Issuer implements oidc.Verifier interface
|
||||
func (i *accessTokenVerifier) Issuer() string {
|
||||
return i.issuer
|
||||
}
|
||||
|
||||
// MaxAgeIAT implements oidc.Verifier interface
|
||||
func (i *accessTokenVerifier) MaxAgeIAT() time.Duration {
|
||||
return i.maxAgeIAT
|
||||
}
|
||||
|
||||
// Offset implements oidc.Verifier interface
|
||||
func (i *accessTokenVerifier) Offset() time.Duration {
|
||||
return i.offset
|
||||
}
|
||||
|
||||
// SupportedSignAlgs implements AccessTokenVerifier interface
|
||||
func (i *accessTokenVerifier) SupportedSignAlgs() []string {
|
||||
return i.supportedSignAlgs
|
||||
}
|
||||
|
||||
// KeySet implements AccessTokenVerifier interface
|
||||
func (i *accessTokenVerifier) KeySet() oidc.KeySet {
|
||||
return i.keySet
|
||||
}
|
||||
|
||||
type AccessTokenVerifierOpt func(*accessTokenVerifier)
|
||||
type AccessTokenVerifierOpt func(*AccessTokenVerifier)
|
||||
|
||||
func WithSupportedAccessTokenSigningAlgorithms(algs ...string) AccessTokenVerifierOpt {
|
||||
return func(verifier *accessTokenVerifier) {
|
||||
verifier.supportedSignAlgs = algs
|
||||
return func(verifier *AccessTokenVerifier) {
|
||||
verifier.SupportedSignAlgs = algs
|
||||
}
|
||||
}
|
||||
|
||||
func NewAccessTokenVerifier(issuer string, keySet oidc.KeySet, opts ...AccessTokenVerifierOpt) AccessTokenVerifier {
|
||||
verifier := &accessTokenVerifier{
|
||||
issuer: issuer,
|
||||
keySet: keySet,
|
||||
// NewAccessTokenVerifier returns a AccessTokenVerifier suitable for access token verification.
|
||||
func NewAccessTokenVerifier(issuer string, keySet oidc.KeySet, opts ...AccessTokenVerifierOpt) *AccessTokenVerifier {
|
||||
verifier := &AccessTokenVerifier{
|
||||
Issuer: issuer,
|
||||
KeySet: keySet,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(verifier)
|
||||
|
@ -66,7 +29,7 @@ func NewAccessTokenVerifier(issuer string, keySet oidc.KeySet, opts ...AccessTok
|
|||
}
|
||||
|
||||
// VerifyAccessToken validates the access token (issuer, signature and expiration).
|
||||
func VerifyAccessToken[C oidc.Claims](ctx context.Context, token string, v AccessTokenVerifier) (claims C, err error) {
|
||||
func VerifyAccessToken[C oidc.Claims](ctx context.Context, token string, v *AccessTokenVerifier) (claims C, err error) {
|
||||
var nilClaims C
|
||||
|
||||
decrypted, err := oidc.DecryptToken(token)
|
||||
|
@ -78,15 +41,15 @@ func VerifyAccessToken[C oidc.Claims](ctx context.Context, token string, v Acces
|
|||
return nilClaims, err
|
||||
}
|
||||
|
||||
if err := oidc.CheckIssuer(claims, v.Issuer()); err != nil {
|
||||
if err := oidc.CheckIssuer(claims, v.Issuer); err != nil {
|
||||
return nilClaims, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckSignature(ctx, decrypted, payload, claims, v.SupportedSignAlgs(), v.KeySet()); err != nil {
|
||||
if err = oidc.CheckSignature(ctx, decrypted, payload, claims, v.SupportedSignAlgs, v.KeySet); err != nil {
|
||||
return nilClaims, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckExpiration(claims, v.Offset()); err != nil {
|
||||
if err = oidc.CheckExpiration(claims, v.Offset); err != nil {
|
||||
return nilClaims, err
|
||||
}
|
||||
|
||||
|
|
|
@ -4,9 +4,9 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
tu "github.com/zitadel/oidc/v2/internal/testutil"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v2/pkg/op"
|
||||
tu "github.com/zitadel/oidc/v3/internal/testutil"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
)
|
||||
|
||||
// MyCustomClaims extends the TokenClaims base,
|
||||
|
|
|
@ -7,8 +7,8 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
tu "github.com/zitadel/oidc/v2/internal/testutil"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
tu "github.com/zitadel/oidc/v3/internal/testutil"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
func TestNewAccessTokenVerifier(t *testing.T) {
|
||||
|
@ -20,7 +20,7 @@ func TestNewAccessTokenVerifier(t *testing.T) {
|
|||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want AccessTokenVerifier
|
||||
want *AccessTokenVerifier
|
||||
}{
|
||||
{
|
||||
name: "simple",
|
||||
|
@ -28,9 +28,9 @@ func TestNewAccessTokenVerifier(t *testing.T) {
|
|||
issuer: tu.ValidIssuer,
|
||||
keySet: tu.KeySet{},
|
||||
},
|
||||
want: &accessTokenVerifier{
|
||||
issuer: tu.ValidIssuer,
|
||||
keySet: tu.KeySet{},
|
||||
want: &AccessTokenVerifier{
|
||||
Issuer: tu.ValidIssuer,
|
||||
KeySet: tu.KeySet{},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -42,10 +42,10 @@ func TestNewAccessTokenVerifier(t *testing.T) {
|
|||
WithSupportedAccessTokenSigningAlgorithms("ABC", "DEF"),
|
||||
},
|
||||
},
|
||||
want: &accessTokenVerifier{
|
||||
issuer: tu.ValidIssuer,
|
||||
keySet: tu.KeySet{},
|
||||
supportedSignAlgs: []string{"ABC", "DEF"},
|
||||
want: &AccessTokenVerifier{
|
||||
Issuer: tu.ValidIssuer,
|
||||
KeySet: tu.KeySet{},
|
||||
SupportedSignAlgs: []string{"ABC", "DEF"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -58,12 +58,12 @@ func TestNewAccessTokenVerifier(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestVerifyAccessToken(t *testing.T) {
|
||||
verifier := &accessTokenVerifier{
|
||||
issuer: tu.ValidIssuer,
|
||||
maxAgeIAT: 2 * time.Minute,
|
||||
offset: time.Second,
|
||||
supportedSignAlgs: []string{string(tu.SignatureAlgorithm)},
|
||||
keySet: tu.KeySet{},
|
||||
verifier := &AccessTokenVerifier{
|
||||
Issuer: tu.ValidIssuer,
|
||||
MaxAgeIAT: 2 * time.Minute,
|
||||
Offset: time.Second,
|
||||
SupportedSignAlgs: []string{string(tu.SignatureAlgorithm)},
|
||||
KeySet: tu.KeySet{},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
|
|
|
@ -2,69 +2,24 @@ package op
|
|||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
type IDTokenHintVerifier interface {
|
||||
oidc.Verifier
|
||||
SupportedSignAlgs() []string
|
||||
KeySet() oidc.KeySet
|
||||
ACR() oidc.ACRVerifier
|
||||
MaxAge() time.Duration
|
||||
}
|
||||
type IDTokenHintVerifier oidc.Verifier
|
||||
|
||||
type idTokenHintVerifier struct {
|
||||
issuer string
|
||||
maxAgeIAT time.Duration
|
||||
offset time.Duration
|
||||
supportedSignAlgs []string
|
||||
maxAge time.Duration
|
||||
acr oidc.ACRVerifier
|
||||
keySet oidc.KeySet
|
||||
}
|
||||
|
||||
func (i *idTokenHintVerifier) Issuer() string {
|
||||
return i.issuer
|
||||
}
|
||||
|
||||
func (i *idTokenHintVerifier) MaxAgeIAT() time.Duration {
|
||||
return i.maxAgeIAT
|
||||
}
|
||||
|
||||
func (i *idTokenHintVerifier) Offset() time.Duration {
|
||||
return i.offset
|
||||
}
|
||||
|
||||
func (i *idTokenHintVerifier) SupportedSignAlgs() []string {
|
||||
return i.supportedSignAlgs
|
||||
}
|
||||
|
||||
func (i *idTokenHintVerifier) KeySet() oidc.KeySet {
|
||||
return i.keySet
|
||||
}
|
||||
|
||||
func (i *idTokenHintVerifier) ACR() oidc.ACRVerifier {
|
||||
return i.acr
|
||||
}
|
||||
|
||||
func (i *idTokenHintVerifier) MaxAge() time.Duration {
|
||||
return i.maxAge
|
||||
}
|
||||
|
||||
type IDTokenHintVerifierOpt func(*idTokenHintVerifier)
|
||||
type IDTokenHintVerifierOpt func(*IDTokenHintVerifier)
|
||||
|
||||
func WithSupportedIDTokenHintSigningAlgorithms(algs ...string) IDTokenHintVerifierOpt {
|
||||
return func(verifier *idTokenHintVerifier) {
|
||||
verifier.supportedSignAlgs = algs
|
||||
return func(verifier *IDTokenHintVerifier) {
|
||||
verifier.SupportedSignAlgs = algs
|
||||
}
|
||||
}
|
||||
|
||||
func NewIDTokenHintVerifier(issuer string, keySet oidc.KeySet, opts ...IDTokenHintVerifierOpt) IDTokenHintVerifier {
|
||||
verifier := &idTokenHintVerifier{
|
||||
issuer: issuer,
|
||||
keySet: keySet,
|
||||
func NewIDTokenHintVerifier(issuer string, keySet oidc.KeySet, opts ...IDTokenHintVerifierOpt) *IDTokenHintVerifier {
|
||||
verifier := &IDTokenHintVerifier{
|
||||
Issuer: issuer,
|
||||
KeySet: keySet,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(verifier)
|
||||
|
@ -74,7 +29,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
|
||||
func VerifyIDTokenHint[C oidc.Claims](ctx context.Context, token string, v IDTokenHintVerifier) (claims C, err error) {
|
||||
func VerifyIDTokenHint[C oidc.Claims](ctx context.Context, token string, v *IDTokenHintVerifier) (claims C, err error) {
|
||||
var nilClaims C
|
||||
|
||||
decrypted, err := oidc.DecryptToken(token)
|
||||
|
@ -86,27 +41,27 @@ func VerifyIDTokenHint[C oidc.Claims](ctx context.Context, token string, v IDTok
|
|||
return nilClaims, err
|
||||
}
|
||||
|
||||
if err := oidc.CheckIssuer(claims, v.Issuer()); err != nil {
|
||||
if err := oidc.CheckIssuer(claims, v.Issuer); err != nil {
|
||||
return nilClaims, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckSignature(ctx, decrypted, payload, claims, v.SupportedSignAlgs(), v.KeySet()); err != nil {
|
||||
if err = oidc.CheckSignature(ctx, decrypted, payload, claims, v.SupportedSignAlgs, v.KeySet); err != nil {
|
||||
return nilClaims, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckExpiration(claims, v.Offset()); err != nil {
|
||||
if err = oidc.CheckExpiration(claims, v.Offset); err != nil {
|
||||
return nilClaims, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckIssuedAt(claims, v.MaxAgeIAT(), v.Offset()); err != nil {
|
||||
if err = oidc.CheckIssuedAt(claims, v.MaxAgeIAT, v.Offset); err != nil {
|
||||
return nilClaims, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckAuthorizationContextClassReference(claims, v.ACR()); err != nil {
|
||||
if err = oidc.CheckAuthorizationContextClassReference(claims, v.ACR); err != nil {
|
||||
return nilClaims, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckAuthTime(claims, v.MaxAge()); err != nil {
|
||||
if err = oidc.CheckAuthTime(claims, v.MaxAge); err != nil {
|
||||
return nilClaims, err
|
||||
}
|
||||
return claims, nil
|
||||
|
|
|
@ -7,8 +7,8 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
tu "github.com/zitadel/oidc/v2/internal/testutil"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
tu "github.com/zitadel/oidc/v3/internal/testutil"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
func TestNewIDTokenHintVerifier(t *testing.T) {
|
||||
|
@ -20,7 +20,7 @@ func TestNewIDTokenHintVerifier(t *testing.T) {
|
|||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want IDTokenHintVerifier
|
||||
want *IDTokenHintVerifier
|
||||
}{
|
||||
{
|
||||
name: "simple",
|
||||
|
@ -28,9 +28,9 @@ func TestNewIDTokenHintVerifier(t *testing.T) {
|
|||
issuer: tu.ValidIssuer,
|
||||
keySet: tu.KeySet{},
|
||||
},
|
||||
want: &idTokenHintVerifier{
|
||||
issuer: tu.ValidIssuer,
|
||||
keySet: tu.KeySet{},
|
||||
want: &IDTokenHintVerifier{
|
||||
Issuer: tu.ValidIssuer,
|
||||
KeySet: tu.KeySet{},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -42,10 +42,10 @@ func TestNewIDTokenHintVerifier(t *testing.T) {
|
|||
WithSupportedIDTokenHintSigningAlgorithms("ABC", "DEF"),
|
||||
},
|
||||
},
|
||||
want: &idTokenHintVerifier{
|
||||
issuer: tu.ValidIssuer,
|
||||
keySet: tu.KeySet{},
|
||||
supportedSignAlgs: []string{"ABC", "DEF"},
|
||||
want: &IDTokenHintVerifier{
|
||||
Issuer: tu.ValidIssuer,
|
||||
KeySet: tu.KeySet{},
|
||||
SupportedSignAlgs: []string{"ABC", "DEF"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -58,14 +58,14 @@ func TestNewIDTokenHintVerifier(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestVerifyIDTokenHint(t *testing.T) {
|
||||
verifier := &idTokenHintVerifier{
|
||||
issuer: tu.ValidIssuer,
|
||||
maxAgeIAT: 2 * time.Minute,
|
||||
offset: time.Second,
|
||||
supportedSignAlgs: []string{string(tu.SignatureAlgorithm)},
|
||||
maxAge: 2 * time.Minute,
|
||||
acr: tu.ACRVerify,
|
||||
keySet: tu.KeySet{},
|
||||
verifier := &IDTokenHintVerifier{
|
||||
Issuer: tu.ValidIssuer,
|
||||
MaxAgeIAT: 2 * time.Minute,
|
||||
Offset: time.Second,
|
||||
SupportedSignAlgs: []string{string(tu.SignatureAlgorithm)},
|
||||
MaxAge: 2 * time.Minute,
|
||||
ACR: tu.ACRVerify,
|
||||
KeySet: tu.KeySet{},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
|
|
|
@ -8,31 +8,28 @@ import (
|
|||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
type JWTProfileVerifier interface {
|
||||
// JWTProfileVerfiier extends oidc.Verifier with
|
||||
// a jwtProfileKeyStorage and a function to check
|
||||
// the subject in a token.
|
||||
type JWTProfileVerifier struct {
|
||||
oidc.Verifier
|
||||
Storage() jwtProfileKeyStorage
|
||||
CheckSubject(request *oidc.JWTTokenRequest) error
|
||||
}
|
||||
|
||||
type jwtProfileVerifier struct {
|
||||
storage jwtProfileKeyStorage
|
||||
subjectCheck func(request *oidc.JWTTokenRequest) error
|
||||
issuer string
|
||||
maxAgeIAT time.Duration
|
||||
offset time.Duration
|
||||
Storage JWTProfileKeyStorage
|
||||
CheckSubject func(request *oidc.JWTTokenRequest) error
|
||||
}
|
||||
|
||||
// NewJWTProfileVerifier creates a oidc.Verifier for JWT Profile assertions (authorization grant and client authentication)
|
||||
func NewJWTProfileVerifier(storage jwtProfileKeyStorage, issuer string, maxAgeIAT, offset time.Duration, opts ...JWTProfileVerifierOption) JWTProfileVerifier {
|
||||
j := &jwtProfileVerifier{
|
||||
storage: storage,
|
||||
subjectCheck: SubjectIsIssuer,
|
||||
issuer: issuer,
|
||||
maxAgeIAT: maxAgeIAT,
|
||||
offset: offset,
|
||||
func NewJWTProfileVerifier(storage JWTProfileKeyStorage, issuer string, maxAgeIAT, offset time.Duration, opts ...JWTProfileVerifierOption) *JWTProfileVerifier {
|
||||
j := &JWTProfileVerifier{
|
||||
Verifier: oidc.Verifier{
|
||||
Issuer: issuer,
|
||||
MaxAgeIAT: maxAgeIAT,
|
||||
Offset: offset,
|
||||
},
|
||||
Storage: storage,
|
||||
CheckSubject: SubjectIsIssuer,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
|
@ -42,53 +39,35 @@ func NewJWTProfileVerifier(storage jwtProfileKeyStorage, issuer string, maxAgeIA
|
|||
return j
|
||||
}
|
||||
|
||||
type JWTProfileVerifierOption func(*jwtProfileVerifier)
|
||||
type JWTProfileVerifierOption func(*JWTProfileVerifier)
|
||||
|
||||
// SubjectCheck sets a custom function to check the subject.
|
||||
// Defaults to SubjectIsIssuer()
|
||||
func SubjectCheck(check func(request *oidc.JWTTokenRequest) error) JWTProfileVerifierOption {
|
||||
return func(verifier *jwtProfileVerifier) {
|
||||
verifier.subjectCheck = check
|
||||
return func(verifier *JWTProfileVerifier) {
|
||||
verifier.CheckSubject = check
|
||||
}
|
||||
}
|
||||
|
||||
func (v *jwtProfileVerifier) Issuer() string {
|
||||
return v.issuer
|
||||
}
|
||||
|
||||
func (v *jwtProfileVerifier) Storage() jwtProfileKeyStorage {
|
||||
return v.storage
|
||||
}
|
||||
|
||||
func (v *jwtProfileVerifier) MaxAgeIAT() time.Duration {
|
||||
return v.maxAgeIAT
|
||||
}
|
||||
|
||||
func (v *jwtProfileVerifier) Offset() time.Duration {
|
||||
return v.offset
|
||||
}
|
||||
|
||||
func (v *jwtProfileVerifier) CheckSubject(request *oidc.JWTTokenRequest) error {
|
||||
return v.subjectCheck(request)
|
||||
}
|
||||
|
||||
// VerifyJWTAssertion verifies the assertion string from JWT Profile (authorization grant and client authentication)
|
||||
//
|
||||
// checks audience, exp, iat, signature and that issuer and sub are the same
|
||||
func VerifyJWTAssertion(ctx context.Context, assertion string, v JWTProfileVerifier) (*oidc.JWTTokenRequest, error) {
|
||||
func VerifyJWTAssertion(ctx context.Context, assertion string, v *JWTProfileVerifier) (*oidc.JWTTokenRequest, error) {
|
||||
request := new(oidc.JWTTokenRequest)
|
||||
payload, err := oidc.ParseToken(assertion, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckAudience(request, v.Issuer()); err != nil {
|
||||
if err = oidc.CheckAudience(request, v.Issuer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckExpiration(request, v.Offset()); err != nil {
|
||||
if err = oidc.CheckExpiration(request, v.Offset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckIssuedAt(request, v.MaxAgeIAT(), v.Offset()); err != nil {
|
||||
if err = oidc.CheckIssuedAt(request, v.MaxAgeIAT, v.Offset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -96,17 +75,18 @@ func VerifyJWTAssertion(ctx context.Context, assertion string, v JWTProfileVerif
|
|||
return nil, err
|
||||
}
|
||||
|
||||
keySet := &jwtProfileKeySet{storage: v.Storage(), clientID: request.Issuer}
|
||||
keySet := &jwtProfileKeySet{storage: v.Storage, clientID: request.Issuer}
|
||||
if err = oidc.CheckSignature(ctx, assertion, payload, request, nil, keySet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return request, nil
|
||||
}
|
||||
|
||||
type jwtProfileKeyStorage interface {
|
||||
type JWTProfileKeyStorage interface {
|
||||
GetKeyByIDAndClientID(ctx context.Context, keyID, userID string) (*jose.JSONWebKey, error)
|
||||
}
|
||||
|
||||
// SubjectIsIssuer
|
||||
func SubjectIsIssuer(request *oidc.JWTTokenRequest) error {
|
||||
if request.Issuer != request.Subject {
|
||||
return errors.New("delegation not allowed, issuer and sub must be identical")
|
||||
|
@ -115,7 +95,7 @@ func SubjectIsIssuer(request *oidc.JWTTokenRequest) error {
|
|||
}
|
||||
|
||||
type jwtProfileKeySet struct {
|
||||
storage jwtProfileKeyStorage
|
||||
storage JWTProfileKeyStorage
|
||||
clientID string
|
||||
}
|
||||
|
||||
|
|
117
pkg/op/verifier_jwt_profile_test.go
Normal file
117
pkg/op/verifier_jwt_profile_test.go
Normal file
|
@ -0,0 +1,117 @@
|
|||
package op_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
tu "github.com/zitadel/oidc/v3/internal/testutil"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
)
|
||||
|
||||
func TestNewJWTProfileVerifier(t *testing.T) {
|
||||
want := &op.JWTProfileVerifier{
|
||||
Verifier: oidc.Verifier{
|
||||
Issuer: tu.ValidIssuer,
|
||||
MaxAgeIAT: time.Minute,
|
||||
Offset: time.Second,
|
||||
},
|
||||
Storage: tu.JWTProfileKeyStorage{},
|
||||
}
|
||||
got := op.NewJWTProfileVerifier(tu.JWTProfileKeyStorage{}, tu.ValidIssuer, time.Minute, time.Second, op.SubjectCheck(func(request *oidc.JWTTokenRequest) error {
|
||||
return oidc.ErrSubjectMissing
|
||||
}))
|
||||
assert.Equal(t, want.Verifier, got.Verifier)
|
||||
assert.Equal(t, want.Storage, got.Storage)
|
||||
assert.ErrorIs(t, got.CheckSubject(nil), oidc.ErrSubjectMissing)
|
||||
}
|
||||
|
||||
func TestVerifyJWTAssertion(t *testing.T) {
|
||||
errCtx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
verifier := op.NewJWTProfileVerifier(tu.JWTProfileKeyStorage{}, tu.ValidIssuer, time.Minute, 0)
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
newToken func() (string, *oidc.JWTTokenRequest)
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "parse error",
|
||||
ctx: context.Background(),
|
||||
newToken: func() (string, *oidc.JWTTokenRequest) { return "!", nil },
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "wrong audience",
|
||||
ctx: context.Background(),
|
||||
newToken: func() (string, *oidc.JWTTokenRequest) {
|
||||
return tu.NewJWTProfileAssertion(
|
||||
tu.ValidClientID, tu.ValidClientID, []string{"wrong"},
|
||||
time.Now(), tu.ValidExpiration,
|
||||
)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "expired",
|
||||
ctx: context.Background(),
|
||||
newToken: func() (string, *oidc.JWTTokenRequest) {
|
||||
return tu.NewJWTProfileAssertion(
|
||||
tu.ValidClientID, tu.ValidClientID, []string{tu.ValidIssuer},
|
||||
time.Now(), time.Now().Add(-time.Hour),
|
||||
)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid iat",
|
||||
ctx: context.Background(),
|
||||
newToken: func() (string, *oidc.JWTTokenRequest) {
|
||||
return tu.NewJWTProfileAssertion(
|
||||
tu.ValidClientID, tu.ValidClientID, []string{tu.ValidIssuer},
|
||||
time.Now().Add(time.Hour), tu.ValidExpiration,
|
||||
)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid subject",
|
||||
ctx: context.Background(),
|
||||
newToken: func() (string, *oidc.JWTTokenRequest) {
|
||||
return tu.NewJWTProfileAssertion(
|
||||
tu.ValidClientID, "wrong", []string{tu.ValidIssuer},
|
||||
time.Now(), tu.ValidExpiration,
|
||||
)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "check signature fail",
|
||||
ctx: errCtx,
|
||||
newToken: tu.ValidJWTProfileAssertion,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "ok",
|
||||
ctx: context.Background(),
|
||||
newToken: tu.ValidJWTProfileAssertion,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assertion, want := tt.newToken()
|
||||
got, err := op.VerifyJWTAssertion(tt.ctx, assertion, verifier)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, want, got)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue