Skip to content

Commit c6b7f5c

Browse files
author
Giuseppe De Marco
authored
Merge pull request #63 from IdentityPython/develop
Version 2.0
2 parents 90f6330 + ca7e310 commit c6b7f5c

File tree

229 files changed

+12425
-6920
lines changed

Some content is hidden

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

229 files changed

+12425
-6920
lines changed

archdoc/docs/client/index.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# The IdpyOIDC client
2+
3+
A client can send requests to an endpoint and deal with the response.
4+
5+
IdpyOIDC assumes that there is one Relying Party(RP)/Client instance per
6+
OpenID Connect Provider(OP)/Authorization Server (AS).
7+
8+
If you have a service that expects to talk to several OPs/ASs
9+
then you must use **idpyoidc.client.rp_handler.RPHandler** to manage the RPs.
10+
11+
RPHandler has methods like:
12+
- begin()
13+
- finalize()
14+
- refresh_access_token()
15+
- logout()
16+
17+
More about RPHandler at the end of this section.
18+
19+
## Client
20+
21+
A client is configured to talk to a set of services each of them represented by
22+
a Service Instance.
23+
24+
# Context
25+
26+
# Service
27+
28+
A Service instance is expected to be able to:
29+
30+
1. Collect all the request arguments
31+
2. If necessary collect and add authentication information to the request attributes or HTTP header
32+
3. Formats the message
33+
4. chooses HTTP method
34+
5. Add HTTP headers
35+
36+
and then after having received the response:
37+
38+
1. Parses the response
39+
2. Gather verification information and verify the response
40+
3. Do any special post-processing.
41+
3. Store information from the response
42+
43+
Doesn't matter which service is considered they all have to be able to do this.
44+
45+
## Request
46+
47+
## Response
48+
49+
# AddOn
50+
51+
# Endpoints
52+
53+
## OAuth2
54+
55+
- Access Token
56+
- Authorization
57+
- Refresh Access Token
58+
- Server Metadata
59+
- Token Exchange

archdoc/docs/combo/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# An entity that can act both as a server and a client

archdoc/docs/server/index.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
# Service
3+
4+
## Request
5+
6+
## Response
7+
8+
# Context
9+
10+
# AddOn

doc/server/contents/conf.rst

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,23 @@ An example::
408408
"normal",
409409
"aggregated",
410410
"distributed"
411+
],
412+
"policy": {
413+
"function": "/path/to/callable",
414+
"kwargs": {}
415+
}
416+
}
417+
},
418+
"revocation": {
419+
"path": "revoke",
420+
"class": "idpyoidc.server.oauth2.revocation.Revocation",
421+
"kwargs": {
422+
"client_authn_method": [
423+
"client_secret_post",
424+
"client_secret_basic",
425+
"client_secret_jwt",
426+
"private_key_jwt",
427+
"bearer_header"
411428
]
412429
}
413430
},
@@ -734,6 +751,10 @@ the following::
734751
"userinfo": {
735752
"class": "oidc_provider.users.UserInfo",
736753
"kwargs": {
754+
"policy": {
755+
"function": "/path/to/callable",
756+
"kwargs": {}
757+
},
737758
"claims_map": {
738759
"phone_number": "telephone",
739760
"family_name": "last_name",
@@ -747,6 +768,17 @@ the following::
747768
}
748769
}
749770

771+
The policy for userinfo endpoint is optional and can also be configured in a client's metadata, for example::
772+
773+
"userinfo": {
774+
"kwargs": {
775+
"policy": {
776+
"function": "/path/to/callable",
777+
"kwargs": {}
778+
}
779+
}
780+
}
781+
750782
================================
751783
Special Configuration directives
752784
================================
@@ -875,6 +907,106 @@ For example::
875907
return request
876908

877909

910+
==============
911+
Token revocation
912+
==============
913+
914+
In order to enable the token revocation endpoint a dictionary with key `token_revocation` should be placed
915+
under the `endpoint` key of the configuration.
916+
917+
If present, the token revocation configuration should contain a `policy` dictionary
918+
that defines the behaviour for each token type. Each token type
919+
is mapped to a dictionary with the keys `callable` (mandatory), which must be a
920+
python callable or a string that represents the path to a python callable, and
921+
`kwargs` (optional), which must be a dict of key-value arguments that will be
922+
passed to the callable.
923+
924+
The key `""` represents a fallback policy that will be used if the token
925+
type can't be found. If a token type is defined in the `policy` but is
926+
not in the `token_types_supported` list then it is ignored.
927+
928+
"token_revocation": {
929+
"path": "revoke",
930+
"class": "idpyoidc.server.oauth2.token_revocation.TokenRevocation",
931+
"kwargs": {
932+
"token_types_supported": ["access_token"],
933+
"client_authn_method": [
934+
"client_secret_post",
935+
"client_secret_basic",
936+
"client_secret_jwt",
937+
"private_key_jwt",
938+
"bearer_header"
939+
],
940+
"policy": {
941+
"urn:ietf:params:oauth:token-type:access_token": {
942+
"callable": "/path/to/callable",
943+
"kwargs": {
944+
"audience": ["https://example.com"],
945+
"scopes": ["openid"]
946+
}
947+
},
948+
"urn:ietf:params:oauth:token-type:refresh_token": {
949+
"callable": "/path/to/callable",
950+
"kwargs": {
951+
"resource": ["https://example.com"],
952+
"scopes": ["openid"]
953+
}
954+
},
955+
"": {
956+
"callable": "/path/to/callable",
957+
"kwargs": {
958+
"scopes": ["openid"]
959+
}
960+
}
961+
}
962+
}
963+
}
964+
965+
For the per-client configuration a similar configuration scheme should be present in the client's
966+
metadata under the `token_revocation` key.
967+
968+
For example::
969+
970+
"token_revocation":{
971+
"token_types_supported": ["access_token"],
972+
"policy": {
973+
"urn:ietf:params:oauth:token-type:access_token": {
974+
"callable": "/path/to/callable",
975+
"kwargs": {
976+
"audience": ["https://example.com"],
977+
"scopes": ["openid"]
978+
}
979+
},
980+
"urn:ietf:params:oauth:token-type:refresh_token": {
981+
"callable": "/path/to/callable",
982+
"kwargs": {
983+
"resource": ["https://example.com"],
984+
"scopes": ["openid"]
985+
}
986+
},
987+
"": {
988+
"callable": "/path/to/callable",
989+
"kwargs": {
990+
"scopes": ["openid"]
991+
}
992+
}
993+
}
994+
}
995+
}
996+
997+
The policy callable accepts a specific argument list and handles the revocation appropriately and returns
998+
an :py:class:`idpyoidc.message.oauth2..TokenRevocationResponse` or raises an exception.
999+
1000+
For example::
1001+
1002+
def custom_token_revocation_policy(token, session_info, **kwargs):
1003+
if some_condition:
1004+
return TokenErrorResponse(
1005+
error="invalid_request", error_description="Some error occured"
1006+
)
1007+
response_args = {"response_args": {}}
1008+
return oauth2.TokenRevocationResponse(**response_args)
1009+
8781010
==================================
8791011
idpyoidc\.server\.configure module
8801012
==================================

example/flask_op/views.py

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def verify(authn_method):
119119
auth_args = authn_method.unpack_token(kwargs['token'])
120120
authz_request = AuthorizationRequest().from_urlencoded(auth_args['query'])
121121

122-
endpoint = current_app.server.server_get("endpoint", 'authorization')
122+
endpoint = current_app.server.get_endpoint('authorization')
123123
_session_id = endpoint.create_session(authz_request, username, auth_args['authn_class_ref'],
124124
auth_args['iat'], authn_method)
125125

@@ -133,8 +133,7 @@ def verify(authn_method):
133133

134134
@oidc_op_views.route('/verify/user', methods=['GET', 'POST'])
135135
def verify_user():
136-
authn_method = current_app.server.server_get(
137-
"endpoint_context").authn_broker.get_method_by_id('user')
136+
authn_method = current_app.server.get_context().authn_broker.get_method_by_id('user')
138137
try:
139138
return verify(authn_method)
140139
except FailedAuthentication as exc:
@@ -143,8 +142,7 @@ def verify_user():
143142

144143
@oidc_op_views.route('/verify/user_pass_jinja', methods=['GET', 'POST'])
145144
def verify_user_pass_jinja():
146-
authn_method = current_app.server.server_get(
147-
"endpoint_context").authn_broker.get_method_by_id('user')
145+
authn_method = current_app.server.get_context().authn_broker.get_method_by_id('user')
148146
try:
149147
return verify(authn_method)
150148
except FailedAuthentication as exc:
@@ -154,9 +152,9 @@ def verify_user_pass_jinja():
154152
@oidc_op_views.route('/.well-known/<service>')
155153
def well_known(service):
156154
if service == 'openid-configuration':
157-
_endpoint = current_app.server.server_get("endpoint", 'provider_config')
155+
_endpoint = current_app.server.get_endpoint('provider_config')
158156
elif service == 'webfinger':
159-
_endpoint = current_app.server.server_get("endpoint", 'discovery')
157+
_endpoint = current_app.server.get_endpoint('discovery')
160158
else:
161159
return make_response('Not supported', 400)
162160

@@ -166,45 +164,45 @@ def well_known(service):
166164
@oidc_op_views.route('/registration', methods=['GET', 'POST'])
167165
def registration():
168166
return service_endpoint(
169-
current_app.server.server_get("endpoint", 'registration'))
167+
current_app.server.get_endpoint('registration'))
170168

171169

172170
@oidc_op_views.route('/registration_api', methods=['GET', 'DELETE'])
173171
def registration_api():
174172
if request.method == "DELETE":
175173
return service_endpoint(
176-
current_app.server.server_get("endpoint", 'registration_delete'))
174+
current_app.server.get_endpoint('registration_delete'))
177175
else:
178176
return service_endpoint(
179-
current_app.server.server_get("endpoint", 'registration_read'))
177+
current_app.server.get_endpoint('registration_read'))
180178

181179

182180
@oidc_op_views.route('/authorization')
183181
def authorization():
184182
return service_endpoint(
185-
current_app.server.server_get("endpoint", 'authorization'))
183+
current_app.server.get_endpoint('authorization'))
186184

187185

188186
@oidc_op_views.route('/token', methods=['GET', 'POST'])
189187
def token():
190188
return service_endpoint(
191-
current_app.server.server_get("endpoint", 'token'))
189+
current_app.server.get_endpoint('token'))
192190

193191
@oidc_op_views.route('/introspection', methods=['POST'])
194192
def introspection_endpoint():
195193
return service_endpoint(
196-
current_app.server.server_get("endpoint", 'introspection'))
194+
current_app.server.get_endpoint('introspection'))
197195

198196
@oidc_op_views.route('/userinfo', methods=['GET', 'POST'])
199197
def userinfo():
200198
return service_endpoint(
201-
current_app.server.server_get("endpoint", 'userinfo'))
199+
current_app.server.get_endpoint('userinfo'))
202200

203201

204202
@oidc_op_views.route('/session', methods=['GET'])
205203
def session_endpoint():
206204
return service_endpoint(
207-
current_app.server.server_get("endpoint", 'session'))
205+
current_app.server.get_endpoint('session'))
208206

209207

210208
IGNORE = ["cookie", "user-agent"]
@@ -298,7 +296,7 @@ def check_session_iframe():
298296
req_args = dict([(k, v) for k, v in request.form.items()])
299297

300298
if req_args:
301-
_context = current_app.server.server_get("endpoint_context")
299+
_context = current_app.server.get_context()
302300
# will contain client_id and origin
303301
if req_args['origin'] != _context.issuer:
304302
return 'error'
@@ -314,15 +312,15 @@ def check_session_iframe():
314312

315313
@oidc_op_views.route('/verify_logout', methods=['GET', 'POST'])
316314
def verify_logout():
317-
part = urlparse(current_app.server.server_get("endpoint_context").issuer)
315+
part = urlparse(current_app.server.get_context().issuer)
318316
page = render_template('logout.html', op=part.hostname,
319317
do_logout='rp_logout', sjwt=request.args['sjwt'])
320318
return page
321319

322320

323321
@oidc_op_views.route('/rp_logout', methods=['GET', 'POST'])
324322
def rp_logout():
325-
_endp = current_app.server.server_get("endpoint", 'session')
323+
_endp = current_app.server.get_endpoint('session')
326324
_info = _endp.unpack_signed_jwt(request.form['sjwt'])
327325
try:
328326
request.form['logout']

example/flask_rp/application.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,14 @@ def init_oidc_rp_handler(app):
2323
_path = ''
2424
_kj.httpc_params = _rp_conf.httpc_params
2525

26-
rph = RPHandler(_rp_conf.base_url, _rp_conf.clients, services=_rp_conf.services,
27-
hash_seed=_rp_conf.hash_seed, keyjar=_kj, jwks_path=_path,
28-
httpc_params=_rp_conf.httpc_params)
26+
rph = RPHandler(base_url=_rp_conf.base_url,
27+
client_configs=_rp_conf.clients,
28+
services=_rp_conf.services,
29+
keyjar=_kj,
30+
hash_seed=_rp_conf.hash_seed,
31+
httpc_params=_rp_conf.httpc_params,
32+
jwks_path=_path,
33+
)
2934

3035
return rph
3136

0 commit comments

Comments
 (0)