TOC

Welcome to MedMij OAuth’s documentation

The medmij_oauth package assists in implementing an oauth server/client application conform the medmij oauth flow (described here). The module consists of 3 main submodules i.e. medmij_oauth.server, medmij_oauth.client and medmij_oauth.exceptions. The client and server submodules are build for use with an async library like aiohttp.

Beside the package there are two example implementations available on the github repo, an oauth server and client implementation built using these modules (Only a reference, not for production use!).

Server

API Reference: medmij_oauth.server

The medmij_oauth.server modules goal is to assist in implementing an oauth server, its main component is the Server class. To make use of the Server class you need to implement the following:

  • subclass of DataStore (class)

  • OAuthSession (class)

  • zg_resource_available (coroutine)

  • get_ocl (coroutine)

DataStore ABC (server)

Your implementation of the DataStore class handles instantiation, persisting and lookups of OAuthSessions. The methods that you need to implement can be found on the DataStore ABC.

Example implementation:

from medmij_oauth.server import (
    DataStore
)

import my_oauth_session as OAuthSession

SESSIONS = {}

class InMemoryDataStore(DataStore):
    async def create_oauth_session(self, response_type, client_id, redirect_uri, scope, state, **kwargs):
        oauth_session = OAuthSession(
            response_type=response_type,
            client_id=client_id,
            redirect_uri=redirect_uri,
            scope=scope,
            state=state
        )

        SESSIONS[oauth_session.id] = oauth_session

        return oauth_session

    async def get_oauth_session_by_id(self, oauth_session_id, **kwargs):
        return SESSIONS.get(oauth_session_id, None)

    async def get_oauth_session_by_authorization_code(self, authorization_code, **kwargs):
        try:
            oauth_session = [
                oauth_session for
                oauth_session in SESSIONS.values()
                if oauth_session.authorization_code == authorization_code
            ][0]
        except IndexError:
            return None

        return oauth_session

    async def save_oauth_session(self, oauth_session=None, **kwargs):
        return oauth_session

    def __repr__(self):
        return 'InMemoryDataStore()'

Most methods on the Server class use the functions of your implementation of the DataStore to handle interaction with the OAuthSessions. Any extra keyword arguments given to those functions are passed on to the methods on the DataStore implementation e.g. a DB object of some sort

Example:

# In Server Class
async def create_oauth_session(self, request_parameters, **kwargs):
    ...

    oauth_session = await self.data_store.create_oauth_session(
        ...
        **kwargs
    )

    return oauth_session

OAuthSession (server)

This class represents the state of the current oauth session. The Server class will handle instantiation and interaction with OAuthSessions through your implementation of the DataStore ABC.

Example implementation:

class OAuthSession():
    def __init__(self, response_type, client_id, redirect_uri, scope, state):
        self.id = str(uuid.uuid4())
        self.response_type = response_type
        self.client_id = client_id
        self.scope = scope
        self.state = state
        self.redirect_uri = redirect_uri
        self.created_at = datetime.datetime.now()
        self.authorization_code = None
        self.authorization_code_expiration = -1
        self.authorization_granted = False
        self.access_token = None
        self.access_token_expiration = -1
        self.zorggebruiker_bsn = ''

More info

zg_resource_available

An coroutine with signature (client_data:dict, **kwargs:various)->bool that checks if resources are available for the current zorggebruiker. Is called when Server.zg_resource_available is invoked, with a dict containing at least the BSN of the zorggebruiker.

Warning

BSN is added to the OAuthSession in response to the DigiD interaction (FLOW #7), this is not (yet) included in the Server class. If you are implementing a server make sure to update the OAuthSession after retreiving the BSN from DigiD.

get_ocl

An coroutine that returns an OCL.

Example implementation:

async def get_ocl():
    # Probably some caching and retreiving an up to date list but as an example load it from disk.
    async with aiofiles.open('path/to/ocl.xml'), mode='r') as file:
        contents = await f.read()
        xml = bytes(file.read(), 'utf-8')

    return medmij_lists.OAuthclientList(xmldata=xml)

Server usage example

