Skip to content

Commit d6522da

Browse files
committed
add(handler): Token Exchange rfc8693 in impersonation mode
1 parent e570564 commit d6522da

File tree

7 files changed

+808
-1
lines changed

7 files changed

+808
-1
lines changed

handler/rfc8693/handler.go

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
// Copyright © 2022 Ory Corp
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package rfc8693
5+
6+
import (
7+
"context"
8+
"errors"
9+
"fmt"
10+
"time"
11+
12+
"github.com/ory/fosite"
13+
"github.com/ory/fosite/compose"
14+
"github.com/ory/fosite/handler/oauth2"
15+
"github.com/ory/fosite/token/jwt"
16+
"github.com/ory/x/errorsx"
17+
)
18+
19+
// #nosec G101
20+
const (
21+
tokenTypeIDToken = "urn:ietf:params:oauth:token-type:id_token"
22+
tokenTypeAT = "urn:ietf:params:oauth:token-type:access_token"
23+
)
24+
25+
func TokenExchangeGrantFactory(config *compose.CommonStrategy, storage, strategy interface{}) interface{} {
26+
return nil
27+
}
28+
29+
type Handler struct {
30+
Storage RFC8693Storage
31+
Strategy ClientAuthenticationStrategy
32+
ScopeStrategy fosite.ScopeStrategy
33+
AudienceMatchingStrategy fosite.AudienceMatchingStrategy
34+
RefreshTokenStrategy oauth2.RefreshTokenStrategy
35+
RefreshTokenStorage oauth2.RefreshTokenStorage
36+
fosite.RefreshTokenScopesProvider
37+
38+
*oauth2.HandleHelper
39+
}
40+
41+
type tokenExchangeParams struct {
42+
subjectToken string
43+
subjectTokenType string
44+
}
45+
46+
func parseRequestParameter(requester fosite.AccessRequester) (*tokenExchangeParams, error) {
47+
form := requester.GetRequestForm()
48+
49+
// From https://tools.ietf.org/html/rfc8693#section-2.1:
50+
//
51+
// subject_token
52+
// REQUIRED. A security token that represents the identity of the
53+
// party on behalf of whom the request is being made. Typically, the
54+
// subject of this token will be the subject of the security token
55+
// issued in response to the request.
56+
subjectToken := form.Get("subject_token")
57+
if subjectToken == "" {
58+
return nil, errorsx.WithStack(fosite.ErrInvalidRequest.WithHintf("subject_token is missing"))
59+
}
60+
61+
// From https://tools.ietf.org/html/rfc8693#section-2.1:
62+
//
63+
// subject_token_type
64+
// REQUIRED. An identifier, that indicates the type of the
65+
// security token in the "subject_token" parameter.
66+
subjectTokenType := form.Get("subject_token_type")
67+
switch subjectTokenType {
68+
case tokenTypeIDToken, tokenTypeAT:
69+
default:
70+
return nil, errorsx.WithStack(fosite.ErrInvalidRequest.WithHintf("unsupported or missing subject_token_type %s", subjectTokenType))
71+
}
72+
73+
// From https://tools.ietf.org/html/rfc8693#section-2.1:
74+
//
75+
// requested_token_type
76+
// OPTIONAL. An identifier, for the type of the requested security token.
77+
// If the requested type is unspecified,
78+
// the issued token type is at the discretion of the authorization server and
79+
// may be dictated by knowledge of the requirements of the service or
80+
// resource indicated by the resource or audience parameter.
81+
requestedTokenType := form.Get("requested_token_type")
82+
switch requestedTokenType {
83+
case tokenTypeAT, "":
84+
default:
85+
return nil, errorsx.WithStack(fosite.ErrInvalidRequest.WithHintf("unsupported requested_token_type %s", requestedTokenType))
86+
}
87+
88+
// From https://tools.ietf.org/html/rfc8693#section-2.1:
89+
//
90+
// actor_token
91+
// OPTIONAL . A security token that represents the identity of the acting party.
92+
// Typically, this will be the party that is authorized to use the requested security
93+
// token and act on behalf of the subject.
94+
actorToken := form.Get("actor_token")
95+
if actorToken != "" {
96+
return nil, errorsx.WithStack(fosite.ErrInvalidRequest.WithHintf("'actor_token' was provided but delegation is currently not supported."))
97+
}
98+
99+
// From https://tools.ietf.org/html/rfc8693#section-2.1:
100+
//
101+
// actor_token_type
102+
// An identifier, as described in Section 3, that indicates the type of the security token
103+
// in the actor_token parameter. This is REQUIRED when the actor_token parameter is present
104+
// in the request but MUST NOT be included otherwise.
105+
actorTokenType := form.Get("actor_token_type")
106+
if actorTokenType != "" {
107+
return nil, errorsx.WithStack(fosite.ErrInvalidRequest.WithHintf("'actor_token_type' was provided but delegation is currently not supported."))
108+
}
109+
110+
return &tokenExchangeParams{
111+
subjectToken: subjectToken,
112+
subjectTokenType: subjectTokenType,
113+
}, nil
114+
}
115+
116+
func (c *Handler) HandleTokenEndpointRequest(ctx context.Context, requester fosite.AccessRequester) error {
117+
if !c.CanHandleTokenEndpointRequest(requester) {
118+
return errorsx.WithStack(fosite.ErrUnknownRequest)
119+
}
120+
121+
client := requester.GetClient()
122+
if client.GetID() == "" {
123+
return errorsx.WithStack(fosite.ErrUnauthorizedClient.WithHint("unauthenticated client"))
124+
}
125+
126+
// Check whether client is allowed to use token exchange.
127+
if !client.GetGrantTypes().Has(string(fosite.GrantTypeTokenExchange)) {
128+
return errorsx.WithStack(fosite.ErrUnauthorizedClient.WithHint("the client is not allowed to use token-exchange"))
129+
}
130+
131+
// Get request parameter related token exchange.
132+
params, err := parseRequestParameter(requester)
133+
if err != nil {
134+
return err
135+
}
136+
137+
// Check and grant scope.
138+
for _, scope := range requester.GetRequestedScopes() {
139+
if !c.ScopeStrategy(client.GetScopes(), scope) {
140+
return errorsx.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope '%s'.", scope))
141+
}
142+
requester.GrantScope(scope)
143+
}
144+
145+
// Check and grant audience.
146+
if err := c.AudienceMatchingStrategy(client.GetAudience(), requester.GetRequestedAudience()); err != nil {
147+
return errorsx.WithStack(fosite.ErrInvalidRequest.WithHintf("audience not match: %v", err))
148+
}
149+
for _, audience := range requester.GetRequestedAudience() {
150+
requester.GrantAudience(audience)
151+
}
152+
153+
// Verify subject token.
154+
switch params.subjectTokenType {
155+
case tokenTypeIDToken:
156+
claims := jwt.MapClaims{}
157+
if _, err := jwt.ParseWithClaims(params.subjectToken, claims, c.keyFunc(ctx)); err != nil {
158+
return errorsx.WithStack(fosite.ErrInvalidRequest.WithHintf("failed to verify JWT: %v", err))
159+
}
160+
subject, err := c.Storage.GetImpersonateSubject(ctx, claims, requester)
161+
if err != nil {
162+
return errorsx.WithStack(fosite.ErrInvalidRequest.WithHintf("not allowed to token exchange by jwt: %v", err))
163+
}
164+
requester.SetSession(&fosite.DefaultSession{
165+
Subject: subject,
166+
})
167+
requester.GetSession().SetExpiresAt(fosite.AccessToken, time.Now().UTC().Add(c.Config.GetAccessTokenLifespan(ctx)))
168+
return nil
169+
case tokenTypeAT:
170+
or, err := c.verifyAccessTokenAsSubjectToken(ctx, client.GetID(), params)
171+
if err != nil {
172+
return errorsx.WithStack(fosite.ErrInvalidRequest.WithHintf("not allowed to token exchange by at: %v", err))
173+
}
174+
requester.SetSession(or.GetSession().Clone())
175+
// When the subject_type is AT, the expiration time is same with subject_token.
176+
// Therefore, we don't need to set the expiresAt.
177+
return nil
178+
default:
179+
return errorsx.WithStack(fosite.ErrInvalidRequest.WithHintf("unsupported subject_type %s", params.subjectTokenType))
180+
}
181+
}
182+
183+
func (c *Handler) PopulateTokenEndpointResponse(ctx context.Context, requester fosite.AccessRequester, responder fosite.AccessResponder) error {
184+
if !c.CanHandleTokenEndpointRequest(requester) {
185+
return errorsx.WithStack(fosite.ErrUnknownRequest)
186+
}
187+
188+
if !requester.GetClient().GetGrantTypes().Has(string(fosite.GrantTypeTokenExchange)) {
189+
return errorsx.WithStack(fosite.ErrUnauthorizedClient.WithHintf("The OAuth 2.0 Client is not allowed to use authorization grant '%s'.", fosite.GrantTypeTokenExchange))
190+
}
191+
192+
atLifespan := fosite.GetEffectiveLifespan(requester.GetClient(), fosite.GrantTypeTokenExchange, fosite.AccessToken, c.Config.GetAccessTokenLifespan(ctx))
193+
194+
if err := c.IssueAccessToken(ctx, atLifespan, requester, responder); err != nil {
195+
return err
196+
}
197+
198+
if canIssueRefreshToken(ctx, c, requester) {
199+
fmt.Println(requester)
200+
refresh, refreshSignature, err := c.RefreshTokenStrategy.GenerateRefreshToken(ctx, requester)
201+
if err != nil {
202+
return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error()))
203+
}
204+
if err := c.RefreshTokenStorage.CreateRefreshTokenSession(ctx, refreshSignature, requester); err != nil {
205+
return errorsx.WithStack(fosite.ErrServerError.WithDebug(err.Error()))
206+
}
207+
208+
responder.SetExtra("refresh_token", refresh)
209+
}
210+
return nil
211+
}
212+
213+
func canIssueRefreshToken(ctx context.Context, c *Handler, requester fosite.Requester) bool {
214+
scope := c.GetRefreshTokenScopes(ctx)
215+
// Require one of the refresh token scopes, if set.
216+
if len(scope) > 0 && !requester.GetGrantedScopes().HasOneOf(scope...) {
217+
return false
218+
}
219+
// Do not issue a refresh token to clients that cannot use the refresh token grant type.
220+
if !requester.GetClient().GetGrantTypes().Has("refresh_token") {
221+
return false
222+
}
223+
return true
224+
}
225+
226+
func (c *Handler) CanSkipClientAuth(requester fosite.AccessRequester) bool {
227+
return c.Strategy.CanSkipClientAuth(requester)
228+
}
229+
230+
func (c *Handler) keyFunc(ctx context.Context) jwt.Keyfunc {
231+
return jwt.Keyfunc(func(t *jwt.Token) (interface{}, error) {
232+
kid, ok := t.Header["kid"].(string)
233+
if !ok {
234+
return nil, errors.New("invalid kid")
235+
}
236+
iss, ok := t.Claims["iss"].(string)
237+
if !ok {
238+
return nil, errors.New("invalid iss")
239+
}
240+
return c.Storage.GetIDTokenPublicKey(ctx, iss, kid)
241+
})
242+
}
243+
244+
func (c *Handler) verifyAccessTokenAsSubjectToken(ctx context.Context, clientID string, params *tokenExchangeParams) (fosite.Requester, error) {
245+
sig := c.HandleHelper.AccessTokenStrategy.AccessTokenSignature(ctx, params.subjectToken)
246+
or, err := c.HandleHelper.AccessTokenStorage.GetAccessTokenSession(ctx, sig, nil)
247+
if err != nil {
248+
return nil, errorsx.WithStack(fosite.ErrRequestUnauthorized.WithWrap(err).WithDebug(err.Error()))
249+
} else if err := c.AccessTokenStrategy.ValidateAccessToken(ctx, or, params.subjectToken); err != nil {
250+
return nil, err
251+
}
252+
253+
allowClientIDs, err := c.Storage.GetAllowedClientIDs(ctx, clientID)
254+
if err != nil {
255+
return nil, err
256+
}
257+
258+
for _, cid := range allowClientIDs {
259+
if or.GetClient().GetID() == cid {
260+
return or, nil
261+
}
262+
}
263+
return nil, fmt.Errorf("this access_token is not allowed to use token exchange based on AT: original_client:%s, request_client:%s ", or.GetClient().GetID(), clientID)
264+
}
265+
266+
func (c *Handler) CanHandleTokenEndpointRequest(requester fosite.AccessRequester) bool {
267+
return requester.GetGrantTypes().ExactOne(string(fosite.GrantTypeTokenExchange))
268+
}

0 commit comments

Comments
 (0)