...
1
21
22 package openid
23
24 import (
25 "context"
26 "net/url"
27 "strconv"
28 "strings"
29 "time"
30
31 "github.com/ory/x/errorsx"
32
33 "github.com/pkg/errors"
34
35 "github.com/ory/fosite"
36 "github.com/ory/fosite/token/jwt"
37 "github.com/ory/go-convenience/stringslice"
38 )
39
40 type OpenIDConnectRequestValidator struct {
41 AllowedPrompt []string
42 Strategy jwt.JWTStrategy
43 IsRedirectURISecure func(*url.URL) bool
44 }
45
46 func NewOpenIDConnectRequestValidator(prompt []string, strategy jwt.JWTStrategy) *OpenIDConnectRequestValidator {
47 if len(prompt) == 0 {
48 prompt = []string{"login", "none", "consent", "select_account"}
49 }
50
51 return &OpenIDConnectRequestValidator{
52 AllowedPrompt: prompt,
53 Strategy: strategy,
54 }
55 }
56
57 func (v *OpenIDConnectRequestValidator) WithRedirectSecureChecker(checker func(*url.URL) bool) *OpenIDConnectRequestValidator {
58 v.IsRedirectURISecure = checker
59 return v
60 }
61
62 func (v *OpenIDConnectRequestValidator) secureChecker() func(*url.URL) bool {
63 if v.IsRedirectURISecure == nil {
64 v.IsRedirectURISecure = fosite.IsRedirectURISecure
65 }
66 return v.IsRedirectURISecure
67 }
68
69 func (v *OpenIDConnectRequestValidator) ValidatePrompt(ctx context.Context, req fosite.AuthorizeRequester) error {
70
71 prompt := fosite.RemoveEmpty(strings.Split(req.GetRequestForm().Get("prompt"), " "))
72
73 if req.GetClient().IsPublic() {
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92 if stringslice.Has(prompt, "none") {
93 if !v.secureChecker()(req.GetRedirectURI()) {
94 return errorsx.WithStack(fosite.ErrConsentRequired.WithHint("OAuth 2.0 Client is marked public and redirect uri is not considered secure (https missing), but \"prompt=none\" was requested."))
95 }
96 }
97 }
98
99 if !isWhitelisted(prompt, v.AllowedPrompt) {
100 return errorsx.WithStack(fosite.ErrInvalidRequest.WithHintf("Used unknown value '%s' for prompt parameter", prompt))
101 }
102
103 if stringslice.Has(prompt, "none") && len(prompt) > 1 {
104
105 return errorsx.WithStack(fosite.ErrInvalidRequest.WithHint("Parameter 'prompt' was set to 'none', but contains other values as well which is not allowed."))
106 }
107
108 maxAge, err := strconv.ParseInt(req.GetRequestForm().Get("max_age"), 10, 64)
109 if err != nil {
110 maxAge = 0
111 }
112
113 session, ok := req.GetSession().(Session)
114 if !ok {
115 return errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to validate OpenID Connect request because session is not of type fosite/handler/openid.Session."))
116 }
117
118 claims := session.IDTokenClaims()
119 if claims.Subject == "" {
120 return errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to validate OpenID Connect request because session subject is empty."))
121 }
122
123
124 if claims.AuthTime.After(time.Now().UTC().Add(time.Second * 5)) {
125 return errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to validate OpenID Connect request because authentication time is in the future."))
126 }
127
128 if maxAge > 0 {
129 if claims.AuthTime.IsZero() {
130 return errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to validate OpenID Connect request because authentication time claim is required when max_age is set."))
131 } else if claims.RequestedAt.IsZero() {
132 return errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to validate OpenID Connect request because requested at claim is required when max_age is set."))
133 } else if claims.AuthTime.Add(time.Second * time.Duration(maxAge)).Before(claims.RequestedAt) {
134 return errorsx.WithStack(fosite.ErrLoginRequired.WithDebug("Failed to validate OpenID Connect request because authentication time does not satisfy max_age time."))
135 }
136 }
137
138 if stringslice.Has(prompt, "none") {
139 if claims.AuthTime.IsZero() {
140 return errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to validate OpenID Connect request because because auth_time is missing from session."))
141 }
142 if !claims.AuthTime.Equal(claims.RequestedAt) && claims.AuthTime.After(claims.RequestedAt) {
143
144 return errorsx.WithStack(fosite.ErrLoginRequired.WithHintf("Failed to validate OpenID Connect request because prompt was set to 'none' but auth_time ('%s') happened after the authorization request ('%s') was registered, indicating that the user was logged in during this request which is not allowed.", claims.AuthTime, claims.RequestedAt))
145 }
146 }
147
148 if stringslice.Has(prompt, "login") {
149 if claims.AuthTime.Before(claims.RequestedAt) {
150 return errorsx.WithStack(fosite.ErrLoginRequired.WithHintf("Failed to validate OpenID Connect request because prompt was set to 'login' but auth_time ('%s') happened before the authorization request ('%s') was registered, indicating that the user was not re-authenticated which is forbidden.", claims.AuthTime, claims.RequestedAt))
151 }
152 }
153
154 idTokenHint := req.GetRequestForm().Get("id_token_hint")
155 if idTokenHint == "" {
156 return nil
157 }
158
159 tokenHint, err := v.Strategy.Decode(ctx, idTokenHint)
160 var ve *jwt.ValidationError
161 if errors.As(err, &ve) && ve.Has(jwt.ValidationErrorExpired) {
162
163 } else if err != nil {
164 return errorsx.WithStack(fosite.ErrInvalidRequest.WithHint("Failed to validate OpenID Connect request as decoding id token from id_token_hint parameter failed.").WithWrap(err).WithDebug(err.Error()))
165 }
166
167 if hintSub, _ := tokenHint.Claims["sub"].(string); hintSub == "" {
168 return errorsx.WithStack(fosite.ErrInvalidRequest.WithHint("Failed to validate OpenID Connect request because provided id token from id_token_hint does not have a subject."))
169 } else if hintSub != claims.Subject {
170 return errorsx.WithStack(fosite.ErrLoginRequired.WithHint("Failed to validate OpenID Connect request because the subject from provided id token from id_token_hint does not match the current session's subject."))
171 }
172
173 return nil
174 }
175
176 func isWhitelisted(items []string, whiteList []string) bool {
177 for _, item := range items {
178 if !stringslice.Has(whiteList, item) {
179 return false
180 }
181 }
182 return true
183 }
184
View as plain text