Skip to content

Commit

Permalink
NXPY-219: Add support for OpenID Connect Discovery
Browse files Browse the repository at this point in the history
  • Loading branch information
Mickaël Schoentgen authored and BoboTiG committed May 3, 2021
1 parent 91778cb commit cf81a66
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 20 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ Changelog

Release date: ``2021-0x-xx``

- `NXPY- <https://jira.nuxeo.com/browse/NXPY->`__:
- `NXPY-219 <https://jira.nuxeo.com/browse/NXPY-219>`__: Add support for OpenID Connect Discovery

Technical changes
-----------------

-
- Added ``redirect_uri`` keyword argument to ``OAuth2.__init__()``
- Added ``openid_configuration_url`` keyword argument to ``OAuth2.__init__()``

5.1.0
-----
Expand Down
6 changes: 5 additions & 1 deletion examples/authentication.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,16 @@ OAuth2 with automatic credentials renewal is available by default.

The ``OAuth2`` class can take several optionnal keyword arguments:

- ``authorization_endpoint``: custom authorization endpoint
- ``client_id``: the consumer client ID
- ``client_secret``: the consumer client secret
- ``openid_configuration_url``: configuration URL for OpenID Connect Discovery
- ``redirect_uri``: the redirect URI (mandatory when using ADFS ofr instance)
- ``token``: existent token
- ``authorization_endpoint``: custom authorization endpoint
- ``token_endpoint``: custom token endpoint

When ``openid_configuration_url`` is passed, ``authorization_endpoint`` and ``token_endpoint`` have no effect.

Scenario 1: Generating a New Token
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
52 changes: 35 additions & 17 deletions nuxeo/auth/oauth2.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from time import time

import requests
from authlib.common.security import generate_token
from authlib.integrations.base_client.errors import AuthlibBaseError
from authlib.integrations.requests_client import OAuth2Session
Expand Down Expand Up @@ -46,32 +47,45 @@ class OAuth2(AuthBase):

def __init__(
self,
host,
client_secret=None,
client_id=None,
token=None,
authorization_endpoint=None,
token_endpoint=None,
host, # type: Text
client_secret=None, # type: Optional[Text]
client_id=None, # type: Optional[Text]
token=None, # type: Optional[Text]
authorization_endpoint=None, # type: Optional[Text]
token_endpoint=None, # type: Optional[Text]
redirect_uri=None, # type: Optional[Text]
openid_configuration_url=None, # type: Optional[Text]
):
# type: (Text, Optional[Text], Optional[Text], Optional[Token], Optional[Text], Optional[Text]) -> None
# type: (...) -> None
if not host.endswith("/"):
host += "/"
self._host = host
self._client = OAuth2Session(client_id=client_id, client_secret=client_secret)
self._client.session.hooks["response"] = [log_response]
self._token_header = ""
self.token = {} # type: Token
if token:
self.set_token(token)

# Allow to pass custom endpoints (not handled by the platform)
auth_endpoint = authorization_endpoint or DEFAULT_AUTHORIZATION_ENDPOINT
token_endpoint = token_endpoint or DEFAULT_TOKEN_ENDPOINT
if not auth_endpoint.startswith("https://"):
auth_endpoint = self._host + auth_endpoint
# Allow to pass custom endpoints
if openid_configuration_url:
# OpenID Connect Discovery
with requests.get(openid_configuration_url) as req:
data = req.json()
auth_endpoint = data["authorization_endpoint"]
token_endpoint = data["token_endpoint"]
else:
auth_endpoint = authorization_endpoint or DEFAULT_AUTHORIZATION_ENDPOINT
token_endpoint = token_endpoint or DEFAULT_TOKEN_ENDPOINT
if not auth_endpoint.startswith("https://"):
auth_endpoint = self._host + auth_endpoint
if not token_endpoint.startswith("https://"):
token_endpoint = self._host + token_endpoint

self._client = OAuth2Session(
client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri
)
self._client.session.hooks["response"] = [log_response]

self._authorization_endpoint = auth_endpoint
if not token_endpoint.startswith("https://"):
token_endpoint = self._host + token_endpoint
self._token_endpoint = token_endpoint

def _request(self, method, *args, **kwargs):
Expand All @@ -86,7 +100,11 @@ def _request(self, method, *args, **kwargs):
def token_is_expired(self):
# type: () -> bool
"""Check whenever the current token is expired or not."""
return self.token and self.token["expires_at"] < time()
token = self.token
if not token:
return False

return token["expires_at"] < time()

def create_authorization_url(self, **kwargs):
# type: (Any) -> Tuple[str, str, str]
Expand Down
29 changes: 29 additions & 0 deletions tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from __future__ import unicode_literals

import pytest
import responses
from requests import Request

from nuxeo.auth import BasicAuth, JWTAuth, OAuth2, PortalSSOAuth, TokenAuth
Expand Down Expand Up @@ -129,6 +130,34 @@ def test_oauth2_token():
assert prepared.headers[auth.AUTHORIZATION] == "Bearer <ACCESS>"


@responses.activate
def test_oauth2_openid_configuration_url():
authorization_endpoint = "/authorization/endpoint"
token_endpoint = "/token/endpoint"
redirect_uri = "/custom/redirect/url"
openid_configuration_url = "https://example.com/.well-known/openid-configuration"
openid_configuation = {
"authorization_endpoint": "https://real.authorization.endpoint",
"token_endpoint": "https://real.token.endpoint",
}

responses.add(responses.GET, openid_configuration_url, json=openid_configuation)
auth = OAuth2(
"<host>",
authorization_endpoint=authorization_endpoint,
token_endpoint=token_endpoint,
openid_configuration_url=openid_configuration_url,
redirect_uri=redirect_uri,
)

# Ensure the redirect_uri is well set
assert auth._client.redirect_uri == redirect_uri

# Check that endpoints referenced from the OpenID configuration have the priority over specific endpoints arguments
assert auth._authorization_endpoint == openid_configuation["authorization_endpoint"]
assert auth._token_endpoint == openid_configuation["token_endpoint"]


def test_token():
auth = TokenAuth("secure_token")
req = Request("GET", "https://httpbin.org/get", auth=auth)
Expand Down

0 comments on commit cf81a66

Please sign in to comment.