-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Rebecka Gulliksson
committed
Feb 11, 2016
0 parents
commit e1e2d10
Showing
6 changed files
with
187 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[bumpversion] | ||
current_version = 0.0.1 | ||
commit = True | ||
tag = True | ||
|
||
[bumpversion:file:setup.py] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
*.pyc | ||
*.egg-info | ||
build/ | ||
dist/ | ||
.idea/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# Flask-pyoidc | ||
|
||
![PyPI](https://img.shields.io/pypi/v/flask-pyoidc.svg) | ||
|
||
This repository contains an example of how to use the [pyoidc](https://github.com/rohe/pyoidc) | ||
library to provide simple OpenID Connect authentication (using the ["Code Flow"](http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth). | ||
|
||
## Usage | ||
|
||
The extension support both static and dynamic provider configuration discovery as well as static | ||
and dynamic client registration. The different modes of provider configuration can be combined in | ||
any way with the different client registration modes. | ||
|
||
* Static provider configuration: `OIDCAuthentication(provider_configuration_info=provider_config)`, | ||
where `provider_config` is a dictionary containing the [provider metadata](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata). | ||
* Dynamic provider configuration: `OIDCAuthentication(issuer=issuer_url)`, where `issuer_url` | ||
is the issuer URL of the provider. | ||
* Static client registration: `OIDCAuthentication(client_registration_info=client_info)`, where | ||
`client_info` is all the [registered metadata](https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationResponse) | ||
about the client. The `redirect_uris` registered with the provider MUST include | ||
`<flask_url>/redirect_uri`, where `<flask_url>` is the URL for the Flask application. | ||
|
||
|
||
|
||
The application using this extension MUST set the following [builtin configuration values of Flask](http://flask.pocoo.org/docs/0.10/config/#builtin-configuration-values): | ||
|
||
* `SERVER_NAME` (MUST be the same as `<flask_url>` if using static client registration | ||
* `SECRET_KEY` (this extension relies on Flask session, which requires `SECRET_KEY`) | ||
|
||
Have a look at the example Flask app in [app.py](example/app.py) for an idea of how to use it. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import flask | ||
from flask import Flask, jsonify | ||
|
||
from flask_pyoidc import OIDCAuthentication | ||
|
||
PORT = 5000 | ||
app = Flask(__name__) | ||
|
||
app.config.update({'SERVER_NAME': 'localhost:{}'.format(PORT), | ||
'SECRET_KEY': 'dev_key'}) | ||
auth = OIDCAuthentication(app, issuer="https://localhost:50009") | ||
|
||
|
||
@app.route('/') | ||
@auth.oidc_auth | ||
def index(): | ||
return jsonify(id_token=flask.g.id_token.to_dict(), access_token=flask.g.access_token, | ||
userinfo=flask.g.userinfo.to_dict()) | ||
|
||
|
||
if __name__ == '__main__': | ||
app.run(port=PORT) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
from setuptools import setup, find_packages | ||
|
||
setup( | ||
name='Flask-pyoidc', | ||
version='0.0.1', | ||
packages=find_packages('src'), | ||
package_dir={'': 'src'}, | ||
url='https://github.com/its-dirg/flask-pyoidc', | ||
license='Apache 2.0', | ||
author='Rebecka Gulliksson', | ||
author_email='[email protected]', | ||
description='Flask extension for OpenID Connect authentication.', | ||
install_requires=[ | ||
'oic', | ||
'Flask' | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import functools | ||
|
||
import flask | ||
from flask.helpers import url_for | ||
from oic.oauth2 import rndstr | ||
from oic.oic import Client | ||
from oic.oic.message import ProviderConfigurationResponse, RegistrationRequest, \ | ||
AuthorizationResponse | ||
from oic.utils.authn.client import CLIENT_AUTHN_METHOD | ||
from werkzeug.utils import redirect | ||
|
||
|
||
class OIDCAuthentication(object): | ||
def __init__(self, flask_app, client_registration_info=None, issuer=None, | ||
provider_configuration_info=None): | ||
self.app = flask_app | ||
|
||
self.client = Client(client_authn_method=CLIENT_AUTHN_METHOD) | ||
if not issuer and not provider_configuration_info: | ||
raise ValueError( | ||
'Either \'issuer\' (for dynamic discovery) or \'provider_configuration_info\' (for static configuration must be specified.') | ||
if issuer and not provider_configuration_info: | ||
self.client.provider_config(issuer) | ||
else: | ||
self.client.handle_provider_config( | ||
ProviderConfigurationResponse(**provider_configuration_info), | ||
provider_configuration_info['issuer']) | ||
|
||
self.client_registration_info = client_registration_info or {} | ||
if client_registration_info and 'client_id' in client_registration_info: | ||
# static client info provided | ||
self.client.store_registration_info(RegistrationRequest(**client_registration_info)) | ||
else: | ||
# do dynamic registration | ||
self.app.add_url_rule('/redirect_uri', 'redirect_uri', | ||
self._handle_authentication_response) | ||
with self.app.app_context(): | ||
self.client_registration_info['redirect_uris'] = url_for('redirect_uri') | ||
self.client.register(self.client.provider_info['registration_endpoint'], | ||
**self.client_registration_info) | ||
|
||
self.callback = None | ||
|
||
def _authenticate(self): | ||
if flask.g.get('userinfo', None): | ||
return self.callback() | ||
|
||
flask.session['state'] = rndstr() | ||
flask.session['nonce'] = rndstr() | ||
args = { | ||
'client_id': self.client.client_id, | ||
'response_type': 'code', | ||
'scope': ['openid'], | ||
'redirect_uri': self.client.registration_response['redirect_uris'][0], | ||
'state': flask.session['state'], | ||
'nonce': flask.session['nonce'], | ||
} | ||
|
||
auth_req = self.client.construct_AuthorizationRequest(request_args=args) | ||
login_url = auth_req.request(self.client.authorization_endpoint) | ||
return redirect(login_url) | ||
|
||
def _handle_authentication_response(self): | ||
# parse authentication response | ||
query_string = flask.request.query_string.decode('utf-8') | ||
authn_resp = self.client.parse_response(AuthorizationResponse, info=query_string, | ||
sformat='urlencoded') | ||
|
||
if authn_resp['state'] != flask.session['state']: | ||
raise ValueError('The \'state\' parameter does not match.') | ||
|
||
# do token request | ||
args = { | ||
'code': authn_resp['code'], | ||
'redirect_uri': self.client.registration_response['redirect_uris'][0], | ||
'client_id': self.client.client_id, | ||
'client_secret': self.client.client_secret | ||
} | ||
token_resp = self.client.do_access_token_request(scope='openid', state=authn_resp['state'], | ||
request_args=args, | ||
authn_method='client_secret_basic') | ||
id_token = token_resp['id_token'] | ||
if id_token['nonce'] != flask.session['nonce']: | ||
raise ValueError('The \'nonce\' parameter does not match.') | ||
access_token = token_resp['access_token'] | ||
|
||
# do userinfo request | ||
userinfo = self.client.do_user_info_request(state=authn_resp['state']) | ||
if userinfo['sub'] != id_token['sub']: | ||
raise ValueError('The \'sub\' of userinfo does not match \'sub\' of ID Token.') | ||
|
||
# store the current user | ||
flask.g.id_token = id_token | ||
flask.g.access_token = access_token | ||
flask.g.userinfo = userinfo | ||
|
||
return self.callback() | ||
|
||
def oidc_auth(self, f): | ||
self.callback = f | ||
|
||
@functools.wraps(f) | ||
def wrapper(): | ||
return self._authenticate() | ||
|
||
return wrapper |