from aiohttp import web

import get_db_somehow

import my_get_ocl
import my_datastore_implementation
import my_zg_resouce_available

server = Server(
    data_store=my_datastore_implementation,
    zg_resource_available=my_zg_resouce_available,
    get_ocl=my_get_ocl
)

app['server'] = server
app['db] = get_db_somehow()

async def get_start_oauth_session(request):
    query_dict = request.query
    server = request.app['server']

    oauth_session = await server.create_oauth_session(query_dict, db=request.app['db'])

    # If there is no resource available the function raises an OAuthException that gets handled by the middleware
    await server.zg_resource_available(oauth_session=oauth_session, client_data={"name": "test patient"})

    ocl = await server.get_ocl()
    pgo = ocl.get(oauth_session.client_id)

    csrf_token = await csrf.generate_csrf_token(request)

    return render_template('ask_auth.html', request, {
        'pgo': pgo,
        'oauth_session_id': oauth_session.id,
        'csrf_token': csrf_token
    })

app.router.add_get('/oauth/authorize', get_start_session)

app = web.Application()
web.run_app(app, port=args.port)

For a full example implementation checkout the server_implementation on github.

Client

API Reference: medmij_oauth.client

The medmij_oauth.client modules goal is to assist in implementing an oauth client, its main component is the Client class. To make use of the Client class you need to implement/supply the following:

  • subclass of DataStore (class)

  • OAuthSession (class)

  • get_zal (coroutine)

  • get_gnl (coroutine)

  • client_info (dict)

  • make_request (coroutine)

DataStore ABC (client)

Your implementation of the DataStore class handles instantiation, persisting and lookups of OAuthSessions. The methods that you need to implement can be found on the DataStore ABC.

Example implementation:

import secrets
import uuid

from ..data_store import (
    DataStore
)

import my_oauth_session as OAuthSession

SESSIONS = {}

class InMemoryDataStore(DataStore):
    async def create_oauth_session(self, za_name, gegevensdienst_id, **kwargs):
        oauth_session = OAuthSession(
            state=secrets.token_hex(16),
            za_name=za_name,
            gegevensdienst_id=gegevensdienst_id,
            scope=gegevensdienst_id
        )

        SESSIONS[oauth_session.id] = oauth_session

        return oauth_session

    async def get_oauth_session_by_id(self, oauth_session_id, **kwargs):
        return SESSIONS.get(oauth_session_id, None)

    async def get_oauth_session_by_state(self, state, **kwargs):
        try:
            oauth_session = [
                oauth_session for
                oauth_session in SESSIONS.values()
                if oauth_session.state == state
            ][0]
        except IndexError:
            return None

        return oauth_session

    async def save_oauth_session(self, oauth_session=None, **kwargs):
        return oauth_session

    def __repr__(self):
        return 'InMemoryDataStore()'

Most methods on the Client class use the functions of your implementation of the DataStore to handle interaction with the OAuthSessions. Any extra keyword arguments given to those functions are passed on to the methods on the DataStore implementation e.g. a DB object of some sort.

Example:

#In Client class
async def create_oauth_session(self, za_name, gegevensdienst_id, **kwargs):
    return await self.data_store.create_oauth_session(
        ...
        **kwargs
    )

OAuthSession (client)

This class represents the state of the current oauth session. The Server class will handle instantiation and interaction with OAuthSessions through your implementation of the DataStore ABC.

Example implementation:

class OAuthSession():
    def __init__(self, state, za_name, gegevensdienst_id, scope):
        self.id = str(uuid.uuid4())
        self.state = state
        self.scope = scope
        self.za_name = za_name
        self.gegevensdienst_id = gegevensdienst_id
        self.authorization_code = None
        self.authorized = False
        self.access_token = None

More info

get_zal

An coroutine that returns a ZAL.

Example implementation:

async def get_zal():
    # Probably some caching and retreiving an up to date list but as an example load it from disk.
    async with aiofiles.open('path/to/zal.xml'), mode='r') as file:
        contents = await f.read()
        xml = bytes(file.read(), 'utf-8')

    return medmij_lists.ZAL(xmldata=xml)

