diff --git a/data/users.py b/data/users.py
index 91b72785c..b2cdfabd5 100644
--- a/data/users.py
+++ b/data/users.py
@@ -54,10 +54,8 @@ class JWTAuthUsers(object):
""" Delegates authentication to a REST endpoint that returns JWTs. """
PUBLIC_KEY_FILENAME = 'jwt-authn.cert'
- def __init__(self, exists_url, verify_url, issuer, override_config_dir, http_client,
- public_key_path=None):
+ def __init__(self, verify_url, issuer, override_config_dir, http_client, public_key_path=None):
self.verify_url = verify_url
- self.exists_url = exists_url
self.issuer = issuer
self.client = http_client
@@ -109,13 +107,6 @@ class JWTAuthUsers(object):
# Parse out the username and email.
return _get_federated_user(payload['sub'], payload['email'], 'jwtauthn', create_new_user)
- def user_exists(self, username):
- result = self.client.get(self.exists_url, auth=(username, ''), timeout=2)
- if result.status_code / 500 >= 1:
- raise Exception('Internal Error when trying to check if user exists: %s' % result.text)
-
- return result.status_code == 200
-
def confirm_existing_user(self, username, password):
db_user = model.get_user(username)
if not db_user:
@@ -140,9 +131,6 @@ class DatabaseUsers(object):
def confirm_existing_user(self, username, password):
return self.verify_user(username, password)
- def user_exists(self, username):
- return model.get_user(username) is not None
-
class LDAPConnection(object):
def __init__(self, ldap_uri, user_dn, user_pw):
@@ -299,10 +287,6 @@ class LDAPUsers(object):
email = found_response[self._email_attr][0]
return _get_federated_user(username, email, 'ldap', create_new_user)
- def user_exists(self, username):
- found_user = self._ldap_user_search(username)
- return found_user is not None
-
class UserAuthentication(object):
@@ -333,10 +317,8 @@ class UserAuthentication(object):
users = LDAPUsers(ldap_uri, base_dn, admin_dn, admin_passwd, user_rdn, uid_attr, email_attr)
elif authentication_type == 'JWT':
verify_url = app.config.get('JWT_VERIFY_ENDPOINT')
- exists_url = app.config.get('JWT_EXISTS_ENDPOINT')
issuer = app.config.get('JWT_AUTH_ISSUER')
- users = JWTAuthUsers(exists_url, verify_url, issuer, override_config_dir,
- app.config['HTTPCLIENT'])
+ users = JWTAuthUsers(verify_url, issuer, override_config_dir, app.config['HTTPCLIENT'])
else:
raise RuntimeError('Unknown authentication type: %s' % authentication_type)
diff --git a/endpoints/api/suconfig.py b/endpoints/api/suconfig.py
index 4802dc99d..b2fa941cd 100644
--- a/endpoints/api/suconfig.py
+++ b/endpoints/api/suconfig.py
@@ -343,6 +343,10 @@ class SuperUserConfigValidate(ApiResource):
'properties': {
'config': {
'type': 'object'
+ },
+ 'password': {
+ 'type': 'string',
+ 'description': 'The users password, used for auth validation'
}
},
},
@@ -358,6 +362,6 @@ class SuperUserConfigValidate(ApiResource):
# this is also safe since this method does not access any information not given in the request.
if not CONFIG_PROVIDER.yaml_exists() or SuperUserPermission().can():
config = request.get_json()['config']
- return validate_service_for_config(service, config)
+ return validate_service_for_config(service, config, request.get_json().get('password', ''))
abort(403)
\ No newline at end of file
diff --git a/static/directives/config/config-setup-tool.html b/static/directives/config/config-setup-tool.html
index 31a05706b..b1c0c7da4 100644
--- a/static/directives/config/config-setup-tool.html
+++ b/static/directives/config/config-setup-tool.html
@@ -383,20 +383,6 @@
-
- User Exists Endpoint: |
-
-
-
- The URL (starting with http or https) on the JWT authentication server for checking whether a username exists.
-
-
-
- The username will be sent in the Authorization header as Basic Auth, and this endpoint should return 200 OK on success (or a 4** otherwise).
-
- |
-
Authentication Issuer: |
diff --git a/static/js/core-config-setup.js b/static/js/core-config-setup.js
index 1d9cf852d..0679e3759 100644
--- a/static/js/core-config-setup.js
+++ b/static/js/core-config-setup.js
@@ -25,11 +25,11 @@ angular.module("core-config-setup", ['angularFileUpload'])
{'id': 'ldap', 'title': 'LDAP Authentication', 'condition': function(config) {
return config.AUTHENTICATION_TYPE == 'LDAP';
- }},
+ }, 'password': true},
{'id': 'jwt', 'title': 'JWT Authentication', 'condition': function(config) {
return config.AUTHENTICATION_TYPE == 'JWT';
- }},
+ }, 'password': true},
{'id': 'mail', 'title': 'E-mail Support', 'condition': function(config) {
return config.FEATURE_MAILING;
@@ -153,12 +153,17 @@ angular.module("core-config-setup", ['angularFileUpload'])
$scope.savingConfiguration = false;
};
- $scope.validateService = function(serviceInfo) {
+ $scope.validateService = function(serviceInfo, opt_password) {
var params = {
'service': serviceInfo.service.id
};
- ApiService.scValidateConfig({'config': $scope.config}, params).then(function(resp) {
+ var data = {
+ 'config': $scope.config,
+ 'password': opt_password || ''
+ };
+
+ ApiService.scValidateConfig(data, params).then(function(resp) {
serviceInfo.status = resp.status ? 'success' : 'error';
serviceInfo.errorMessage = $.trim(resp.reason || '');
}, ApiService.errorDisplay('Could not validate configuration. Please report this error.'));
@@ -175,6 +180,57 @@ angular.module("core-config-setup", ['angularFileUpload'])
};
$scope.validateAndSave = function() {
+ $scope.validating = $scope.getServices($scope.config);
+
+ var requirePassword = false;
+ for (var i = 0; i < $scope.validating.length; ++i) {
+ var serviceInfo = $scope.validating[i];
+ if (serviceInfo.service.password) {
+ requirePassword = true;
+ break;
+ }
+ }
+
+ if (!requirePassword) {
+ $scope.performValidateAndSave();
+ return;
+ }
+
+ var box = bootbox.dialog({
+ "message": 'Please enter your superuser password to validate your auth configuration:' +
+ '',
+ "title": 'Enter Password',
+ "buttons": {
+ "verify": {
+ "label": "Validate Config",
+ "className": "btn-success",
+ "callback": function() {
+ $scope.performValidateAndSave($('#validatePassword').val());
+ }
+ },
+ "close": {
+ "label": "Cancel",
+ "className": "btn-default",
+ "callback": function() {
+ }
+ }
+ }
+ });
+
+ box.bind('shown.bs.modal', function(){
+ box.find("input").focus();
+ box.find("form").submit(function() {
+ if (!$('#validatePassword').val()) { return; }
+
+ box.modal('hide');
+ verifyNow();
+ });
+ });
+ };
+
+ $scope.performValidateAndSave = function(opt_password) {
$scope.savingConfiguration = false;
$scope.validating = $scope.getServices($scope.config);
@@ -185,7 +241,7 @@ angular.module("core-config-setup", ['angularFileUpload'])
for (var i = 0; i < $scope.validating.length; ++i) {
var serviceInfo = $scope.validating[i];
- $scope.validateService(serviceInfo);
+ $scope.validateService(serviceInfo, opt_password);
}
};
diff --git a/test/test_jwt_auth.py b/test/test_jwt_auth.py
index 1cd17a9cf..1cf6c845d 100644
--- a/test/test_jwt_auth.py
+++ b/test/test_jwt_auth.py
@@ -42,15 +42,6 @@ class JWTAuthTestCase(LiveServerTestCase):
data = base64.b64decode(request.headers['Authorization'][len('Basic '):])
return data.split(':', 1)
- @jwt_app.route('/user/exists', methods=['GET'])
- def user_exists():
- username, _ = _get_basic_auth()
- for user in users:
- if user['name'] == username or user['email'] == username:
- return 'OK'
-
- abort(404)
-
@jwt_app.route('/user/verify', methods=['GET'])
def verify_user():
username, password = _get_basic_auth()
@@ -92,7 +83,6 @@ class JWTAuthTestCase(LiveServerTestCase):
self.session = requests.Session()
self.jwt_auth = JWTAuthUsers(
- self.get_server_url() + '/user/exists',
self.get_server_url() + '/user/verify',
'authy', '', app.config['HTTPCLIENT'],
JWTAuthTestCase.public_key.name)
@@ -101,13 +91,6 @@ class JWTAuthTestCase(LiveServerTestCase):
finished_database_for_testing(self)
self.ctx.__exit__(True, None, None)
- def test_user_exists(self):
- self.assertFalse(self.jwt_auth.user_exists('testuser'))
- self.assertFalse(self.jwt_auth.user_exists('anotheruser'))
-
- self.assertTrue(self.jwt_auth.user_exists('cooluser'))
- self.assertTrue(self.jwt_auth.user_exists('user@domain.com'))
-
def test_verify_user(self):
result, error_message = self.jwt_auth.verify_user('invaliduser', 'foobar')
self.assertEquals('Invalid username or password', error_message)
diff --git a/util/config/validator.py b/util/config/validator.py
index 2829a5733..1fc5b2b62 100644
--- a/util/config/validator.py
+++ b/util/config/validator.py
@@ -7,7 +7,7 @@ import OpenSSL
import logging
from fnmatch import fnmatch
-from data.users import LDAPConnection, JWTAuthUsers
+from data.users import LDAPConnection, JWTAuthUsers, LDAPUsers
from flask import Flask
from flask.ext.mail import Mail, Message
from data.database import validate_database_url, User
@@ -31,7 +31,7 @@ def get_storage_provider(config):
except TypeError:
raise Exception('Missing required storage configuration parameter(s)')
-def validate_service_for_config(service, config):
+def validate_service_for_config(service, config, password=None):
""" Attempts to validate the configuration for the given service. """
if not service in _VALIDATORS:
return {
@@ -39,7 +39,7 @@ def validate_service_for_config(service, config):
}
try:
- _VALIDATORS[service](config)
+ _VALIDATORS[service](config, password)
return {
'status': True
}
@@ -51,7 +51,7 @@ def validate_service_for_config(service, config):
}
-def _validate_database(config):
+def _validate_database(config, _):
""" Validates connecting to the database. """
try:
validate_database_url(config['DB_URI'])
@@ -62,7 +62,7 @@ def _validate_database(config):
raise ex
-def _validate_redis(config):
+def _validate_redis(config, _):
""" Validates connecting to redis. """
redis_config = config.get('BUILDLOGS_REDIS', {})
if not 'host' in redis_config:
@@ -72,7 +72,7 @@ def _validate_redis(config):
client.ping()
-def _validate_registry_storage(config):
+def _validate_registry_storage(config, _):
""" Validates registry storage. """
driver = get_storage_provider(config)
@@ -87,7 +87,7 @@ def _validate_registry_storage(config):
raise Exception('Could not prepare storage: %s' % str(ex))
-def _validate_mailing(config):
+def _validate_mailing(config, _):
""" Validates sending email. """
test_app = Flask("mail-test-app")
test_app.config.update(config)
@@ -103,7 +103,7 @@ def _validate_mailing(config):
test_mail.send(test_msg)
-def _validate_gitlab(config):
+def _validate_gitlab(config, _):
""" Validates the OAuth credentials and API endpoint for a GitLab service. """
github_config = config.get('GITLAB_TRIGGER_CONFIG')
if not github_config:
@@ -130,7 +130,7 @@ def _validate_gitlab(config):
def _validate_github(config_key):
- return lambda config: _validate_github_with_key(config_key, config)
+ return lambda config, _: _validate_github_with_key(config_key, config)
def _validate_github_with_key(config_key, config):
@@ -167,7 +167,7 @@ def _validate_github_with_key(config_key, config):
raise Exception('Invalid organization: %s' % org_id)
-def _validate_bitbucket(config):
+def _validate_bitbucket(config, _):
""" Validates the config for BitBucket. """
trigger_config = config.get('BITBUCKET_TRIGGER_CONFIG')
if not trigger_config:
@@ -189,7 +189,7 @@ def _validate_bitbucket(config):
raise Exception('Invaid consumer key or secret')
-def _validate_google_login(config):
+def _validate_google_login(config, _):
""" Validates the Google Login client ID and secret. """
google_login_config = config.get('GOOGLE_LOGIN_CONFIG')
if not google_login_config:
@@ -208,7 +208,7 @@ def _validate_google_login(config):
raise Exception('Invalid client id or client secret')
-def _validate_ssl(config):
+def _validate_ssl(config, _):
""" Validates the SSL configuration (if enabled). """
if config.get('PREFERRED_URL_SCHEME', 'http') != 'https':
return
@@ -276,7 +276,7 @@ def _validate_ssl(config):
-def _validate_ldap(config):
+def _validate_ldap(config, password):
""" Validates the LDAP connection. """
if config.get('AUTHENTICATION_TYPE', 'Database') != 'LDAP':
return
@@ -305,37 +305,47 @@ def _validate_ldap(config):
raise Exception(values.get('desc', 'Unknown error'))
+ # Verify that the superuser exists. If not, raise an exception.
+ base_dn = config.get('LDAP_BASE_DN')
+ user_rdn = config.get('LDAP_USER_RDN', [])
+ uid_attr = config.get('LDAP_UID_ATTR', 'uid')
+ email_attr = config.get('LDAP_EMAIL_ATTR', 'mail')
-def _validate_jwt(config):
+ users = LDAPUsers(ldap_uri, base_dn, admin_dn, admin_passwd, user_rdn, uid_attr, email_attr)
+
+ username = get_authenticated_user().username
+ (result, err_msg) = users.verify_user(username, password)
+ if not result:
+ raise Exception(('Verification of superuser %s failed: %s. \n\nThe user either does not exist ' +
+ 'in the remote authentication system ' +
+ 'OR LDAP auth is misconfigured.') % (username, err_msg))
+
+
+def _validate_jwt(config, password):
""" Validates the JWT authentication system. """
if config.get('AUTHENTICATION_TYPE', 'Database') != 'JWT':
return
verify_endpoint = config.get('JWT_VERIFY_ENDPOINT')
- exists_endpoint = config.get('JWT_EXISTS_ENDPOINT')
issuer = config.get('JWT_AUTH_ISSUER')
if not verify_endpoint:
raise Exception('Missing JWT Verification endpoint')
- if not exists_endpoint:
- raise Exception('Missing JWT Exists endpoint')
-
if not issuer:
raise Exception('Missing JWT Issuer ID')
# Try to instatiate the JWT authentication mechanism. This will raise an exception if
# the key cannot be found.
- users = JWTAuthUsers(exists_endpoint, verify_endpoint, issuer,
- OVERRIDE_CONFIG_DIRECTORY,
- app.config['HTTPCLIENT'])
+ users = JWTAuthUsers(verify_endpoint, issuer, OVERRIDE_CONFIG_DIRECTORY, app.config['HTTPCLIENT'])
# Verify that the superuser exists. If not, raise an exception.
username = get_authenticated_user().username
- result = users.user_exists(username)
+ (result, err_msg) = users.verify_user(username, password)
if not result:
- raise Exception(('Verification of superuser %s failed. The user either does not exist ' +
- 'in the remote authentication system OR JWT auth is misconfigured.') % username)
+ raise Exception(('Verification of superuser %s failed: %s. \n\nThe user either does not ' +
+ 'exist in the remote authentication system ' +
+ 'OR JWT auth is misconfigured.') % (username, err_msg))
_VALIDATORS = {
|