Skip to content

Commit

Permalink
Allow RedirectUriConfig be specified in OIDCAuthentication constructo…
Browse files Browse the repository at this point in the history
…r. (#97)

This allows complete freedom to specify the redirect URI structure
in more complex setups, e.g. when using 'SCRIPT_NAME' with gunicorn.
Ref #95.
  • Loading branch information
zamzterz authored Nov 10, 2020
1 parent 68e39cd commit cc99346
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 18 deletions.
9 changes: 6 additions & 3 deletions src/flask_pyoidc/flask_pyoidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,25 +40,28 @@ class OIDCAuthentication:
OIDCAuthentication object for Flask extension.
"""

def __init__(self, provider_configurations, app=None):
def __init__(self, provider_configurations, app=None, redirect_uri_config = None):
"""
Args:
provider_configurations (Mapping[str, ProviderConfiguration]):
provider configurations by name
app (flask.app.Flask): optional Flask app
redirect_uri_config (RedirectUriConfig): optional redirect URI config to use instead of
'OIDC_REDIRECT_URI' config parameter.
"""
self._provider_configurations = provider_configurations

self.clients = None
self._logout_view = None
self._error_view = None
self._redirect_uri_config = None
self._redirect_uri_config = redirect_uri_config

if app:
self.init_app(app)

def init_app(self, app):
self._redirect_uri_config = RedirectUriConfig(app.config)
if not self._redirect_uri_config:
self._redirect_uri_config = RedirectUriConfig.from_config(app.config)

# setup redirect_uri as a flask route
app.add_url_rule('/' + self._redirect_uri_config.endpoint,
Expand Down
40 changes: 30 additions & 10 deletions src/flask_pyoidc/redirect_uri_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,48 @@


class RedirectUriConfig:
def __init__(self, config):
def __init__(self, full_uri, endpoint):
self.full_uri = full_uri
self.endpoint = endpoint

def __eq__(self, other):
return self.full_uri == other.full_uri and self.endpoint == other.endpoint

def __str__(self):
return '(' + self.full_uri + ', ' + self.endpoint + ')'

def __repr__(self):
return str(self)

@classmethod
def from_config(cls, config):
if 'OIDC_REDIRECT_URI' in config:
self._parse_redirect_uri(config['OIDC_REDIRECT_URI'])
else:
self._parse_legacy_config(config)
return cls(*RedirectUriConfig._parse_redirect_uri(config['OIDC_REDIRECT_URI']))

def _parse_redirect_uri(self, redirect_uri):
return cls(*RedirectUriConfig._parse_legacy_config(config))

@staticmethod
def _parse_redirect_uri(redirect_uri):
parsed = urlparse(redirect_uri)
self.full_uri = redirect_uri
self.endpoint = parsed.path.lstrip('/')
endpoint = parsed.path.lstrip('/')
return redirect_uri, endpoint

def _parse_legacy_config(self, config):
@staticmethod
def _parse_legacy_config(config):
redirect_domain = config.get('OIDC_REDIRECT_DOMAIN', config.get('SERVER_NAME'))
if not redirect_domain:
raise ValueError("'OIDC_REDIRECT_URI' must be configured.")

scheme = config.get('PREFERRED_URL_SCHEME', 'http')
self.endpoint = config.get('OIDC_REDIRECT_ENDPOINT', 'redirect_uri').lstrip('/')
self.full_uri = scheme + '://' + redirect_domain + '/' + self.endpoint

warnings.warn(
"Please use 'OIDC_REDIRECT_URI' to configure the redirect_uri for flask-pyoidc. 'OIDC_REDIRECT_DOMAIN' and 'OIDC_REDIRECT_ENDPOINT' have been deprecated.",
DeprecationWarning,
stacklevel=2
)

endpoint = config.get('OIDC_REDIRECT_ENDPOINT', 'redirect_uri').lstrip('/')
full_uri = scheme + '://' + redirect_domain + '/' + endpoint

return full_uri, endpoint

12 changes: 12 additions & 0 deletions tests/test_flask_pyoidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import time
from datetime import datetime
from flask import Flask
from flask_pyoidc.redirect_uri_config import RedirectUriConfig
from http.cookies import SimpleCookie
from jwkest import jws
from oic.oic import AuthorizationResponse
Expand Down Expand Up @@ -72,6 +73,17 @@ def assert_view_mock(self, callback_mock, result):
assert callback_mock.called
assert result == self.CALLBACK_RETURN_VALUE

def test_explicit_redirect_uri_config_should_be_preferred(self):
redirect_uri_config = RedirectUriConfig('https://example.com/abc/redirect_uri', 'redirect_uri')
assert OIDCAuthentication({}, self.app, redirect_uri_config)._redirect_uri_config == redirect_uri_config

def test_explicit_redirect_uri_config_should_be_preserved_after_init_app(self):
redirect_uri_config = RedirectUriConfig('https://example.com/abc/redirect_uri', 'redirect_uri')
authn = OIDCAuthentication({}, None, redirect_uri_config)
assert authn._redirect_uri_config == redirect_uri_config
authn.init_app(self.app)
assert authn._redirect_uri_config == redirect_uri_config

def test_should_authenticate_if_no_session(self):
authn = self.init_app()
view_mock = self.get_view_mock()
Expand Down
10 changes: 5 additions & 5 deletions tests/test_redirect_uri_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@ class TestRedirectUriConfig(object):
LEGACY_CONFIG = {'SERVER_NAME': 'example.com', 'PREFERRED_URL_SCHEME': 'http'}

def test_legacy_config_defaults(self):
config = RedirectUriConfig(self.LEGACY_CONFIG)
config = RedirectUriConfig.from_config(self.LEGACY_CONFIG)
assert config.endpoint == 'redirect_uri'
assert config.full_uri == 'http://example.com/redirect_uri'

def test_legacy_config_endpoint(self):
config = RedirectUriConfig({'OIDC_REDIRECT_ENDPOINT': '/foo', **self.LEGACY_CONFIG})
config = RedirectUriConfig.from_config({'OIDC_REDIRECT_ENDPOINT': '/foo', **self.LEGACY_CONFIG})
assert config.endpoint == 'foo'

def test_legacy_config_domain(self):
config = {
'OIDC_REDIRECT_DOMAIN': 'other.example.com:6000', # should be preferred over SERVER_NAME
**self.LEGACY_CONFIG
}
redirect_uri_config = RedirectUriConfig(config)
redirect_uri_config = RedirectUriConfig.from_config(config)
assert redirect_uri_config.full_uri == 'http://other.example.com:6000/redirect_uri'

def test_redirect_uri_config(self):
Expand All @@ -28,11 +28,11 @@ def test_redirect_uri_config(self):
'OIDC_REDIRECT_DOMAIN': 'other.example.com:6000',
**self.LEGACY_CONFIG
}
redirect_uri_config = RedirectUriConfig(config)
redirect_uri_config = RedirectUriConfig.from_config(config)
assert redirect_uri_config.full_uri == 'https://myexample.com:6000/callback'
assert redirect_uri_config.endpoint == 'callback'

def test_should_raise_if_missing_all_config(self):
with pytest.raises(ValueError) as exc_info:
RedirectUriConfig({})
RedirectUriConfig.from_config({})
assert 'OIDC_REDIRECT_URI' in str(exc_info.value)

0 comments on commit cc99346

Please sign in to comment.