feat: coverage prompt=none, response_mode=fragment (#385)
This commit is contained in:
parent
e62473ba71
commit
157bc6ceb0
5 changed files with 117 additions and 10 deletions
|
@ -34,7 +34,7 @@ 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, extraOptions ...op.Option) *mux.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"))
|
||||
|
@ -50,7 +50,7 @@ func SetupServer(issuer string, storage Storage) *mux.Router {
|
|||
})
|
||||
|
||||
// creation of the OpenIDProvider with the just created in-memory Storage
|
||||
provider, err := newOP(storage, issuer, key)
|
||||
provider, err := newOP(storage, issuer, key, extraOptions...)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ func SetupServer(issuer string, storage Storage) *mux.Router {
|
|||
// newOP will create an OpenID Provider for localhost on a specified port with a given encryption key
|
||||
// and a predefined default logout uri
|
||||
// it will enable all options (see descriptions)
|
||||
func newOP(storage op.Storage, issuer string, key [32]byte) (op.OpenIDProvider, error) {
|
||||
func newOP(storage op.Storage, issuer string, key [32]byte, extraOptions ...op.Option) (op.OpenIDProvider, error) {
|
||||
config := &op.Config{
|
||||
CryptoKey: key,
|
||||
|
||||
|
@ -112,10 +112,12 @@ func newOP(storage op.Storage, issuer string, key [32]byte) (op.OpenIDProvider,
|
|||
},
|
||||
}
|
||||
handler, err := op.NewOpenIDProvider(issuer, config, storage,
|
||||
//we must explicitly allow the use of the http issuer
|
||||
op.WithAllowInsecure(),
|
||||
// as an example on how to customize an endpoint this will change the authorization_endpoint from /authorize to /auth
|
||||
op.WithCustomAuthEndpoint(op.NewEndpoint("auth")),
|
||||
append([]op.Option{
|
||||
// we must explicitly allow the use of the http issuer
|
||||
op.WithAllowInsecure(),
|
||||
// as an example on how to customize an endpoint this will change the authorization_endpoint from /authorize to /auth
|
||||
op.WithCustomAuthEndpoint(op.NewEndpoint("auth")),
|
||||
}, extraOptions...)...,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -28,8 +28,10 @@ var serviceKey1 = &rsa.PublicKey{
|
|||
E: 65537,
|
||||
}
|
||||
|
||||
var _ op.Storage = &Storage{}
|
||||
var _ op.ClientCredentialsStorage = &Storage{}
|
||||
var (
|
||||
_ op.Storage = &Storage{}
|
||||
_ op.ClientCredentialsStorage = &Storage{}
|
||||
)
|
||||
|
||||
// storage implements the op.Storage interface
|
||||
// typically you would implement this as a layer on top of your database
|
||||
|
@ -167,6 +169,12 @@ func (s *Storage) CreateAuthRequest(ctx context.Context, authReq *oidc.AuthReque
|
|||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
if len(authReq.Prompt) == 1 && authReq.Prompt[0] == "none" {
|
||||
// With prompt=none, there is no way for the user to log in
|
||||
// so return error right away.
|
||||
return nil, oidc.ErrLoginRequired()
|
||||
}
|
||||
|
||||
// typically, you'll fill your storage / storage model with the information of the passed object
|
||||
request := authRequestToInternal(authReq, userID)
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
"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/v2/pkg/op"
|
||||
)
|
||||
|
||||
func TestRelyingPartySession(t *testing.T) {
|
||||
|
@ -280,6 +281,92 @@ func RunAuthorizationCodeFlow(t *testing.T, opServer *httptest.Server, clientID,
|
|||
return provider, accessToken, refreshToken, idToken
|
||||
}
|
||||
|
||||
func TestErrorFromPromptNone(t *testing.T) {
|
||||
jar, err := cookiejar.New(nil)
|
||||
require.NoError(t, err, "create cookie jar")
|
||||
httpClient := &http.Client{
|
||||
Timeout: time.Second * 5,
|
||||
CheckRedirect: func(_ *http.Request, _ []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
Jar: jar,
|
||||
}
|
||||
|
||||
t.Log("------- start example OP ------")
|
||||
targetURL := "http://local-site"
|
||||
exampleStorage := storage.NewStorage(storage.NewUserStore(targetURL))
|
||||
var dh deferredHandler
|
||||
opServer := httptest.NewServer(&dh)
|
||||
defer opServer.Close()
|
||||
t.Logf("auth server at %s", opServer.URL)
|
||||
dh.Handler = exampleop.SetupServer(opServer.URL, exampleStorage, op.WithHttpInterceptors(
|
||||
func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Logf("request to %s", r.URL)
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
},
|
||||
))
|
||||
seed := rand.New(rand.NewSource(int64(os.Getpid()) + time.Now().UnixNano()))
|
||||
clientID := t.Name() + "-" + strconv.FormatInt(seed.Int63(), 25)
|
||||
clientSecret := "secret"
|
||||
client := storage.WebClient(clientID, clientSecret, targetURL)
|
||||
storage.RegisterClients(client)
|
||||
|
||||
t.Log("------- create RP ------")
|
||||
key := []byte("test1234test1234")
|
||||
cookieHandler := httphelper.NewCookieHandler(key, key, httphelper.WithUnsecure())
|
||||
provider, err := rp.NewRelyingPartyOIDC(
|
||||
opServer.URL,
|
||||
clientID,
|
||||
clientSecret,
|
||||
targetURL,
|
||||
[]string{"openid", "email", "profile", "offline_access"},
|
||||
rp.WithPKCE(cookieHandler),
|
||||
rp.WithVerifierOpts(
|
||||
rp.WithIssuedAtOffset(5*time.Second),
|
||||
rp.WithSupportedSigningAlgorithms("RS256", "RS384", "RS512", "ES256", "ES384", "ES512"),
|
||||
),
|
||||
)
|
||||
require.NoError(t, err, "new rp")
|
||||
|
||||
t.Log("------- start auth flow with prompt=none ------- ")
|
||||
state := "state-32892"
|
||||
capturedW := httptest.NewRecorder()
|
||||
localURL, err := url.Parse(targetURL + "/login")
|
||||
require.NoError(t, err)
|
||||
|
||||
get := httptest.NewRequest("GET", localURL.String(), nil)
|
||||
rp.AuthURLHandler(func() string { return state }, provider,
|
||||
rp.WithPromptURLParam("none"),
|
||||
rp.WithResponseModeURLParam(oidc.ResponseModeFragment),
|
||||
)(capturedW, get)
|
||||
|
||||
defer func() {
|
||||
if t.Failed() {
|
||||
t.Log("response body (redirect from RP to OP)", capturedW.Body.String())
|
||||
}
|
||||
}()
|
||||
require.GreaterOrEqual(t, capturedW.Code, 200, "captured response code")
|
||||
require.Less(t, capturedW.Code, 400, "captured response code")
|
||||
|
||||
//nolint:bodyclose
|
||||
resp := capturedW.Result()
|
||||
jar.SetCookies(localURL, resp.Cookies())
|
||||
|
||||
startAuthURL, err := resp.Location()
|
||||
require.NoError(t, err, "get redirect")
|
||||
assert.NotEmpty(t, startAuthURL, "login url")
|
||||
t.Log("Starting auth at", startAuthURL)
|
||||
|
||||
t.Log("------- get redirect from OP ------")
|
||||
loginPageURL := getRedirect(t, "get redirect to login page", httpClient, startAuthURL)
|
||||
t.Log("login page URL", loginPageURL)
|
||||
|
||||
require.Contains(t, loginPageURL.String(), `error=login_required`, "prompt=none should error")
|
||||
require.Contains(t, loginPageURL.String(), `local-site#error=`, "response_mode=fragment means '#' instead of '?'")
|
||||
}
|
||||
|
||||
type deferredHandler struct {
|
||||
http.Handler
|
||||
}
|
||||
|
|
|
@ -569,6 +569,11 @@ func WithPromptURLParam(prompt ...string) URLParamOpt {
|
|||
return withPrompt(prompt...)
|
||||
}
|
||||
|
||||
// WithResponseModeURLParam sets the `response_mode` parameter in a URL.
|
||||
func WithResponseModeURLParam(mode oidc.ResponseMode) URLParamOpt {
|
||||
return withURLParam("response_mode", string(mode))
|
||||
}
|
||||
|
||||
type AuthURLOpt func() []oauth2.AuthCodeOption
|
||||
|
||||
// WithCodeChallenge sets the `code_challenge` params in the auth request
|
||||
|
|
|
@ -60,7 +60,7 @@ const (
|
|||
)
|
||||
|
||||
// AuthRequest according to:
|
||||
//https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
|
||||
type AuthRequest struct {
|
||||
Scopes SpaceDelimitedArray `json:"scope" schema:"scope"`
|
||||
ResponseType ResponseType `json:"response_type" schema:"response_type"`
|
||||
|
@ -100,3 +100,8 @@ func (a *AuthRequest) GetResponseType() ResponseType {
|
|||
func (a *AuthRequest) GetState() string {
|
||||
return a.State
|
||||
}
|
||||
|
||||
// GetResponseMode returns the optional ResponseMode
|
||||
func (a *AuthRequest) GetResponseMode() ResponseMode {
|
||||
return a.ResponseMode
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue