Skip to content
This repository was archived by the owner on Jun 1, 2023. It is now read-only.

Commit 6e7c562

Browse files
authored
Merge pull request #39 from IdentityPython/develop
Inclusion of oidcservice in oidcrp.
2 parents e67cb5f + f5d1707 commit 6e7c562

File tree

106 files changed

+10787
-2267
lines changed

Some content is hidden

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

106 files changed

+10787
-2267
lines changed

.github/workflows/python-app.yml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# This workflow will install Python dependencies, run tests and lint with a single version of Python
2+
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
3+
4+
name: oidcrp
5+
6+
on:
7+
push:
8+
branches: [ main, develop ]
9+
pull_request:
10+
branches: [ main, develop ]
11+
12+
jobs:
13+
build:
14+
15+
runs-on: ubuntu-latest
16+
17+
strategy:
18+
fail-fast: false
19+
matrix:
20+
python-version:
21+
- '3.6'
22+
- '3.7'
23+
- '3.8'
24+
- '3.9'
25+
26+
steps:
27+
- uses: actions/checkout@v2
28+
- name: Set up Python ${{ matrix.python-version }}
29+
uses: actions/setup-python@v2
30+
with:
31+
python-version: ${{ matrix.python-version }}
32+
- name: Install rustc and cargo
33+
run: |
34+
sudo apt-get install rustc
35+
sudo apt install cargo
36+
- name: Install dependencies
37+
run: |
38+
python -m pip install --upgrade pip
39+
pip install -U wheel --user
40+
pip install setuptools-rust
41+
python setup.py install
42+
python setup.py test
43+
pip install flake8
44+
pip install pytest
45+
pip install pytest-httpserver
46+
- name: Lint with flake8
47+
run: |
48+
# stop the build if there are Python syntax errors or undefined names
49+
flake8 ./src/oidcrp --count --select=E9,F63,F7,F82 --show-source --statistics
50+
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
51+
flake8 ./src/oidcrp --count --exit-zero --statistics
52+
53+
- name: Unit tests
54+
run: |
55+
py.test tests/

doc/source/rp_handler.rst

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ that some of the functions the service provides needs access to some user
1414
related resources on a resource server. That's when you need OpenID Connect
1515
(OIDC) or Oauth2.
1616

17-
The RPHandler as implemented in :py:class:`oidcrp.RPHandler` is a service within
17+
The RPHandler as implemented in :py:class:`oidcrp.rp_handler.RPHandler` is a
18+
service within
1819
the web service that handles user authentication and access authorization on
1920
behalf of the web service.
2021

@@ -127,7 +128,7 @@ Tier 1 API
127128
The high-level methods you have access to (in the order they are to be
128129
used) are:
129130

130-
:py:meth:`oidcrp.RPHandler.begin`
131+
:py:meth:`oidcrp.rp_handler.RPHandler.begin`
131132
This method will initiate a RP/Client instance if none exists for the
132133
OP/AS in question. It will then run service 1 if needed, services 2 and 3
133134
according to configuration and finally will construct the authorization
@@ -152,7 +153,7 @@ like this::
152153

153154
After the RP has received this response the processing continues with:
154155

155-
:py:meth:`oidcrp.RPHandler.get_session_information`
156+
:py:meth:`oidcrp.rp_handler.RPHandler.get_session_information`
156157
In the authorization response there MUST be a state parameter. The value
157158
of that parameter is the key into a data store that will provide you
158159
with information about the session so far.
@@ -161,7 +162,7 @@ After the RP has received this response the processing continues with:
161162

162163
session_info = rph.state_db_interface.get_state(kwargs['state'])
163164

164-
:py:meth:`oidcrp.RPHandler.finalize`
165+
:py:meth:`oidcrp.rp_handler.RPHandler.finalize`
165166
Will parse the authorization response and depending on the configuration
166167
run the services 5 and 6.
167168

@@ -177,14 +178,14 @@ The tier 1 API is good for getting you started with authenticating a user and
177178
getting user information but if you're look at a long-term engagement you need
178179
a finer grained set of methods. These I call the tier 2 API:
179180

180-
:py:meth:`oidcrp.RPHandler.do_provider_info`
181+
:py:meth:`oidcrp.rp_handler.RPHandler.do_provider_info`
181182
Either get the provider info from configuration or through dynamic
182183
discovery. Will overwrite previously saved provider metadata.
183184

184-
:py:meth:`oidcrp.RPHandler.do_client_registration`
185+
:py:meth:`oidcrp.rp_handler.RPHandler.do_client_registration`
185186
Do dynamic client registration is configured to do so and the OP supports it.
186187

