diff --git a/conf/gunicorn_local.py b/conf/gunicorn_local.py index bf312a5ef..a992326b7 100644 --- a/conf/gunicorn_local.py +++ b/conf/gunicorn_local.py @@ -2,8 +2,8 @@ import sys import os sys.path.append(os.path.join(os.path.dirname(__file__), "../")) -from util.log import logfile_path from Crypto import Random +from util.log import logfile_path logconfig = logfile_path(debug=True) diff --git a/conf/gunicorn_registry.py b/conf/gunicorn_registry.py index dce4c7ac8..b821d3d72 100644 --- a/conf/gunicorn_registry.py +++ b/conf/gunicorn_registry.py @@ -2,8 +2,8 @@ import sys import os sys.path.append(os.path.join(os.path.dirname(__file__), "../")) -from util.log import logfile_path from Crypto import Random +from util.log import logfile_path logconfig = logfile_path(debug=False) diff --git a/conf/gunicorn_secscan.py b/conf/gunicorn_secscan.py index 04b7cdce9..eea9afbf3 100644 --- a/conf/gunicorn_secscan.py +++ b/conf/gunicorn_secscan.py @@ -2,8 +2,8 @@ import sys import os sys.path.append(os.path.join(os.path.dirname(__file__), "../")) -from util.log import logfile_path from Crypto import Random +from util.log import logfile_path logconfig = logfile_path(debug=False) diff --git a/conf/gunicorn_verbs.py b/conf/gunicorn_verbs.py index 9f2c5aef1..fedf21cfb 100644 --- a/conf/gunicorn_verbs.py +++ b/conf/gunicorn_verbs.py @@ -2,9 +2,8 @@ import sys import os sys.path.append(os.path.join(os.path.dirname(__file__), "../")) -from util.log import logfile_path from Crypto import Random - +from util.log import logfile_path logconfig = logfile_path(debug=False) diff --git a/conf/gunicorn_web.py b/conf/gunicorn_web.py index 411ae1190..57e2eb17b 100644 --- a/conf/gunicorn_web.py +++ b/conf/gunicorn_web.py @@ -2,8 +2,8 @@ import sys import os sys.path.append(os.path.join(os.path.dirname(__file__), "../")) -from util.log import logfile_path from Crypto import Random +from util.log import logfile_path logconfig = logfile_path(debug=False) diff --git a/data/users/oidc.py b/data/users/oidc.py index 8c7831bf2..53ab18b0b 100644 --- a/data/users/oidc.py +++ b/data/users/oidc.py @@ -1,6 +1,6 @@ import logging -from data.users.federated import FederatedUsers, UserInformation +from data import model from oauth.loginmanager import OAuthLoginManager from oauth.oidc import PublicKeyLoadException from util.security.jwtutil import InvalidTokenError @@ -13,29 +13,29 @@ class UnknownServiceException(Exception): pass -class OIDCInternalAuth(FederatedUsers): +class OIDCInternalAuth(object): """ Handles authentication by delegating authentication to a signed OIDC JWT produced by the configured OIDC service. """ def __init__(self, config, login_service_id, requires_email): - super(OIDCInternalAuth, self).__init__('oidc', requires_email) login_manager = OAuthLoginManager(config) + + self.login_service_id = login_service_id self.login_service = login_manager.get_service(login_service_id) if self.login_service is None: raise UnknownServiceException('Unknown OIDC login service %s' % login_service_id) + @property + def federated_service(self): + return None + @property def supports_encrypted_credentials(self): # Since the "password" is already a signed JWT. return False - def get_user(self, username_or_email): - return (None, 'Cannot retrieve users for OIDC') - - def query_users(self, query, limit=20): - return (None, 'Cannot query users for OIDC') - def verify_credentials(self, username_or_email, id_token): + # Parse the ID token. try: payload = self.login_service.decode_user_jwt(id_token) except InvalidTokenError as ite: @@ -45,5 +45,39 @@ class OIDCInternalAuth(FederatedUsers): logger.exception('Could not load public key during OIDC decode: %s', pke.message) return (None, 'Could not validate OIDC token') - user_info = UserInformation(username=payload['sub'], id=payload['sub'], email=None) - return (user_info, None) + # Find the user ID. + user_id = payload['sub'] + + # Lookup the federated login and user record with that matching ID and service. + user_found = model.user.verify_federated_login(self.login_service_id, user_id) + if user_found is None: + return (None, 'User does not exist') + + if not user_found.enabled: + return (None, 'User account is disabled. Please contact your administrator.') + + return (user_found, None) + + def verify_and_link_user(self, username_or_email, password): + return self.verify_credentials(username_or_email, password) + + def confirm_existing_user(self, username, password): + return self.verify_credentials(username, password) + + def link_user(self, username_or_email): + return (None, 'Unsupported for this authentication system') + + def get_and_link_federated_user_info(self, user_info): + return (None, 'Unsupported for this authentication system') + + def query_users(self, query, limit): + return (None, '', '') + + def check_group_lookup_args(self, group_lookup_args): + return (False, 'Not supported') + + def iterate_group_members(self, group_lookup_args, page_size=None, disable_pagination=False): + return (None, 'Not supported') + + def service_metadata(self): + return {} diff --git a/data/users/test/test_oidc.py b/data/users/test/test_oidc.py index 5d7a8bfd4..68e8fdf1d 100644 --- a/data/users/test/test_oidc.py +++ b/data/users/test/test_oidc.py @@ -1,19 +1,40 @@ +import pytest + from httmock import HTTMock +from data import model from data.users.oidc import OIDCInternalAuth from oauth.test.test_oidc import (id_token, oidc_service, signing_key, jwks_handler, discovery_handler, app_config, http_client, discovery_content) +from test.fixtures import * -def test_oidc_login(app_config, id_token, jwks_handler, discovery_handler): +@pytest.mark.parametrize('username, expect_success', [ + ('devtable', True), + ('disabled', False) +]) +def test_oidc_login(username, expect_success, app_config, id_token, jwks_handler, + discovery_handler, app): internal_auth = OIDCInternalAuth(app_config, 'someoidc', False) with HTTMock(jwks_handler, discovery_handler): - # Try a valid token. - (user, err) = internal_auth.verify_credentials('someusername', id_token) - assert err is None - assert user.username == 'cooluser' - # Try an invalid token. (user, err) = internal_auth.verify_credentials('someusername', 'invalidtoken') assert err is not None assert user is None + + # Try a valid token for an unlinked user. + (user, err) = internal_auth.verify_credentials('someusername', id_token) + assert err is not None + assert user is None + + # Link the user to the service. + model.user.attach_federated_login(model.user.get_user(username), 'someoidc', 'cooluser') + + # Try a valid token for a linked user. + (user, err) = internal_auth.verify_credentials('someusername', id_token) + if expect_success: + assert err is None + assert user.username == username + else: + assert err is not None + assert user is None diff --git a/static/js/pages/setup.js b/static/js/pages/setup.js index 8ee89d590..df56c9e2c 100644 --- a/static/js/pages/setup.js +++ b/static/js/pages/setup.js @@ -237,11 +237,10 @@ import * as URI from 'urijs'; $scope.serializeDbUri = function(fields) { if (!fields['server']) { return ''; } + if (!fields['database']) { return ''; } var uri = URI(); try { - if (!fields['server']) { return ''; } - if (!fields['database']) { return ''; } uri = uri && uri.host(fields['server']); uri = uri && uri.protocol(fields['kind']); uri = uri && uri.username(fields['username']);