get_gnl

An coroutine that returns a GNL.

Example implementation:

async def get_test_gnl():
    # Probably some caching and retreiving an up to date list but as an example load it from disk.
    with open(path.join(path.dirname(__file__), 'resources/MedMij_Gegevensdienstnamenlijst_example.xml'), 'r') as file:
        xml = bytes(file.read(), 'utf-8')

    return medmij_lists.GNL(xmldata=xml)

client_info

Dict containing info about the client application e.i. client_id and redirect_url for authorization request responses.

Example:

client_info = {
    "client_id": "oauthclient.local",
    "redirect_url": "https://oauthclient.local/oauth/cb"
}

make_request

Coroutine that makes a POST request. Should have the signature (url:string, body:dict)->dict. Used by the client to make a exchange_authorization_code request to the oauth server.

Example:

# Example uses aiohttp client (https://docs.aiohttp.org/en/stable/client.html) to make the actual request.
# For a complete example of how to implement this check out the example client implementation.

async def make_request(self, url='', body=None):
    optional_data = {}

    if body is not None:
        if not isinstance(body, str):
            body = json.dumps(body)

        optional_data['data'] = body.encode('utf-8')

    async with self.session.request("POST", url, **optional_data) as resp:
        json_resp = await resp.json()

    return json_resp

Client usage example

from aiohttp import web

import get_db_somehow

import my_datastore_implementation
import my_get_zal
import my_get_gnl
import my_make_request

client_info = {
    "client_id": "oauthclient.local",
    "redirect_url": "https://oauthclient.local/oauth/cb"
}

client = Client(
    data_store=my_datastore_implemtation,
    get_zal=my_get_zal,
    get_gnl=my_get_gnl,
    make_request=my_make_request,
    client_info=client_info
)

