fix: parse max_age and prompt correctly (and change scope type) (#105)
* fix: parse max_age and prompt correctly (and change scope type) * remove unnecessary omitempty
This commit is contained in:
parent
0591a0d1ef
commit
400f5c4de4
16 changed files with 98 additions and 85 deletions
|
@ -95,7 +95,7 @@ func (a *AuthRequest) GetScopes() []string {
|
|||
}
|
||||
}
|
||||
|
||||
func (a *AuthRequest) SetCurrentScopes(scopes oidc.Scopes) {}
|
||||
func (a *AuthRequest) SetCurrentScopes(scopes []string) {}
|
||||
|
||||
func (a *AuthRequest) GetState() string {
|
||||
return ""
|
||||
|
@ -243,7 +243,7 @@ func (s *AuthStorage) SetIntrospectionFromToken(ctx context.Context, introspect
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *AuthStorage) ValidateJWTProfileScopes(ctx context.Context, userID string, scope oidc.Scopes) (oidc.Scopes, error) {
|
||||
func (s *AuthStorage) ValidateJWTProfileScopes(ctx context.Context, userID string, scope []string) ([]string, error) {
|
||||
return scope, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@ import (
|
|||
var (
|
||||
Encoder = func() utils.Encoder {
|
||||
e := schema.NewEncoder()
|
||||
e.RegisterEncoder(oidc.Scopes{}, func(value reflect.Value) string {
|
||||
return value.Interface().(oidc.Scopes).Encode()
|
||||
e.RegisterEncoder(oidc.SpaceDelimitedArray{}, func(value reflect.Value) string {
|
||||
return value.Interface().(oidc.SpaceDelimitedArray).Encode()
|
||||
})
|
||||
return e
|
||||
}()
|
||||
|
|
|
@ -430,10 +430,10 @@ func WithCodeChallenge(codeChallenge string) AuthURLOpt {
|
|||
}
|
||||
|
||||
//WithPrompt sets the `prompt` params in the auth request
|
||||
func WithPrompt(prompt oidc.Prompt) AuthURLOpt {
|
||||
func WithPrompt(prompt ...string) AuthURLOpt {
|
||||
return func() []oauth2.AuthCodeOption {
|
||||
return []oauth2.AuthCodeOption{
|
||||
oauth2.SetAuthURLParam("prompt", string(prompt)),
|
||||
oauth2.SetAuthURLParam("prompt", oidc.SpaceDelimitedArray(prompt).Encode()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,39 +44,39 @@ const (
|
|||
|
||||
//PromptNone (`none`) disallows the Authorization Server to display any authentication or consent user interface pages.
|
||||
//An error (login_required, interaction_required, ...) will be returned if the user is not already authenticated or consent is needed
|
||||
PromptNone Prompt = "none"
|
||||
PromptNone = "none"
|
||||
|
||||
//PromptLogin (`login`) directs the Authorization Server to prompt the End-User for reauthentication.
|
||||
PromptLogin Prompt = "login"
|
||||
PromptLogin = "login"
|
||||
|
||||
//PromptConsent (`consent`) directs the Authorization Server to prompt the End-User for consent (of sharing information).
|
||||
PromptConsent Prompt = "consent"
|
||||
PromptConsent = "consent"
|
||||
|
||||
//PromptSelectAccount (`select_account `) directs the Authorization Server to prompt the End-User to select a user account (to enable multi user / session switching)
|
||||
PromptSelectAccount Prompt = "select_account"
|
||||
PromptSelectAccount = "select_account"
|
||||
)
|
||||
|
||||
//AuthRequest according to:
|
||||
//https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
|
||||
type AuthRequest struct {
|
||||
ID string
|
||||
Scopes Scopes `schema:"scope"`
|
||||
ResponseType ResponseType `schema:"response_type"`
|
||||
ClientID string `schema:"client_id"`
|
||||
RedirectURI string `schema:"redirect_uri"` //TODO: type
|
||||
Scopes SpaceDelimitedArray `schema:"scope"`
|
||||
ResponseType ResponseType `schema:"response_type"`
|
||||
ClientID string `schema:"client_id"`
|
||||
RedirectURI string `schema:"redirect_uri"` //TODO: type
|
||||
|
||||
State string `schema:"state"`
|
||||
|
||||
// ResponseMode TODO: ?
|
||||
|
||||
Nonce string `schema:"nonce"`
|
||||
Display Display `schema:"display"`
|
||||
Prompt Prompt `schema:"prompt"`
|
||||
MaxAge uint32 `schema:"max_age"`
|
||||
UILocales Locales `schema:"ui_locales"`
|
||||
IDTokenHint string `schema:"id_token_hint"`
|
||||
LoginHint string `schema:"login_hint"`
|
||||
ACRValues []string `schema:"acr_values"`
|
||||
Nonce string `schema:"nonce"`
|
||||
Display Display `schema:"display"`
|
||||
Prompt SpaceDelimitedArray `schema:"prompt"`
|
||||
MaxAge *uint `schema:"max_age"`
|
||||
UILocales Locales `schema:"ui_locales"`
|
||||
IDTokenHint string `schema:"id_token_hint"`
|
||||
LoginHint string `schema:"login_hint"`
|
||||
ACRValues []string `schema:"acr_values"`
|
||||
|
||||
CodeChallenge string `schema:"code_challenge"`
|
||||
CodeChallengeMethod CodeChallengeMethod `schema:"code_challenge_method"`
|
||||
|
|
|
@ -21,7 +21,7 @@ type IntrospectionResponse interface {
|
|||
UserInfoSetter
|
||||
SetActive(bool)
|
||||
IsActive() bool
|
||||
SetScopes(scopes Scopes)
|
||||
SetScopes(scopes []string)
|
||||
SetClientID(id string)
|
||||
}
|
||||
|
||||
|
@ -30,10 +30,10 @@ func NewIntrospectionResponse() IntrospectionResponse {
|
|||
}
|
||||
|
||||
type introspectionResponse struct {
|
||||
Active bool `json:"active"`
|
||||
Scope Scopes `json:"scope,omitempty"`
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
Subject string `json:"sub,omitempty"`
|
||||
Active bool `json:"active"`
|
||||
Scope SpaceDelimitedArray `json:"scope,omitempty"`
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
Subject string `json:"sub,omitempty"`
|
||||
userInfoProfile
|
||||
userInfoEmail
|
||||
userInfoPhone
|
||||
|
@ -46,7 +46,7 @@ func (u *introspectionResponse) IsActive() bool {
|
|||
return u.Active
|
||||
}
|
||||
|
||||
func (u *introspectionResponse) SetScopes(scope Scopes) {
|
||||
func (u *introspectionResponse) SetScopes(scope []string) {
|
||||
u.Scope = scope
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package oidc
|
||||
|
||||
type JWTProfileGrantRequest struct {
|
||||
Assertion string `schema:"assertion"`
|
||||
Scope Scopes `schema:"scope"`
|
||||
GrantType GrantType `schema:"grant_type"`
|
||||
Assertion string `schema:"assertion"`
|
||||
Scope SpaceDelimitedArray `schema:"scope"`
|
||||
GrantType GrantType `schema:"grant_type"`
|
||||
}
|
||||
|
||||
//NewJWTProfileGrantRequest creates an oauth2 `JSON Web Token (JWT) Profile` Grant
|
||||
|
|
|
@ -58,12 +58,12 @@ func (a *AccessTokenRequest) SetClientSecret(clientSecret string) {
|
|||
}
|
||||
|
||||
type RefreshTokenRequest struct {
|
||||
RefreshToken string `schema:"refresh_token"`
|
||||
Scopes Scopes `schema:"scope"`
|
||||
ClientID string `schema:"client_id"`
|
||||
ClientSecret string `schema:"client_secret"`
|
||||
ClientAssertion string `schema:"client_assertion"`
|
||||
ClientAssertionType string `schema:"client_assertion_type"`
|
||||
RefreshToken string `schema:"refresh_token"`
|
||||
Scopes SpaceDelimitedArray `schema:"scope"`
|
||||
ClientID string `schema:"client_id"`
|
||||
ClientSecret string `schema:"client_secret"`
|
||||
ClientAssertion string `schema:"client_assertion"`
|
||||
ClientAssertionType string `schema:"client_assertion_type"`
|
||||
}
|
||||
|
||||
func (a *RefreshTokenRequest) GrantType() GrantType {
|
||||
|
@ -81,12 +81,12 @@ func (a *RefreshTokenRequest) SetClientSecret(clientSecret string) {
|
|||
}
|
||||
|
||||
type JWTTokenRequest struct {
|
||||
Issuer string `json:"iss"`
|
||||
Subject string `json:"sub"`
|
||||
Scopes Scopes `json:"-"`
|
||||
Audience Audience `json:"aud"`
|
||||
IssuedAt Time `json:"iat"`
|
||||
ExpiresAt Time `json:"exp"`
|
||||
Issuer string `json:"iss"`
|
||||
Subject string `json:"sub"`
|
||||
Scopes SpaceDelimitedArray `json:"-"`
|
||||
Audience Audience `json:"aud"`
|
||||
IssuedAt Time `json:"iat"`
|
||||
ExpiresAt Time `json:"exp"`
|
||||
}
|
||||
|
||||
//GetIssuer implements the Claims interface
|
||||
|
@ -143,12 +143,12 @@ func (j *JWTTokenRequest) GetScopes() []string {
|
|||
}
|
||||
|
||||
type TokenExchangeRequest struct {
|
||||
subjectToken string `schema:"subject_token"`
|
||||
subjectTokenType string `schema:"subject_token_type"`
|
||||
actorToken string `schema:"actor_token"`
|
||||
actorTokenType string `schema:"actor_token_type"`
|
||||
resource []string `schema:"resource"`
|
||||
audience Audience `schema:"audience"`
|
||||
Scope Scopes `schema:"scope"`
|
||||
requestedTokenType string `schema:"requested_token_type"`
|
||||
subjectToken string `schema:"subject_token"`
|
||||
subjectTokenType string `schema:"subject_token_type"`
|
||||
actorToken string `schema:"actor_token"`
|
||||
actorTokenType string `schema:"actor_token_type"`
|
||||
resource []string `schema:"resource"`
|
||||
audience Audience `schema:"audience"`
|
||||
Scope SpaceDelimitedArray `schema:"scope"`
|
||||
requestedTokenType string `schema:"requested_token_type"`
|
||||
}
|
||||
|
|
|
@ -54,30 +54,36 @@ func (l *Locales) UnmarshalText(text []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type Prompt string
|
||||
type MaxAge *uint
|
||||
|
||||
func NewMaxAge(i uint) MaxAge {
|
||||
return &i
|
||||
}
|
||||
|
||||
type SpaceDelimitedArray []string
|
||||
|
||||
type Prompt SpaceDelimitedArray
|
||||
|
||||
type ResponseType string
|
||||
|
||||
type Scopes []string
|
||||
|
||||
func (s Scopes) Encode() string {
|
||||
func (s SpaceDelimitedArray) Encode() string {
|
||||
return strings.Join(s, " ")
|
||||
}
|
||||
|
||||
func (s *Scopes) UnmarshalText(text []byte) error {
|
||||
func (s *SpaceDelimitedArray) UnmarshalText(text []byte) error {
|
||||
*s = strings.Split(string(text), " ")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Scopes) MarshalText() ([]byte, error) {
|
||||
func (s SpaceDelimitedArray) MarshalText() ([]byte, error) {
|
||||
return []byte(s.Encode()), nil
|
||||
}
|
||||
|
||||
func (s *Scopes) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal((*s).Encode())
|
||||
func (s SpaceDelimitedArray) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal((s).Encode())
|
||||
}
|
||||
|
||||
func (s *Scopes) UnmarshalJSON(data []byte) error {
|
||||
func (s *SpaceDelimitedArray) UnmarshalJSON(data []byte) error {
|
||||
var str string
|
||||
if err := json.Unmarshal(data, &str); err != nil {
|
||||
return err
|
||||
|
|
|
@ -220,7 +220,7 @@ func TestScopes_UnmarshalText(t *testing.T) {
|
|||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var scopes Scopes
|
||||
var scopes SpaceDelimitedArray
|
||||
if err := scopes.UnmarshalText(tt.args.text); (err != nil) != tt.wantErr {
|
||||
t.Errorf("UnmarshalText() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
|
@ -230,7 +230,7 @@ func TestScopes_UnmarshalText(t *testing.T) {
|
|||
}
|
||||
func TestScopes_MarshalText(t *testing.T) {
|
||||
type args struct {
|
||||
scopes Scopes
|
||||
scopes SpaceDelimitedArray
|
||||
}
|
||||
type res struct {
|
||||
scopes []byte
|
||||
|
@ -244,7 +244,7 @@ func TestScopes_MarshalText(t *testing.T) {
|
|||
{
|
||||
"unknown value",
|
||||
args{
|
||||
Scopes{"unknown"},
|
||||
SpaceDelimitedArray{"unknown"},
|
||||
},
|
||||
res{
|
||||
[]byte("unknown"),
|
||||
|
@ -254,7 +254,7 @@ func TestScopes_MarshalText(t *testing.T) {
|
|||
{
|
||||
"struct",
|
||||
args{
|
||||
Scopes{`{"unknown":"value"}`},
|
||||
SpaceDelimitedArray{`{"unknown":"value"}`},
|
||||
},
|
||||
res{
|
||||
[]byte(`{"unknown":"value"}`),
|
||||
|
@ -264,7 +264,7 @@ func TestScopes_MarshalText(t *testing.T) {
|
|||
{
|
||||
"openid",
|
||||
args{
|
||||
Scopes{"openid"},
|
||||
SpaceDelimitedArray{"openid"},
|
||||
},
|
||||
res{
|
||||
[]byte("openid"),
|
||||
|
@ -274,7 +274,7 @@ func TestScopes_MarshalText(t *testing.T) {
|
|||
{
|
||||
"multiple scopes",
|
||||
args{
|
||||
Scopes{"openid", "email", "custom:scope"},
|
||||
SpaceDelimitedArray{"openid", "email", "custom:scope"},
|
||||
},
|
||||
res{
|
||||
[]byte("openid email custom:scope"),
|
||||
|
|
|
@ -106,7 +106,11 @@ func ParseAuthorizeRequest(r *http.Request, decoder utils.Decoder) (*oidc.AuthRe
|
|||
}
|
||||
|
||||
//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) (string, 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
|
||||
}
|
||||
client, err := storage.GetClientByClientID(ctx, authReq.ClientID)
|
||||
if err != nil {
|
||||
return "", ErrServerError(err.Error())
|
||||
|
@ -124,6 +128,19 @@ func ValidateAuthRequest(ctx context.Context, authReq *oidc.AuthRequest, storage
|
|||
return ValidateAuthReqIDTokenHint(ctx, authReq.IDTokenHint, verifier)
|
||||
}
|
||||
|
||||
//ValidateAuthReqPrompt validates the passed prompt values and sets max_age to 0 if prompt login is present
|
||||
func ValidateAuthReqPrompt(prompts []string, maxAge *uint) (_ *uint, err error) {
|
||||
for _, prompt := range prompts {
|
||||
if prompt == oidc.PromptNone && len(prompts) > 1 {
|
||||
return nil, ErrInvalidRequest("The prompt parameter `none` must only be used as a single value")
|
||||
}
|
||||
if prompt == oidc.PromptLogin {
|
||||
maxAge = oidc.NewMaxAge(0)
|
||||
}
|
||||
}
|
||||
return maxAge, nil
|
||||
}
|
||||
|
||||
//ValidateAuthReqScopes validates the passed scopes
|
||||
func ValidateAuthReqScopes(client Client, scopes []string) ([]string, error) {
|
||||
if len(scopes) == 0 {
|
||||
|
|
|
@ -123,7 +123,7 @@ func TestParseAuthorizeRequest(t *testing.T) {
|
|||
}(),
|
||||
},
|
||||
res{
|
||||
&oidc.AuthRequest{Scopes: oidc.Scopes{"openid"}},
|
||||
&oidc.AuthRequest{Scopes: oidc.SpaceDelimitedArray{"openid"}},
|
||||
false,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -316,10 +316,10 @@ func (mr *MockStorageMockRecorder) TokenRequestByRefreshToken(arg0, arg1 interfa
|
|||
}
|
||||
|
||||
// ValidateJWTProfileScopes mocks base method.
|
||||
func (m *MockStorage) ValidateJWTProfileScopes(arg0 context.Context, arg1 string, arg2 oidc.Scopes) (oidc.Scopes, error) {
|
||||
func (m *MockStorage) ValidateJWTProfileScopes(arg0 context.Context, arg1 string, arg2 []string) ([]string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ValidateJWTProfileScopes", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(oidc.Scopes)
|
||||
ret0, _ := ret[0].([]string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
|
|
@ -140,10 +140,10 @@ func (c *ConfClient) GetID() string {
|
|||
}
|
||||
|
||||
func (c *ConfClient) AccessTokenLifetime() time.Duration {
|
||||
return time.Duration(5 * time.Minute)
|
||||
return 5 * time.Minute
|
||||
}
|
||||
func (c *ConfClient) IDTokenLifetime() time.Duration {
|
||||
return time.Duration(5 * time.Minute)
|
||||
return 5 * time.Minute
|
||||
}
|
||||
func (c *ConfClient) AccessTokenType() op.AccessTokenType {
|
||||
return c.accessTokenType
|
||||
|
|
|
@ -83,13 +83,3 @@ func ValidateEndSessionRequest(ctx context.Context, req *oidc.EndSessionRequest,
|
|||
}
|
||||
return nil, ErrInvalidRequest("post_logout_redirect_uri invalid")
|
||||
}
|
||||
|
||||
func NeedsExistingSession(authRequest *oidc.AuthRequest) bool {
|
||||
if authRequest == nil {
|
||||
return true
|
||||
}
|
||||
if authRequest.Prompt == oidc.PromptNone {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ type OPStorage interface {
|
|||
SetIntrospectionFromToken(ctx context.Context, userinfo oidc.IntrospectionResponse, tokenID, subject, clientID string) error
|
||||
GetPrivateClaimsFromScopes(ctx context.Context, userID, clientID string, scopes []string) (map[string]interface{}, error)
|
||||
GetKeyByIDAndUserID(ctx context.Context, keyID, userID string) (*jose.JSONWebKey, error)
|
||||
ValidateJWTProfileScopes(ctx context.Context, userID string, scope oidc.Scopes) (oidc.Scopes, error)
|
||||
ValidateJWTProfileScopes(ctx context.Context, userID string, scopes []string) ([]string, error)
|
||||
}
|
||||
|
||||
type Storage interface {
|
||||
|
|
|
@ -17,7 +17,7 @@ type RefreshTokenRequest interface {
|
|||
GetClientID() string
|
||||
GetScopes() []string
|
||||
GetSubject() string
|
||||
SetCurrentScopes(scopes oidc.Scopes)
|
||||
SetCurrentScopes(scopes []string)
|
||||
}
|
||||
|
||||
//RefreshTokenExchange handles the OAuth 2.0 refresh_token grant, including
|
||||
|
@ -72,7 +72,7 @@ func ValidateRefreshTokenRequest(ctx context.Context, tokenReq *oidc.RefreshToke
|
|||
//ValidateRefreshTokenScopes validates that the requested scope is a subset of the original auth request scope
|
||||
//it will set the requested scopes as current scopes onto RefreshTokenRequest
|
||||
//if empty the original scopes will be used
|
||||
func ValidateRefreshTokenScopes(requestedScopes oidc.Scopes, authRequest RefreshTokenRequest) error {
|
||||
func ValidateRefreshTokenScopes(requestedScopes []string, authRequest RefreshTokenRequest) error {
|
||||
if len(requestedScopes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue