From 2b9873483a6fdb9cabe3cdac5730057b6f08fb2a Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Wed, 24 May 2017 12:57:55 -0400 Subject: [PATCH] Enable toggling of the direct login feature in the superuser panel Allows superusers to disable login to the UI via credentials if at least one OIDC provider is configured --- .../directives/config/config-setup-tool.html | 142 +++++++++++------- static/js/core-config-setup.js | 6 + util/config/configutil.py | 1 + util/config/validator.py | 2 + .../validators/test/test_validate_access.py | 22 +++ util/config/validators/validate_access.py | 22 +++ 6 files changed, 142 insertions(+), 53 deletions(-) create mode 100644 util/config/validators/test/test_validate_access.py create mode 100644 util/config/validators/validate_access.py diff --git a/static/directives/config/config-setup-tool.html b/static/directives/config/config-setup-tool.html index 205142beb..873b5650c 100644 --- a/static/directives/config/config-setup-tool.html +++ b/static/directives/config/config-setup-tool.html @@ -53,59 +53,6 @@ - - Anonymous Access: - -
- Enable Anonymous Access -
-
- If enabled, public repositories and search can be accessed by anyone that can - reach the registry, even if they are not authenticated. Disable to only allow - authenticated users to view and pull "public" resources. -
- - - - User Creation: - -
- Enable Open User Creation -
-
- If enabled, user accounts can be created by anyone. - Users can always be created in the users panel under this superuser view. -
- - - - Encrypted Client Password: - -
- Require Encrypted Client Passwords -
-
- If enabled, users will not be able to login from the Docker command - line with a non-encrypted password and must generate an encrypted - password to use. -
-
- This feature is highly recommended for setups with external authentication, as Docker currently stores passwords in plaintext on user's machines. -
- - - - Team Invitations: - -
- Require Team Invitations -
-
- If enabled, when adding a new user to a team, they will receive an invitation to join the team, with the option to decline. - Otherwise, users will be immediately part of a team when added by a team administrator. -
- - @@ -1149,6 +1096,95 @@ + +
+
+ Access Settings +
+
+
+

Various settings around access and authentication to the registry.

