Skip to content

Commit 869068c

Browse files
authored
Merge pull request #71 from IdentityPython/develop
OAuth2 Client code refactoring and linting
2 parents c6b7f5c + 55d16fc commit 869068c

File tree

205 files changed

+10739
-3893
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

205 files changed

+10739
-3893
lines changed

demo/README.md

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
# Usage stories
2+
3+
This is a set of usage stories.
4+
Here to display what you can do with IdpyOIDC using OAuth2 or OIDC.
5+
6+
Every story follows the same pattern it starts by initiating one client/RP and
7+
one AS/OP.
8+
After that a sequence of requests/responses are performed. Each one follows this
9+
pattern:
10+
11+
- The client/RP constructs the request and possible client authentication information
12+
- The request and client authentication information is printed
13+
- The AS/OP does client authentication based on the authentication information received
14+
- The AS/OP parses and verifies the client request
15+
- The AS/OP constructs the server response
16+
- The client/RP parses and verifies the server response
17+
- The parsed and verified response is printed
18+
19+
This pattern is repeated for each request/response in the sequence.
20+
21+
To understand the descriptions below you have to remember that an AS/OP provides
22+
**endpoints** while a client/RP accesses **services**. An endpoint can
23+
support more than one service. A service can only reside at one endpoint.
24+
25+
## Basic OAuth2 Stories
26+
27+
These are based on the two basic OAuth2 RFCs;
28+
* [The OAuth 2.0 Authorization Framework](https://www.rfc-editor.org/rfc/rfc6749)
29+
* [The OAuth 2.0 Authorization Framework: Bearer Token Usage](https://www.rfc-editor.org/rfc/rfc6750)
30+
31+
### Client Credentials Grant (oauth2_cc.py)
32+
33+
Displays the usage of the
34+
[client credentials grant](https://www.rfc-editor.org/rfc/rfc6749#section-4.4) .
35+
36+
The client can request an access token using only its client
37+
credentials (or other supported means of authentication).
38+
39+
The request/response sequence only contains the client credential exchange.
40+
41+
The client is statically registered with the AS.
42+
43+
#### configuration
44+
45+
The server configuration expresses these points:
46+
47+
- The server needs only one endpoint, the token endpoint.
48+
- The token released form the token endpoint is a signed JSON Web token (JWT)
49+
- The server deals only with access tokens. The default lifetime of a token is 3600
50+
seconds.
51+
- The server can deal with 2 client authentication methods at the token endpoint:
52+
client_secret_basic and client_secret_post
53+
- In this example the audience for the token (the resource server) is statically set.
54+
55+
56+
"endpoint": {
57+
"token": {
58+
"path": "token",
59+
"class": Token,
60+
"kwargs": {
61+
"client_authn_method": ["client_secret_basic", "client_secret_post"],
62+
},
63+
},
64+
},
65+
"token_handler_args": {
66+
"jwks_defs": {"key_defs": KEYDEFS},
67+
"token": {
68+
"class": "idpyoidc.server.token.jwt_token.JWTToken",
69+
"kwargs": {
70+
"lifetime": 3600,
71+
"aud": ["https://example.org/appl"],
72+
}
73+
}
74+
}
75+
76+
The client configuration
77+
78+
- lists only one service - client credentials
79+
- specifies client ID and client secret since the client is statically
80+
registered with the server.
81+
82+
83+
"client_id": "client_1",
84+
"client_secret": "another password",
85+
"base_url": "https://example.com",
86+
"services": {
87+
"client_credentials": {
88+
"class": "idpyoidc.client.oauth2.client_credentials.CCAccessTokenRequest"
89+
}
90+
}
91+
92+
**services** is a dictionary. The keys in that dictionary is for your usage only.
93+
Internally the software uses identifiers that are statically assigned to every Service class.
94+
This means that you can not have two instances of the same class in a _services_
95+
definition.
96+
97+
### Resource Owners Password Credentials (oauth2_ropc.py)
98+
99+
**NOTE** Resource Owners Password Credentials is not part of OAuth2.1
100+
101+
Displays the usage of the
102+
[resource owners username and password](https://www.rfc-editor.org/rfc/rfc6749#section-4.3)
103+
for doing authorization.
104+
105+
The resource owner password credentials grant type is suitable in
106+
cases where the resource owner has a trust relationship with the
107+
client, such as the device operating system or a highly privileged application.
108+
109+
#### Configuration
110+
111+
The big difference between Client Credentials and Resource Owners Passsword credentials
112+
is that the server also most support user authentication. Therefor this
113+
part is added to the server configuration:
114+
115+
"authentication": {
116+
"user": {
117+
"acr": "urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocolPassword",
118+
"class": "idpyoidc.server.user_authn.user.UserPass",
119+
"kwargs": {
120+
"db_conf": {
121+
"class": "idpyoidc.server.util.JSONDictDB",
122+
"kwargs": {"filename": full_path("passwd.json")}
123+
}
124+
}
125+
}
126+
}
127+
128+
This allows for a very simple username/password check against a static file.
129+
130+
On the client side the change is that the service configuration now looks
131+
like this:
132+
133+
services = {
134+
"ropc": {
135+
"class": "idpyoidc.client.oauth2.resource_owner_password_credentials.ROPCAccessTokenRequest"
136+
}
137+
}
138+
139+
140+
### Authorization Code Grant (oauth2_code.py)
141+
142+
The
143+
[authorization code grant](https://www.rfc-editor.org/rfc/rfc6749#section-4.1)
144+
is used to obtain both access tokens and possibly refresh tokens and is optimized
145+
for confidential clients.
146+
147+
Since this is a redirection-based flow, the client must be capable of
148+
interacting with the resource owner's user-agent (typically a web
149+
browser) and capable of receiving incoming requests (via redirection)
150+
from the authorization server.
151+
152+
In the demo implementation the response is transmitted directly from the server
153+
to the client no user agent is involved.
154+
155+
In this story the flow contains three request/responses
156+
157+
- Fetching server metadata
158+
- Authorization
159+
- Access token
160+
161+
#### Configuration
162+
163+
Let's take it part by part.
164+
First the endpoints, straight forward support for the sequence of exchanges we
165+
want to exercise.
166+
167+
"endpoint": {
168+
"metadata": {
169+
"path": ".well-known/oauth-authorization-server",
170+
"class": "idpyoidc.server.oauth2.server_metadata.ServerMetadata",
171+
"kwargs": {},
172+
},
173+
"authorization": {
174+
"path": "authorization",
175+
"class": "idpyoidc.server.oauth2.authorization.Authorization",
176+
"kwargs": {},
177+
},
178+
"token": {
179+
"path": "token",
180+
"class": "idpyoidc.server.oauth2.token.Token",
181+
"kwargs": {},
182+
}
183+
},
184+
185+
Next comes the type of tokens the grant manager can issue.
186+
In this case authorization codes and access tokens.
187+
188+
"token_handler_args": {
189+
"key_conf": {"key_defs": KEYDEFS},
190+
"code": {
191+
"lifetime": 600,
192+
"kwargs": {
193+
"crypt_conf": CRYPT_CONFIG
194+
}
195+
},
196+
"token": {
197+
"class": "idpyoidc.server.token.jwt_token.JWTToken",
198+
"kwargs": {
199+
"lifetime": 3600,
200+
"aud": ["https://example.org/appl"],
201+
},
202+
}
203+
},
204+
205+
The software can produce 3 types of tokens.
206+
207+
- An encrypted value, unreadable by anyone but the server
208+
- A signed JSON Web Token following the pattern described in
209+
[JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens](https://datatracker.ietf.org/doc/rfc9068/)
210+
- An IDToken which only is used to represent ID Tokens.
211+
212+
In this example only the two first types are used since no ID Tokens are produced.
213+
214+
The next part is about the grant manager.
215+
216+
"authz": {
217+
"class": AuthzHandling,
218+
"kwargs": {
219+
"grant_config": {
220+
"usage_rules": {
221+
"authorization_code": {
222+
"supports_minting": ["access_token"],
223+
"max_usage": 1,
224+
},
225+
"access_token": {
226+
"expires_in": 600,
227+
}
228+
}
229+
}
230+
},
231+
},
232+
233+
What this says is that an authorization code can only be used once and
234+
only to mint an access token. The lifetime for an authorization code is
235+
the default which is 300 seconds (5 minutes).
236+
The access token can not be used to mint anything. Note that in the
237+
token handler arguments the lifetime is set to 3600 seconds for a token
238+
while in the authz part and access tokens lifetime is defined to be
239+
600 seconds. It's the later that is used since it is more specific.
240+
241+
"authentication": {
242+
"anon": {
243+
"acr": INTERNETPROTOCOLPASSWORD,
244+
"class": "idpyoidc.server.user_authn.user.NoAuthn",
245+
"kwargs": {"user": "diana"},
246+
}
247+
},
248+
249+
It's convenient to use this no-authentication method in this context since we
250+
can't deal with user interaction.
251+
What happens is that authentication is assumed to have happened and that
252+
it resulted in that **diana** was authenticated.
253+
254+
## OAuth2 Extension Stories
255+
256+
The stories display support for a set of OAuth2 extension RFCs
257+
258+
### PKCE (oauth2_add_on_pkce.py)
259+
260+
[Proof Key for Code Exchange by OAuth Public Clients](https://datatracker.ietf.org/doc/rfc7636/).
261+
A technique to mitigate against the authorization code interception attack through
262+
the use of Proof Key for Code Exchange (PKCE).
263+
264+
#### Configuration
265+
266+
On the server side only one thing is added:
267+
268+
"add_ons": {
269+
"pkce": {
270+
"function": "idpyoidc.server.oauth2.add_on.pkce.add_support",
271+
"kwargs": {},
272+
},
273+
}
274+
275+
Similar on the client side:
276+
277+
"add_ons": {
278+
"pkce": {
279+
"function": "idpyoidc.client.oauth2.add_on.pkce.add_support",
280+
"kwargs": {
281+
"code_challenge_length": 64,
282+
"code_challenge_method": "S256"
283+
},
284+
},
285+
}
286+
287+
### JAR (oauth2_add_on_jar.py)
288+
289+
[JWT-Secured Authorization Request (JAR)](https://datatracker.ietf.org/doc/rfc9101/)
290+
This document introduces the ability to send request parameters in a
291+
JSON Web Token (JWT) instead, which allows the request to be signed
292+
with JSON Web Signature (JWS) and encrypted with JSON Web Encryption
293+
(JWE) so that the integrity, source authentication, and
294+
confidentiality properties of the authorization request are attained.
295+
The request can be sent by value or by reference.
296+
297+
#### Configuration
298+
299+
On the server side nothing has to be done. The support for the
300+
request and request_uri parameters are built in to begin with.
301+
The reason for this is that OIDC had this from the beginning.
302+
303+
On the client side this had to be added:
304+
305+
"add_ons": {
306+
"jar": {
307+
"function": "idpyoidc.client.oauth2.add_on.jar.add_support",
308+
"kwargs": {
309+
'request_type': 'request_parameter',
310+
'request_object_signing_alg': "ES256",
311+
'expires_in': 600
312+
},
313+
},
314+
}
315+

demo/common.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import os
2+
3+
BASEDIR = os.path.abspath(os.path.dirname(__file__))
4+
5+
6+
def full_path(local_file):
7+
return os.path.join(BASEDIR, local_file)
8+
9+
10+
CRYPT_CONFIG = {
11+
"kwargs": {
12+
"keys": {
13+
"key_defs": [
14+
{"type": "OCT", "use": ["enc"], "kid": "password"},
15+
{"type": "OCT", "use": ["enc"], "kid": "salt"},
16+
]
17+
},
18+
"iterations": 1,
19+
}
20+
}
21+
22+
SESSION_PARAMS = {"encrypter": CRYPT_CONFIG}
23+
24+
KEYDEFS = [
25+
{"type": "RSA", "key": "", "use": ["sig"]},
26+
{"type": "EC", "crv": "P-256", "use": ["sig"]},
27+
]

0 commit comments

Comments
 (0)