app['client'] = client
app['db] = get_db_somehow()

async def get_start_session(request):
    client = request.app['client']
    client = request.app['db']

    session = await create_oauth_session(request_params, db=db)

app.router.add_get('/oauth/start', get_start_session)

app = web.Application()
web.run_app(app, port=args.port)

For a full example implementation checkout the client_implementation on github.

Exceptions

API Reference: medmij_oauth.exceptions

The OAuthException class is used to represent an error as described in rfc6749. The exception contains the error, error_description, if it is allowed to redirect, redirect_url if allowed and correct HTTP status_code.

The different possible errors are contained in the ERRORS enum. Further optional arguments that the OAuthException’s constructor takes are, error_descripion, redirect and redirect_url.

Example Usage:

raise OAuthException(ERRORS.INVALID_REQUEST, error_description='Invalid redirect url', redirect=False)
raise OAuthException(ERRORS.UNAUTHORIZED_CLIENT, error_description='No such resource', redirect=True, base_redirect_url='https://oauthclient.com')

Example of OAuth exception handling in middleware

...

async def oauth_error_middleware(request, handler):
    try:
        response = await handler(request)
        return response
    except OAuthException as ex:
        # If redirect is set on the exception, it is safe to redirect zorggebruiker to supplied redirect url
        if ex.redirect:
            return web.HTTPFound(ex.get_redirect_url())

        # Else just render to screen with the correct HTTP statuscode
        return web.Response(
            text=ex.get_json(),
            status=ex.status_code,
            content_type='application/json'
        )

The MedMij OAuth flow

In the API references you find links to this flow, that means that those functions assist in implementing this step of the oauth flow. (e.g. Server.create_oauth_session)

  1. De PGO Server start de flow door in de PGO Presenter van de Zorggebruiker de mogelijkheid te presenteren om een bepaalde Gegevensdienst bij een zekere Zorgaanbieder te verzamelen. Het gaat altijd om precies één Gegevensdienst (één scope, in OAuth-termen). Uit de Zorgaanbiederslijst weet de PGO Server welke Gegevensdiensten voor een Zorgaanbieder beschikbaar zijn. Desgewenst worden de Gegevensdienstnamen uit de Gegevensdienstnamenlijst gebruikt.

  1. De Zorggebruiker maakt expliciet zijn selectie en laat de OAuth User Agent een verzamel-verzoek sturen naar de Authorization Server. Het adres van het authorization endpoint komt uit de ZAL. De redirect URI geeft aan waarnaartoe de Authorization Server de OAuth User Agent verderop moet redirecten (met de authorization code).

  1. Daarop begint de Authorization Server de OAuth-flow (in zijn rol als OAuth Authorization Server) door een sessie te creëren.

  1. Dan start de Authorization Server (nu in de rol van SAML Service Provider) de SAML-flow door de browser naar DigiD te redirecten, onder meegeven van een redirect URI, die aangeeft waarnaartoe DigiD straks de OAuth User Agent moet terugsturen, na het inloggen van de Zorggebruiker.

  1. DigiD vraagt van de Zorggebruiker via zijn PGO Presenter om inloggegevens.

  1. Wanneer deze juist zijn, redirect DigiD de OAuth User Agent terug naar de Authorization Server, onder meegeven van een ophaalbewijs: het SAML-artefact.

  1. Met dit ophaalbewijs haalt de Authorization Server rechtstreeks bij DigiD het BSN op.

  1. De Authorization Server controleert alvast of de Zorgaanbieder voor de betreffende Gegevensdienst überhaupt gezondheidsinformatie van die Persoon beschikbaar heeft. Daarvan maakt deel uit dat de Persoon daarvoor minstens 16 jaar oud moet zijn.

  1. Zo ja, dan presenteert de Authorization Server via de PGO Presenter aan Zorggebruiker de vraag of laatstgenoemde hem toestaat de gevraagde persoonlijke gezondheidsinformatie aan de PGO Server (als OAuth Client) te sturen. Onder het flow-diagram staat gespecificeerd welke informatie, waarvandaan, de OAuth Authorization Server verwerkt in de aan Zorggebruiker voor te leggen autorisatievraag.

  1. Bij akkoord logt de Authorization Server dit als toestemming, genereert een authorization code en stuurt dit als ophaalbewijs, door middel van een browser redirect met de in stap 1 ontvangen redirect URI, naar de PGO Server. De Authorization Server stuurt daarbij de local state-informatie mee die hij in de eerste stap van de PGO Server heeft gekregen. Laatstgenoemde herkent daaraan het verzoek waarmee hij de authorization code moet associëren.

  1. De PGO Server vat niet alleen deze authorization code op als ophaalbewijs, maar leidt er ook uit af dat de toestemming is gegeven en logt het verkrijgen van het ophaalbewijs.

  1. Met dit ophaalbewijs wendt de PGO Server zich weer tot de Authorization Server, maar nu zonder tussenkomst van de OAuth User Agent, voor een access token.

  1. Daarop genereert de Authorization Server een access token en stuurt deze naar de PGO Server.

  1. Nu is de PGO Server gereed om het verzoek om de gezondheidsinformatie naar de Resource Server te sturen. Het adres van het resource endpoint haalt hij uit de ZAL. Hij plaatst het access token in het bericht en zorgt ervoor dat in het bericht geen BSN is opgenomen.

  1. De Resource Server controleert of het ontvangen token recht geeft op de gevraagde resources, haalt deze (al dan niet) bij achterliggende bronnen op en verstuurt ze in een FHIR-response naar de PGO Server.

  1. Deze bewaart de ontvangen gezondheidsinformatie in het persoonlijke dossier. Mocht de Gegevensdienst waartoe de Zorggebruiker heeft geautoriseerd uit meerdere Transacties bestaan, bevraagt de PGO Server de Resource Server daarna mogelijk opnieuw voor de nog resterende Transacties , eventueel na nieuwe gebruikersinteractie. Zolang het access token geldig is, kan dat.

Tests

$ pytest -v

Requirements

Modules

  • Python >=3.7

Example implementations

  • aiohttp==3.3.2

  • aiohttp-jinja2==1.0.0

  • aiohttp-session==2.5.1

  • cryptography==2.3

  • SQLAlchemy==1.2.10

Tests

  • pytest==3.7.1

  • pytest-asyncio==0.9.0

License

This project is licensed under the AGPL-3.0 License - see the LICENSE file for details

medmij_oauth.server module

Server

class medmij_oauth.server.Server(data_store=None, zg_resource_available=None, get_ocl=None)[source]

Class to assist in the OAuth serverside flow

Parameters
  • data_store (DataStore) – Must be subclass of DataStore, handles data interaction with OAuthSessions see DataStore for more info.

  • zg_resource_available (function) – Function that is called by Server.zg_resource_available to determine if resources are available for zorggebruiker.

  • get_ocl (function) – Function that returns a OCL

coroutine create_oauth_session(request_parameters, **kwargs)[source]

Create and return a new OAuthSession. (FLOW #3)

Parameters
  • request_parameters (dict) – Dictionary containing the request parameters from the start verzamelen.

  • **kwargs (various) – Keyword arguments get passed on to the data_store.create_oauth_session function, e.g. db object

Returns

The created OAuthSession

Return type

OAuthSession

Raises

OAuthException – If supplied request_parameters are not valid

coroutine exchange_authorization_code(request_parameters, **kwargs)[source]

Handle the oauth client’s request to exchange the authorization code for an access token. (FLOW #13)

Parameters
  • request_parameters (str) – Params send with the request.

  • **kwargs (various) – Keyword arguments get passed on to the various DataStore functions, e.g. db object

Returns

Dict containing the parameters for a valid response, including the access_token, token_type, expires_in and scope

Return type

dict

Raises

OAuthException – If request parameters are invalid

coroutine get_ocl()[source]

Return the OCL returned by the get_ocl function supplied in instantiation of Server object

coroutine handle_auth_grant(oauth_session_id=None, authorized=False, **kwargs)[source]

Handle the zorggebruikers response to the authorization question. (FLOW #10)

Parameters
  • oauth_session_id (str) – ID for the OAuthSession of current zorggebruiker.

  • authorized (bool) – Indicates if zorggebruiker response was negative (False) or positive (True)

  • **kwargs (various) – Keyword arguments get passed on to self.data_store.get_oauth_session_by_id and self.data_store.save_oauth_session

Returns

Tuple containing the updated OAuthSession (with authorization_code and authorization_code_expiration) and the redirect_url

Return type

tuple (OAuthSession, str)

Raises

OAuthException – If zorggebruiker response was negative

coroutine zg_resource_available(oauth_session=None, oauth_session_id=None, client_data={}, **kwargs)[source]

Determine if this service has resources available for this zorggebruikers by calling the supplied zg_resource_available function on instatiation of the Server. (FLOW #8)

This function requires a least an oauth_session or an oauthsession id. BSN is added to the client_data that is passed to the self._zg_resource_available function.

Parameters
  • oauth_session (OAuthSession) – OAuthSession for the current zorggebruiker (optional).

  • oauth_session_id (string) – ID for the OAuthSession of current zorggebruiker (optional).

  • client_data (dict) – Optional additional zorggebruikerinfo that gets passed on to the self._zg_resource_available function.

  • **kwargs (various) – Keyword arguments get passed to the supplied self._zg_resource_available function

Returns

returns True if resouces are available for this zorggebruiker

Return type

bool

Raises

OAuthException – If there is no resource available for this zorggebruiker

Datastore

class medmij_oauth.server.DataStore[source]

Bases: abc.ABC

Abstract Class that handles interaction instantiation, persisting and lookups of OAuthSessions.

coroutine create_oauth_session(response_type, client_id, redirect_uri, scope, state, **kwargs)[source]

Create a new oauth_session, persist the oauth_session and return it.

coroutine get_oauth_session_by_authorization_code(authorization_code, **kwargs)[source]

Get a oauth_session based on its authorization_code and return it, else return None

coroutine get_oauth_session_by_id(oauth_session_id, **kwargs)[source]

Get a oauth_session based on its id and return it, else return None

coroutine save_oauth_session(oauth_session, **kwargs)[source]

Persist the current state of the oauth_session and return it

OAuthSession

Class that should be implemented by implementor of the OAuth Server. This class is should be instantiated by your implementation of the DataStore base class and represents the current state of your OAuth Session.

The OAuthSession should at least have the following attributes:

  • id (uuid)

  • response_type (string)

  • client_id (string)

  • scope (string)

  • state (string)

  • redirect_uri (string)

  • authorization_code (string)

  • authorization_code_expiration (datetime.datetime)

  • authorization_granted (boolean)

  • access_token (string)

  • access_token_expiration (datetime.datetime)

  • zorggebruiker_bsn (string)

Example implementation:

class OAuthSession():
    def __init__(self, response_type, client_id, redirect_uri, scope, state):
        self.id = str(uuid.uuid4())
        self.response_type = response_type
        self.client_id = client_id
        self.scope = scope
        self.state = state
        self.redirect_uri = redirect_uri
        self.created_at = datetime.datetime.now()
        self.authorization_code = None
        self.authorization_code_expiration = -1
        self.authorization_granted = False
        self.access_token = None
        self.access_token_expiration = -1
        self.zorggebruiker_bsn = ''

medmij_oauth.client module

Client

class medmij_oauth.client.Client(data_store=None, get_zal=None, get_gnl=None, client_info=None, make_request=None)[source]

Class to assist in the OAuth clientside flow

Parameters
  • data_store (DataStore) – Must be subclass of DataStore, handles data interaction with OAuthSessions see DataStore for more info.

  • get_zal (coroutine) – Function that returns a ZAL

  • get_gnl (coroutine) – Function that returns a GegevensdienstNamenlijst

  • client_info (dict) – Dict containing info about the client application (client_id and redirect_url for authorization request responses)

  • make_request (coroutine) – coroutine that makes a post request. Should have the signature (url:string, body:dict)->dict. Used to make a authorization exchange request to the oauth server.

coroutine create_auth_request_url(oauth_session)[source]

Build and return authorization request url (FLOW #2)

Parameters

oauth_session (OAuthSession) – OAuthSession for current zorggebruiker

Returns

The authorization request url

Return type

str

coroutine create_oauth_session(za_name, gegevensdienst_id, **kwargs)[source]

Create and return a new OAuthSession to start the oauth flow. Add the zorggebruikers choice of zorgaanbieder gegevensdienst. (FLOW #2)

Parameters
  • za_name (string) – Name of zorgaanbieder chosen by the zorggebruiker.

  • gegevensdienst_id (string) – Id of the gegevensdienst chosen by the zorggebruiker

  • **kwargs (various) – Keyword arguments get passed on to the data_store.create_oauth_session function, e.g. db object

Returns

The created OAuthSession

Return type

OAuthSession

coroutine exchange_authorization_code(oauth_session, **kwargs)[source]

Make a request to a oauth server with the supplied make_request function on instantiation of the Client, exchange the received authorization code for an access token and update the oauth_session. (FLOW #12)

Parameters
  • oauth_session (OAuthSession) – Authorized oauth session of which to exchange the authorization code

  • **kwargs (various) – Keyword arguments get passed on to the data_store.save_oauth_session function, e.g. db object

Returns

The updated OAuthSession containing the access_token

Return type

OAuthSession

Raises

OAuthException – If the server’s response is invalid

coroutine get_zal()[source]

Return a tuple of the ZAL and GNL (zal, gnl) returned by the get_zal and get_gnl function supplied in instantiation of Client object

coroutine handle_auth_response(parameters, **kwargs)[source]

Handles the response to the authorization request. (FLOW #10, FLOW #11)

Parameters
  • parameters (dict) – The query params from the servers’s response to the authorization request

  • **kwargs (various) – Keyword arguments get passed on to the data_store.get_oauth_session_by_state function, e.g. db object

Returns

The updated OAuthSession no containing the authorization_code, and authorized set to True

Return type

OAuthSession

Raises
  • OAuthException – If validation of the params fails

  • ValueError – If there is no session found linked to the state parameter in the provided query parameters

Datastore

class medmij_oauth.client.DataStore[source]

Bases: abc.ABC

Abstract Class that handles interaction instantiation, persisting and lookups of OAuthSessions.

coroutine create_oauth_session(state, za_name, gegevensdienst_id, scope, **kwargs)[source]

Create a new oauth_session, persist the oauth_session and return it.

coroutine get_oauth_session_by_id(oauth_session_id, **kwargs)[source]

Get a oauth_session based on it’s id and return it, else return None

coroutine get_oauth_session_by_state(state, **kwargs)[source]

Get a oauth_session based on the state param and return it, else return None

coroutine save_oauth_session(oauth_session, **kwargs)[source]

Persist the current state of the oauth_session and return it

OAuthSession

Class that should be implemented by implementor of the OAuth client. This class is should be instantiated by your implementation of the DataStore base class and represents the current state of an OAuth Session.

The OAuthSession should at least have the following attributes:

  • id (uuid)

  • state (string)

  • scope (string)

  • za_name (string)

  • gegevensdienst_id (string)

  • authorization_code (string)

  • authorized (boolean)

  • access_token (string)

Example implementation:

class OAuthSession():
    def __init__(self, state, za_name, gegevensdienst_id, scope):
        self.id = str(uuid.uuid4())
        self.state = state
        self.scope = scope
        self.za_name = za_name
        self.gegevensdienst_id = gegevensdienst_id
        self.authorization_code = None
        self.authorized = False
        self.access_token = None

medmij_oauth.exceptions module

Module for handling OAuth related errors as specified in rfc6749

OAuthException

exception medmij_oauth.exceptions.OAuthException(error_code, error_description='', redirect=False, base_redirect_url='')[source]

OAuthException class, represents a oauth error as described in rfc6749

Parameters
  • error_code (error code) – Int that represents a type of error

  • error_description (string) – Human readable description of the error e.g. ‘no such resource’

  • redirect (bool) – Indication if on handling of the exception the user should be redirected back to the client application of if the error should be rendered to the screen

  • base_redirect_url (string (optional)) – The base of the redirect url (on redirection the error params are appended to the base_redirect_url as a query string)

Usage examples:

raise OAuthException(ERRORS.INVALID_REQUEST, error_description='Invalid redirect url', redirect=False)
raise OAuthException(ERRORS.UNAUTHORIZED_CLIENT, error_description='No such resource', redirect=True, base_redirect_url='https://oauthclient.com')
get_dict()[source]

Return dict representation of the exception that is targeted at the end user. Included properties are ‘error’ and ‘error_description’.

get_json()[source]

Return json representation of the exception that is targeted at the end user Included properties are ‘error’ and ‘error_description’

{
    'error': 'unauthorized_client',
    'error_description': 'no such resource'
}
get_redirect_url()[source]

Return redirect url to which the end user should be redirected. The redirect_url constists of two parts, self.base_redirect_url and a query string that contains the error and error description

Raises a Exception if self.direct != True or if self.base_redirect_url is not set.

e.g.

https://oauthclient.com/cb/?error=unauthorized_client&error_description=No%20such%20resource

Error codes

medmij_oauth.exceptions.lookup_error_code(error)[source]

Lookup error code by text. When an oauth client receives a error response, it can reproduce the exception by looking up the error code with the ‘error’ query param that it received.

Raises a ValueError if the error passed to it is unknown.

Example:

error = query_params.get('error')
error_description = query_params.get('error_description')

raise OAuthException(error_code=lookup_error_code(error), error_description=error_description)
class medmij_oauth.exceptions.ERRORS(value)[source]

Error codes enum to be used as error_code for instantiation of OAuthException

Usage example:

raise OAuthException(ERRORS.UNAUTHORIZED_CLIENT, 'no such resource', ...)
ACCESS_DENIED = 2
INVALID_CLIENT = 8
INVALID_GRANT = 9
INVALID_REQUEST = 1
INVALID_SCOPE = 5
SERVER_ERROR = 6
TEMPORARILY_UNAVAILABLE = 7
UNAUTHORIZED_CLIENT = 3
UNSUPPORTED_GRANT_TYPE = 10
UNSUPPORTED_RESPONSE_TYPE = 4