1# -*- coding: utf-8 -*-
2"""
3oauthlib.oauth2.rfc6749
4~~~~~~~~~~~~~~~~~~~~~~~
5
6This module is an implementation of various logic needed
7for consuming and providing OAuth 2.0 RFC6749.
8"""
9import time
10
11from oauthlib.common import to_unicode
12
13from ..parameters import prepare_token_request
14from .base import Client
15
16
17class ServiceApplicationClient(Client):
18 """A public client utilizing the JWT bearer grant.
19
20 JWT bearer tokes can be used to request an access token when a client
21 wishes to utilize an existing trust relationship, expressed through the
22 semantics of (and digital signature or keyed message digest calculated
23 over) the JWT, without a direct user approval step at the authorization
24 server.
25
26 This grant type does not involve an authorization step. It may be
27 used by both public and confidential clients.
28 """
29
30 grant_type = 'urn:ietf:params:oauth:grant-type:jwt-bearer'
31
32 def __init__(self, client_id, private_key=None, subject=None, issuer=None,
33 audience=None, **kwargs):
34 """Initialize a JWT client with defaults for implicit use later.
35
36 :param client_id: Client identifier given by the OAuth provider upon
37 registration.
38
39 :param private_key: Private key used for signing and encrypting.
40 Must be given as a string.
41
42 :param subject: The principal that is the subject of the JWT, i.e.
43 which user is the token requested on behalf of.
44 For example, ``foo@example.com.
45
46 :param issuer: The JWT MUST contain an "iss" (issuer) claim that
47 contains a unique identifier for the entity that issued
48 the JWT. For example, ``your-client@provider.com``.
49
50 :param audience: A value identifying the authorization server as an
51 intended audience, e.g.
52 ``https://provider.com/oauth2/token``.
53
54 :param kwargs: Additional arguments to pass to base client, such as
55 state and token. See ``Client.__init__.__doc__`` for
56 details.
57 """
58 super().__init__(client_id, **kwargs)
59 self.private_key = private_key
60 self.subject = subject
61 self.issuer = issuer
62 self.audience = audience
63
64 def prepare_request_body(self,
65 private_key=None,
66 subject=None,
67 issuer=None,
68 audience=None,
69 expires_at=None,
70 issued_at=None,
71 extra_claims=None,
72 body='',
73 scope=None,
74 include_client_id=False,
75 **kwargs):
76 """Create and add a JWT assertion to the request body.
77
78 :param private_key: Private key used for signing and encrypting.
79 Must be given as a string.
80
81 :param subject: (sub) The principal that is the subject of the JWT,
82 i.e. which user is the token requested on behalf of.
83 For example, ``foo@example.com.
84
85 :param issuer: (iss) The JWT MUST contain an "iss" (issuer) claim that
86 contains a unique identifier for the entity that issued
87 the JWT. For example, ``your-client@provider.com``.
88
89 :param audience: (aud) A value identifying the authorization server as an
90 intended audience, e.g.
91 ``https://provider.com/oauth2/token``.
92
93 :param expires_at: A unix expiration timestamp for the JWT. Defaults
94 to an hour from now, i.e. ``round(time.time()) + 3600``.
95
96 :param issued_at: A unix timestamp of when the JWT was created.
97 Defaults to now, i.e. ``time.time()``.
98
99 :param extra_claims: A dict of additional claims to include in the JWT.
100
101 :param body: Existing request body (URL encoded string) to embed parameters
102 into. This may contain extra parameters. Default ''.
103
104 :param scope: The scope of the access request.
105
106 :param include_client_id: `True` to send the `client_id` in the
107 body of the upstream request. This is required
108 if the client is not authenticating with the
109 authorization server as described in
110 `Section 3.2.1`_. False otherwise (default).
111 :type include_client_id: Boolean
112
113 :param not_before: A unix timestamp after which the JWT may be used.
114 Not included unless provided. *
115
116 :param jwt_id: A unique JWT token identifier. Not included unless
117 provided. *
118
119 :param kwargs: Extra credentials to include in the token request.
120
121 Parameters marked with a `*` above are not explicit arguments in the
122 function signature, but are specially documented arguments for items
123 appearing in the generic `**kwargs` keyworded input.
124
125 The "scope" parameter may be used, as defined in the Assertion
126 Framework for OAuth 2.0 Client Authentication and Authorization Grants
127 [I-D.ietf-oauth-assertions] specification, to indicate the requested
128 scope.
129
130 Authentication of the client is optional, as described in
131 `Section 3.2.1`_ of OAuth 2.0 [RFC6749] and consequently, the
132 "client_id" is only needed when a form of client authentication that
133 relies on the parameter is used.
134
135 The following non-normative example demonstrates an Access Token
136 Request with a JWT as an authorization grant (with extra line breaks
137 for display purposes only):
138
139 .. code-block: http
140
141 POST /token.oauth2 HTTP/1.1
142 Host: as.example.com
143 Content-Type: application/x-www-form-urlencoded
144
145 grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
146 &assertion=eyJhbGciOiJFUzI1NiJ9.
147 eyJpc3Mi[...omitted for brevity...].
148 J9l-ZhwP[...omitted for brevity...]
149
150 .. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1
151 """
152 import jwt # noqa: PLC0415
153
154 key = private_key or self.private_key
155 if not key:
156 raise ValueError('An encryption key must be supplied to make JWT'
157 ' token requests.')
158 claim = {
159 'iss': issuer or self.issuer,
160 'aud': audience or self.audience,
161 'sub': subject or self.subject,
162 'exp': int(expires_at or time.time() + 3600),
163 'iat': int(issued_at or time.time()),
164 }
165
166 for attr in ('iss', 'aud', 'sub'):
167 if claim[attr] is None:
168 raise ValueError(
169 'Claim must include %s but none was given.' % attr)
170
171 if 'not_before' in kwargs:
172 claim['nbf'] = kwargs.pop('not_before')
173
174 if 'jwt_id' in kwargs:
175 claim['jti'] = kwargs.pop('jwt_id')
176
177 claim.update(extra_claims or {})
178
179 assertion = jwt.encode(claim, key, 'RS256')
180 assertion = to_unicode(assertion)
181
182 kwargs['client_id'] = self.client_id
183 kwargs['include_client_id'] = include_client_id
184 scope = self.scope if scope is None else scope
185 return prepare_token_request(self.grant_type,
186 body=body,
187 assertion=assertion,
188 scope=scope,
189 **kwargs)