187-
:py:meth:`oidcrp.RPHandler.init_authorization`
188+
:py:meth:`oidcrp.rp_handler.RPHandler.init_authorization`
188189
Initialize an authorization/authentication event. If the user has a
189190
previous session stored this will not overwrite that but will create a new
190191
one.
@@ -197,7 +198,7 @@ a finer grained set of methods. These I call the tier 2 API:
197198
The state_key you see mentioned here and below is the value of the state
198199
parameter in the authorization request.
199200

200-
:py:meth:`oidcrp.RPHandler.get_access_token`
201+
:py:meth:`oidcrp.rp_handler.RPHandler.get_access_token`
201202
Will use an access code received as the response to an
202203
authentication/authorization to get an access token from the OP/AS.
203204
Access codes can only be used once.
@@ -206,7 +207,7 @@ parameter in the authorization request.
206207

207208
res = self.rph.get_access_token(state_key)
208209

209-
:py:meth:`oidcrp.RPHandler.refresh_access_token`
210+
:py:meth:`oidcrp.rp_handler.RPHandler.refresh_access_token`
210211
If the client has received a refresh token this method can be used to get
211212
a new access token.
212213

@@ -218,15 +219,15 @@ You may change the set of scopes that are bound to the new access token but
218219
that change can only be a downgrade from what was specified in the
219220
authorization request and accepted by the user.
220221

221-
:py:meth:`oidcrp.RPHandler.get_user_info`
222+
:py:meth:`oidcrp.rp_handler.RPHandler.get_user_info`
222223
If the client is allowed to do so, it can refresh the user info by
223224
requesting user information from the userinfo endpoint.
224225

225226
Usage example::
226227

227228
resp = self.rph.get_user_info(state_key)
228229

229-
:py:meth:`oidcrp.RPHandler.has_active_authentication`
230+
:py:meth:`oidcrp.rp_handler.RPHandler.has_active_authentication`
230231
After a while when the user returns after having been away for a while
231232
you may want to know if you should let her reauthenticate or not.
232233
This method will tell you if the last done authentication is still
@@ -238,7 +239,7 @@ authorization request and accepted by the user.
238239

239240
response will be True or False depending in the state of the authentication.
240241

241-
:py:meth:`oidcrp.RPHandler.get_valid_access_token`
242+
:py:meth:`oidcrp.rp_handler.RPHandler.get_valid_access_token`
242243
When you are issued a access token it normally comes with a life time.
243244
After that time you are expected to use the refresh token to get a new
244245
access token. There are 2 ways of finding out if the access token you have is
@@ -289,7 +290,7 @@ these 2 together then defines the base_url. which is normally defined as::
289290
logging
290291
How the process should log
291292

292-
http_params
293+
httpc_params
293294
Defines how the process performs HTTP requests to other entities.
294295
Parameters here are typically **verify** which controls whether the http
295296
client will verify the server TLS certificate or not.
@@ -302,9 +303,6 @@ rp_keys
302303
Definition of the private keys that all RPs are going to use in the OIDC
303304
protocol exchange.
304305

305-
jwks_uri
306-
Where the OP/AS can find the RPs public keys
307-
308306
There might be other parameters that you need dependent on which web framework
309307
you chose to use.
310308

@@ -339,8 +337,22 @@ redirect_uris
339337
the use back to this URL after the authorization/authentication has
340338
completed. These URLs should be OP/AS specific.
341339

342-
behavior
343-
Information about how the RP should behave towards the OP/AS
340+
behaviour
341+
Information about how the RP should behave towards the OP/AS. This is
342+
a set of attributes with values. The attributes taken from the
343+
`client metadata`_ specification. *behaviour* is used when the client
344+
has been registered statically and it is know what the client wants to
345+
use and the OP supports.
346+
347+
Usage example::
348+
349+
"behaviour": {
350+
"response_types": ["code"],
351+
"scope": ["openid", "profile", "email"],
352+
"token_endpoint_auth_method": ["client_secret_basic",
353+
'client_secret_post']
354+
}
355+
344356

345357
rp_keys
346358
If the OP doesn't support dynamic provider discovery it may still want to
@@ -356,7 +368,11 @@ rp_keys
356368
If the provider info discovery is done dynamically you need this
357369

358370
client_preferences
359-
How the RP should prefer to behave against the OP/AS
371+
How the RP should prefer to behave against the OP/AS. The content are the
372+
same as for *behaviour*. The difference is that this is specified if the
373+
RP is expected to do dynamic client registration which means that at the
374+
point of writing the configuration it is only known what the RP can and
375+
wants to do but unknown what the OP supports.
360376

361377
issuer
362378
The Issuer ID of the OP.
@@ -368,6 +384,8 @@ allow
368384
in the provider info is not the same as the URL you used to fetch the
369385
information.
370386

