...
1
21
22 package openid
23
24 import (
25 "context"
26 "strconv"
27 "time"
28
29 "github.com/ory/x/errorsx"
30
31 "github.com/mohae/deepcopy"
32 "github.com/pkg/errors"
33
34 "github.com/ory/fosite"
35 "github.com/ory/fosite/token/jwt"
36 "github.com/ory/go-convenience/stringslice"
37 )
38
39 const defaultExpiryTime = time.Hour
40
41 type Session interface {
42
43
44 IDTokenClaims() *jwt.IDTokenClaims
45
46
47 IDTokenHeaders() *jwt.Headers
48
49 fosite.Session
50 }
51
52
53 type DefaultSession struct {
54 Claims *jwt.IDTokenClaims
55 Headers *jwt.Headers
56 ExpiresAt map[fosite.TokenType]time.Time
57 Username string
58 Subject string
59 }
60
61 func NewDefaultSession() *DefaultSession {
62 return &DefaultSession{
63 Claims: &jwt.IDTokenClaims{
64 RequestedAt: time.Now().UTC(),
65 },
66 Headers: &jwt.Headers{},
67 }
68 }
69
70 func (s *DefaultSession) Clone() fosite.Session {
71 if s == nil {
72 return nil
73 }
74
75 return deepcopy.Copy(s).(fosite.Session)
76 }
77
78 func (s *DefaultSession) SetExpiresAt(key fosite.TokenType, exp time.Time) {
79 if s.ExpiresAt == nil {
80 s.ExpiresAt = make(map[fosite.TokenType]time.Time)
81 }
82 s.ExpiresAt[key] = exp
83 }
84
85 func (s *DefaultSession) GetExpiresAt(key fosite.TokenType) time.Time {
86 if s.ExpiresAt == nil {
87 s.ExpiresAt = make(map[fosite.TokenType]time.Time)
88 }
89
90 if _, ok := s.ExpiresAt[key]; !ok {
91 return time.Time{}
92 }
93 return s.ExpiresAt[key]
94 }
95
96 func (s *DefaultSession) GetUsername() string {
97 if s == nil {
98 return ""
99 }
100 return s.Username
101 }
102
103 func (s *DefaultSession) SetSubject(subject string) {
104 s.Subject = subject
105 }
106
107 func (s *DefaultSession) GetSubject() string {
108 if s == nil {
109 return ""
110 }
111
112 return s.Subject
113 }
114
115 func (s *DefaultSession) IDTokenHeaders() *jwt.Headers {
116 if s.Headers == nil {
117 s.Headers = &jwt.Headers{}
118 }
119 return s.Headers
120 }
121
122 func (s *DefaultSession) IDTokenClaims() *jwt.IDTokenClaims {
123 if s.Claims == nil {
124 s.Claims = &jwt.IDTokenClaims{}
125 }
126 return s.Claims
127 }
128
129 type DefaultStrategy struct {
130 jwt.JWTStrategy
131
132 Expiry time.Duration
133 Issuer string
134
135 MinParameterEntropy int
136 }
137
138 func (h DefaultStrategy) GenerateIDToken(ctx context.Context, requester fosite.Requester) (token string, err error) {
139 if h.Expiry == 0 {
140 h.Expiry = defaultExpiryTime
141 }
142
143 sess, ok := requester.GetSession().(Session)
144 if !ok {
145 return "", errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because session must be of type fosite/handler/openid.Session."))
146 }
147
148 claims := sess.IDTokenClaims()
149 if claims.Subject == "" {
150 return "", errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because subject is an empty string."))
151 }
152
153 if requester.GetRequestForm().Get("grant_type") != "refresh_token" {
154 maxAge, err := strconv.ParseInt(requester.GetRequestForm().Get("max_age"), 10, 64)
155 if err != nil {
156 maxAge = 0
157 }
158
159
160 if claims.AuthTime.After(time.Now().UTC().Add(time.Second * 5)) {
161 return "", errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to validate OpenID Connect request because authentication time is in the future."))
162 }
163
164 if maxAge > 0 {
165 if claims.AuthTime.IsZero() {
166 return "", errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because authentication time claim is required when max_age is set."))
167 } else if claims.RequestedAt.IsZero() {
168 return "", errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because requested at claim is required when max_age is set."))
169 } else if claims.AuthTime.Add(time.Second * time.Duration(maxAge)).Before(claims.RequestedAt) {
170 return "", errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because authentication time does not satisfy max_age time."))
171 }
172 }
173
174 prompt := requester.GetRequestForm().Get("prompt")
175 if prompt != "" {
176 if claims.AuthTime.IsZero() {
177 return "", errorsx.WithStack(fosite.ErrServerError.WithDebug("Unable to determine validity of prompt parameter because auth_time is missing in id token claims."))
178 }
179 }
180
181 switch prompt {
182 case "none":
183 if !claims.AuthTime.Equal(claims.RequestedAt) && claims.AuthTime.After(claims.RequestedAt) {
184 return "", errorsx.WithStack(fosite.ErrServerError.
185 WithDebugf("Failed to generate id token 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))
186 }
187 case "login":
188 if !claims.AuthTime.Equal(claims.RequestedAt) && claims.AuthTime.Before(claims.RequestedAt) {
189 return "", errorsx.WithStack(fosite.ErrServerError.
190 WithDebugf("Failed to generate id token 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))
191 }
192 }
193
194
195
196 if requester.GetRequestForm().Get("acr_values") != "" && claims.AuthenticationContextClassReference == "" {
197 claims.AuthenticationContextClassReference = "0"
198 }
199
200 if tokenHintString := requester.GetRequestForm().Get("id_token_hint"); tokenHintString != "" {
201 tokenHint, err := h.JWTStrategy.Decode(ctx, tokenHintString)
202 var ve *jwt.ValidationError
203 if errors.As(err, &ve) && ve.Has(jwt.ValidationErrorExpired) {
204
205 } else if err != nil {
206 return "", errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebugf("Unable to decode id token from 'id_token_hint' parameter because %s.", err.Error()))
207 }
208
209 if hintSub, _ := tokenHint.Claims["sub"].(string); hintSub == "" {
210 return "", errorsx.WithStack(fosite.ErrServerError.WithDebug("Provided id token from 'id_token_hint' does not have a subject."))
211 } else if hintSub != claims.Subject {
212 return "", errorsx.WithStack(fosite.ErrServerError.WithDebug("Subject from authorization mismatches id token subject from 'id_token_hint'."))
213 }
214 }
215 }
216
217 if claims.ExpiresAt.IsZero() {
218 claims.ExpiresAt = time.Now().UTC().Add(h.Expiry)
219 }
220
221 if claims.ExpiresAt.Before(time.Now().UTC()) {
222 return "", errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because expiry claim can not be in the past."))
223 }
224
225 if claims.AuthTime.IsZero() {
226 claims.AuthTime = time.Now().Truncate(time.Second).UTC()
227 }
228
229 if claims.Issuer == "" {
230 claims.Issuer = h.Issuer
231 }
232
233
234 if nonce := requester.GetRequestForm().Get("nonce"); len(nonce) == 0 {
235 } else if len(nonce) > 0 && len(nonce) < h.MinParameterEntropy {
236
237 return "", errorsx.WithStack(fosite.ErrInsufficientEntropy.WithHintf("Parameter 'nonce' is set but does not satisfy the minimum entropy of %d characters.", h.MinParameterEntropy))
238 } else if len(nonce) > 0 {
239 claims.Nonce = nonce
240 }
241
242 claims.Audience = stringslice.Unique(append(claims.Audience, requester.GetClient().GetID()))
243 claims.IssuedAt = time.Now().UTC()
244
245 token, _, err = h.JWTStrategy.Generate(ctx, claims.ToMapClaims(), sess.IDTokenHeaders())
246 return token, err
247 }
248
View as plain text