+
+ + + + + + + + + + + + + + + + + + + + + + +
Basic Credentials Login: +
+ Login to User Interface via credentials +
+
+
+ Login to User Interface via credentials must be enabled. Click here to enable. +
+
+ Login to User Interface via credentials is enabled (requires at least one OIDC provider to disable) +
+
+
+ If enabled, users will be able to login to the user interface via their username and password credentials. +
+
+ If disabled, users will only be able to login to the user interface via one of the configured External Authentication providers. +
+
Anonymous Access: +
+ Enable Anonymous Access +
+
+ If enabled, public repositories and search can be accessed by anyone that can + reach the registry, even if they are not authenticated. Disable to only allow + authenticated users to view and pull "public" resources. +
+
User Creation: +
+ Enable Open User Creation +
+
+ If enabled, user accounts can be created by anyone. + Users can always be created in the users panel under this superuser view. +
+
Encrypted Client Password: +
+ Require Encrypted Client Passwords +
+
+ If enabled, users will not be able to login from the Docker command + line with a non-encrypted password and must generate an encrypted + password to use. +
+
+ This feature is highly recommended for setups with external authentication, as Docker currently stores passwords in plaintext on user's machines. +
+
Team Invitations: +
+ Require Team Invitations +
+
+ If enabled, when adding a new user to a team, they will receive an invitation to join the team, with the option to decline. + Otherwise, users will be immediately part of a team when added by a team administrator. +
+
+
+
diff --git a/static/js/core-config-setup.js b/static/js/core-config-setup.js index d483e3d3e..763330ff6 100644 --- a/static/js/core-config-setup.js +++ b/static/js/core-config-setup.js @@ -23,6 +23,8 @@ angular.module("core-config-setup", ['angularFileUpload']) {'id': 'time-machine', 'title': 'Time Machine'}, + {'id': 'access', 'title': 'Access Settings'}, + {'id': 'ssl', 'title': 'SSL certificate and key', 'condition': function(config) { return config.PREFERRED_URL_SCHEME == 'https'; }}, @@ -136,6 +138,10 @@ angular.module("core-config-setup", ['angularFileUpload']) ] }; + $scope.enableFeature = function(config, feature) { + config[feature] = true; + }; + $scope.validateHostname = function(hostname) { if (hostname.indexOf('127.0.0.1') == 0 || hostname.indexOf('localhost') == 0) { return 'Please specify a non-localhost hostname. "localhost" will refer to the container, not your machine.' diff --git a/util/config/configutil.py b/util/config/configutil.py index ee39feba3..44ea58574 100644 --- a/util/config/configutil.py +++ b/util/config/configutil.py @@ -19,6 +19,7 @@ def add_enterprise_config_defaults(config_obj, current_secret_key, hostname): config_obj['FEATURE_REQUIRE_TEAM_INVITE'] = config_obj.get('FEATURE_REQUIRE_TEAM_INVITE', True) config_obj['FEATURE_CHANGE_TAG_EXPIRATION'] = config_obj.get('FEATURE_CHANGE_TAG_EXPIRATION', True) + config_obj['FEATURE_DIRECT_LOGIN'] = config_obj.get('FEATURE_DIRECT_LOGIN', True) # Default features that are off. config_obj['FEATURE_MAILING'] = config_obj.get('FEATURE_MAILING', False) diff --git a/util/config/validator.py b/util/config/validator.py index 360942b16..4190caa34 100644 --- a/util/config/validator.py +++ b/util/config/validator.py @@ -20,6 +20,7 @@ from util.config.validators.validate_gitlab_trigger import GitLabTriggerValidato from util.config.validators.validate_github import GitHubLoginValidator, GitHubTriggerValidator from util.config.validators.validate_oidc import OIDCLoginValidator from util.config.validators.validate_timemachine import TimeMachineValidator +from util.config.validators.validate_access import AccessSettingsValidator logger = logging.getLogger(__name__) @@ -55,6 +56,7 @@ VALIDATORS = { BittorrentValidator.name: BittorrentValidator.validate, OIDCLoginValidator.name: OIDCLoginValidator.validate, TimeMachineValidator.name: TimeMachineValidator.validate, + AccessSettingsValidator.name: AccessSettingsValidator.validate, } def validate_service_for_config(service, config, password=None): diff --git a/util/config/validators/test/test_validate_access.py b/util/config/validators/test/test_validate_access.py new file mode 100644 index 000000000..9008767eb --- /dev/null +++ b/util/config/validators/test/test_validate_access.py @@ -0,0 +1,22 @@ +import pytest + +from util.config.validators import ConfigValidationException +from util.config.validators.validate_access import AccessSettingsValidator + +from test.fixtures import * + +@pytest.mark.parametrize('unvalidated_config, expected_exception', [ + ({}, None), + ({'FEATURE_DIRECT_LOGIN': False}, ConfigValidationException), + ({'FEATURE_DIRECT_LOGIN': False, 'SOMETHING_LOGIN_CONFIG': {}}, None), + ({'FEATURE_DIRECT_LOGIN': False, 'FEATURE_GITHUB_LOGIN': True}, None), + ({'FEATURE_DIRECT_LOGIN': False, 'FEATURE_GOOGLE_LOGIN': True}, None), +]) +def test_validate_invalid_oidc_login_config(unvalidated_config, expected_exception, app): + validator = AccessSettingsValidator() + + if expected_exception is not None: + with pytest.raises(expected_exception): + validator.validate(unvalidated_config, None, None) + else: + validator.validate(unvalidated_config, None, None) diff --git a/util/config/validators/validate_access.py b/util/config/validators/validate_access.py new file mode 100644 index 000000000..eb80090d8 --- /dev/null +++ b/util/config/validators/validate_access.py @@ -0,0 +1,22 @@ +from app import app +from util.config.validators import BaseValidator, ConfigValidationException +from oauth.loginmanager import OAuthLoginManager +from oauth.oidc import OIDCLoginService + +class AccessSettingsValidator(BaseValidator): + name = "access" + + @classmethod + def validate(cls, config, user, user_password): + if not config.get('FEATURE_DIRECT_LOGIN', True): + # Make sure we have at least one OIDC enabled. + github_login = config.get('FEATURE_GITHUB_LOGIN', False) + google_login = config.get('FEATURE_GOOGLE_LOGIN', False) + + client = app.config['HTTPCLIENT'] + login_manager = OAuthLoginManager(config, client=client) + custom_oidc = [s for s in login_manager.services if isinstance(s, OIDCLoginService)] + + if not github_login and not google_login and not custom_oidc: + msg = 'Cannot disable credentials login to UI without configured OIDC service' + raise ConfigValidationException(msg)