387+
.. _client metadata: https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata
388+
371389
-------------------------
372390
RP configuration - Google
373391
-------------------------
@@ -380,7 +398,7 @@ with dummy values::
380398
"client_id": "xxxxxxxxx.apps.googleusercontent.com",
381399
"client_secret": "2222222222",
382400
"redirect_uris": ["{}/authz_cb/google".format(BASEURL)],
383-
"client_prefs": {
401+
"behaviour": {
384402
"response_types": ["code"],
385403
"scope": ["openid", "profile", "email"],
386404
"token_endpoint_auth_method": ["client_secret_basic",
@@ -415,7 +433,7 @@ right now supports 2 variants both listed here. The RP will by default pick
415433
the first if a list of possible values. Which in this case means the RP will
416434
authenticate using the *client_secret_basic* if allowed by Google::
417435

418-
"client_prefs": {
436+
"behaviour": {
419437
"response_types": ["code"],
420438
"scope": ["openid", "profile", "email"],
421439
"token_endpoint_auth_method": ["client_secret_basic",
@@ -447,7 +465,7 @@ Configuration that allows you to use a Microsoft OP as identity provider::
447465
'client_id': '242424242424',
448466
'client_secret': 'ipipipippipipippi',
449467
"redirect_uris": ["{}/authz_cb/microsoft".format(BASEURL)],
450-
"client_prefs": {
468+
"behaviour": {
451469
"response_types": ["id_token"],
452470
"scope": ["openid"],
453471
"token_endpoint_auth_method": ['client_secret_post'],
@@ -465,7 +483,7 @@ Configuration that allows you to use a Microsoft OP as identity provider::
465483
One piece at the time. Microsoft has something called a tenant. Either you
466484
specify your RP to only one tenant in which case the issuer returned
467485
as *iss* in the id_token will be the same as the *issuer*. If our RP
468-
is expected to work in a multi-tenant environment then the *iss* will never
486+
is expected to work in a multi-tenant environment then the *iss* will **never**
469487
match issuer. Let's assume our RP works in a single-tenant context::
470488

471489
'issuer': 'https://login.microsoftonline.com/<tenant_id>/v2.0',
@@ -486,7 +504,7 @@ response not in the fragment of the redirect URL which is the default but
486504
instead using the response_mode *form_post*. *client_secret_post* is a
487505
client authentication that Microsoft supports at the token enpoint::
488506

489-
"client_prefs": {
507+
"behaviour": {
490508
"response_types": ["id_token"],
491509
"scope": ["openid"],
492510
"token_endpoint_auth_method": ['client_secret_post'],
@@ -574,7 +592,7 @@ can be used to access user info at the userinfo endpoint.
574592
GitHub deviates from the standard in a number of way. First the Oauth2
575593
standard doesn't mention anything like an userinfo endpoint, that is OIDC.
576594
So GitHub has implemented something that is in between OAuth2 and OIDC.
577-
What's more disturbing is that the accesstoken response by default is not
595+
What's more disturbing is that the access token response by default is not
578596
encoded as a JSON document which the standard say but instead it's
579597
urlencoded. Lucky for us, we can deal with both these things by configuration
580598
rather then writing code.::
@@ -584,3 +602,4 @@ rather then writing code.::
584602
'AccessToken': {'response_body_type': 'urlencoded'},
585603
'UserInfo': {'default_authn_method': ''}
586604
}
605+
Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,17 @@
55
from cryptojwt.key_jar import init_key_jar
66
from flask.app import Flask
77

8-
from oidcrp import RPHandler
9-
from oidcrp.configure import Configuration
8+
from oidcrp.rp_handler import RPHandler
109

1110
dir_path = os.path.dirname(os.path.realpath(__file__))
1211

1312

1413
def init_oidc_rp_handler(app):
1514
_rp_conf = app.rp_config
1615

17-
if _rp_conf.rp_keys:
18-
_kj = init_key_jar(**_rp_conf.rp_keys)
19-
_path = _rp_conf.rp_keys['public_path']
16+
if _rp_conf.keys:
17+
_kj = init_key_jar(**_rp_conf.keys)
18+
_path = _rp_conf.keys['public_path']
2019
# removes ./ and / from the begin of the string
2120
_path = re.sub('^(.)/', '', _path)
2221
else:
@@ -31,11 +30,11 @@ def init_oidc_rp_handler(app):
3130
return rph
3231

3332

34-
def oidc_provider_init_app(config_file, name=None, **kwargs):
33+
def oidc_provider_init_app(config, name=None, **kwargs):
3534
name = name or __name__
3635
app = Flask(name, static_url_path='', **kwargs)
3736

38-
app.rp_config = Configuration.create_from_config_file(config_file)
37+
app.rp_config = config
3938

4039
# Session key for the application session
4140
app.config['SECRET_KEY'] = os.urandom(12).hex()

0 commit comments

Comments
 (0)