Remove user_exists endpoint from all auth systems

This commit is contained in:
Joseph Schorr 2015-06-22 18:17:37 -04:00
parent b21a033ef3
commit 07439328a4
6 changed files with 102 additions and 81 deletions

View file

@ -54,10 +54,8 @@ class JWTAuthUsers(object):
""" Delegates authentication to a REST endpoint that returns JWTs. """ """ Delegates authentication to a REST endpoint that returns JWTs. """
PUBLIC_KEY_FILENAME = 'jwt-authn.cert' PUBLIC_KEY_FILENAME = 'jwt-authn.cert'
def __init__(self, exists_url, verify_url, issuer, override_config_dir, http_client, def __init__(self, verify_url, issuer, override_config_dir, http_client, public_key_path=None):
public_key_path=None):
self.verify_url = verify_url self.verify_url = verify_url
self.exists_url = exists_url
self.issuer = issuer self.issuer = issuer
self.client = http_client self.client = http_client
@ -109,13 +107,6 @@ class JWTAuthUsers(object):
# Parse out the username and email. # Parse out the username and email.
return _get_federated_user(payload['sub'], payload['email'], 'jwtauthn', create_new_user) 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): def confirm_existing_user(self, username, password):
db_user = model.get_user(username) db_user = model.get_user(username)
if not db_user: if not db_user:
@ -140,9 +131,6 @@ class DatabaseUsers(object):
def confirm_existing_user(self, username, password): def confirm_existing_user(self, username, password):
return self.verify_user(username, password) return self.verify_user(username, password)
def user_exists(self, username):
return model.get_user(username) is not None
class LDAPConnection(object): class LDAPConnection(object):
def __init__(self, ldap_uri, user_dn, user_pw): def __init__(self, ldap_uri, user_dn, user_pw):
@ -299,10 +287,6 @@ class LDAPUsers(object):
email = found_response[self._email_attr][0] email = found_response[self._email_attr][0]
return _get_federated_user(username, email, 'ldap', create_new_user) 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): 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) users = LDAPUsers(ldap_uri, base_dn, admin_dn, admin_passwd, user_rdn, uid_attr, email_attr)
elif authentication_type == 'JWT': elif authentication_type == 'JWT':
verify_url = app.config.get('JWT_VERIFY_ENDPOINT') verify_url = app.config.get('JWT_VERIFY_ENDPOINT')
exists_url = app.config.get('JWT_EXISTS_ENDPOINT')
issuer = app.config.get('JWT_AUTH_ISSUER') issuer = app.config.get('JWT_AUTH_ISSUER')
users = JWTAuthUsers(exists_url, verify_url, issuer, override_config_dir, users = JWTAuthUsers(verify_url, issuer, override_config_dir, app.config['HTTPCLIENT'])
app.config['HTTPCLIENT'])
else: else:
raise RuntimeError('Unknown authentication type: %s' % authentication_type) raise RuntimeError('Unknown authentication type: %s' % authentication_type)

View file

@ -343,6 +343,10 @@ class SuperUserConfigValidate(ApiResource):
'properties': { 'properties': {
'config': { 'config': {
'type': 'object' '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. # 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(): if not CONFIG_PROVIDER.yaml_exists() or SuperUserPermission().can():
config = request.get_json()['config'] 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) abort(403)

View file

@ -383,20 +383,6 @@
</div> </div>
</td> </td>
</tr> </tr>
<tr>
<td>User Exists Endpoint:</td>
<td>
<span class="config-string-field" binding="config.JWT_EXISTS_ENDPOINT"
pattern="http(s)?://.+"></span>
<div class="help-text">
The URL (starting with http or https) on the JWT authentication server for checking whether a username exists.
</div>
<div class="help-text" style="margin-top: 6px;">
The username will be sent in the <code>Authorization</code> header as Basic Auth, and this endpoint should return <code>200 OK</code> on success (or a <code>4**</code> otherwise).
</div>
</td>
</tr>
<tr> <tr>
<td>Authentication Issuer:</td> <td>Authentication Issuer:</td>
<td> <td>

View file

@ -25,11 +25,11 @@ angular.module("core-config-setup", ['angularFileUpload'])
{'id': 'ldap', 'title': 'LDAP Authentication', 'condition': function(config) { {'id': 'ldap', 'title': 'LDAP Authentication', 'condition': function(config) {
return config.AUTHENTICATION_TYPE == 'LDAP'; return config.AUTHENTICATION_TYPE == 'LDAP';
}}, }, 'password': true},
{'id': 'jwt', 'title': 'JWT Authentication', 'condition': function(config) { {'id': 'jwt', 'title': 'JWT Authentication', 'condition': function(config) {
return config.AUTHENTICATION_TYPE == 'JWT'; return config.AUTHENTICATION_TYPE == 'JWT';
}}, }, 'password': true},
{'id': 'mail', 'title': 'E-mail Support', 'condition': function(config) { {'id': 'mail', 'title': 'E-mail Support', 'condition': function(config) {
return config.FEATURE_MAILING; return config.FEATURE_MAILING;
@ -153,12 +153,17 @@ angular.module("core-config-setup", ['angularFileUpload'])
$scope.savingConfiguration = false; $scope.savingConfiguration = false;
}; };
$scope.validateService = function(serviceInfo) { $scope.validateService = function(serviceInfo, opt_password) {
var params = { var params = {
'service': serviceInfo.service.id '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.status = resp.status ? 'success' : 'error';
serviceInfo.errorMessage = $.trim(resp.reason || ''); serviceInfo.errorMessage = $.trim(resp.reason || '');
}, ApiService.errorDisplay('Could not validate configuration. Please report this error.')); }, ApiService.errorDisplay('Could not validate configuration. Please report this error.'));
@ -175,6 +180,57 @@ angular.module("core-config-setup", ['angularFileUpload'])
}; };
$scope.validateAndSave = function() { $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:' +
'<form style="margin-top: 10px" action="javascript:void(0)">' +
'<input id="validatePassword" class="form-control" type="password" placeholder="Password">' +
'</form>',
"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.savingConfiguration = false;
$scope.validating = $scope.getServices($scope.config); $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) { for (var i = 0; i < $scope.validating.length; ++i) {
var serviceInfo = $scope.validating[i]; var serviceInfo = $scope.validating[i];
$scope.validateService(serviceInfo); $scope.validateService(serviceInfo, opt_password);
} }
}; };

View file

@ -42,15 +42,6 @@ class JWTAuthTestCase(LiveServerTestCase):
data = base64.b64decode(request.headers['Authorization'][len('Basic '):]) data = base64.b64decode(request.headers['Authorization'][len('Basic '):])
return data.split(':', 1) 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']) @jwt_app.route('/user/verify', methods=['GET'])
def verify_user(): def verify_user():
username, password = _get_basic_auth() username, password = _get_basic_auth()
@ -92,7 +83,6 @@ class JWTAuthTestCase(LiveServerTestCase):
self.session = requests.Session() self.session = requests.Session()
self.jwt_auth = JWTAuthUsers( self.jwt_auth = JWTAuthUsers(
self.get_server_url() + '/user/exists',
self.get_server_url() + '/user/verify', self.get_server_url() + '/user/verify',
'authy', '', app.config['HTTPCLIENT'], 'authy', '', app.config['HTTPCLIENT'],
JWTAuthTestCase.public_key.name) JWTAuthTestCase.public_key.name)
@ -101,13 +91,6 @@ class JWTAuthTestCase(LiveServerTestCase):
finished_database_for_testing(self) finished_database_for_testing(self)
self.ctx.__exit__(True, None, None) 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): def test_verify_user(self):
result, error_message = self.jwt_auth.verify_user('invaliduser', 'foobar') result, error_message = self.jwt_auth.verify_user('invaliduser', 'foobar')
self.assertEquals('Invalid username or password', error_message) self.assertEquals('Invalid username or password', error_message)

View file

@ -7,7 +7,7 @@ import OpenSSL
import logging import logging
from fnmatch import fnmatch from fnmatch import fnmatch
from data.users import LDAPConnection, JWTAuthUsers from data.users import LDAPConnection, JWTAuthUsers, LDAPUsers
from flask import Flask from flask import Flask
from flask.ext.mail import Mail, Message from flask.ext.mail import Mail, Message
from data.database import validate_database_url, User from data.database import validate_database_url, User
@ -31,7 +31,7 @@ def get_storage_provider(config):
except TypeError: except TypeError:
raise Exception('Missing required storage configuration parameter(s)') 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. """ """ Attempts to validate the configuration for the given service. """
if not service in _VALIDATORS: if not service in _VALIDATORS:
return { return {
@ -39,7 +39,7 @@ def validate_service_for_config(service, config):
} }
try: try:
_VALIDATORS[service](config) _VALIDATORS[service](config, password)
return { return {
'status': True '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. """ """ Validates connecting to the database. """
try: try:
validate_database_url(config['DB_URI']) validate_database_url(config['DB_URI'])
@ -62,7 +62,7 @@ def _validate_database(config):
raise ex raise ex
def _validate_redis(config): def _validate_redis(config, _):
""" Validates connecting to redis. """ """ Validates connecting to redis. """
redis_config = config.get('BUILDLOGS_REDIS', {}) redis_config = config.get('BUILDLOGS_REDIS', {})
if not 'host' in redis_config: if not 'host' in redis_config:
@ -72,7 +72,7 @@ def _validate_redis(config):
client.ping() client.ping()
def _validate_registry_storage(config): def _validate_registry_storage(config, _):
""" Validates registry storage. """ """ Validates registry storage. """
driver = get_storage_provider(config) driver = get_storage_provider(config)
@ -87,7 +87,7 @@ def _validate_registry_storage(config):
raise Exception('Could not prepare storage: %s' % str(ex)) raise Exception('Could not prepare storage: %s' % str(ex))
def _validate_mailing(config): def _validate_mailing(config, _):
""" Validates sending email. """ """ Validates sending email. """
test_app = Flask("mail-test-app") test_app = Flask("mail-test-app")
test_app.config.update(config) test_app.config.update(config)
@ -103,7 +103,7 @@ def _validate_mailing(config):
test_mail.send(test_msg) test_mail.send(test_msg)
def _validate_gitlab(config): def _validate_gitlab(config, _):
""" Validates the OAuth credentials and API endpoint for a GitLab service. """ """ Validates the OAuth credentials and API endpoint for a GitLab service. """
github_config = config.get('GITLAB_TRIGGER_CONFIG') github_config = config.get('GITLAB_TRIGGER_CONFIG')
if not github_config: if not github_config:
@ -130,7 +130,7 @@ def _validate_gitlab(config):
def _validate_github(config_key): 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): 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) raise Exception('Invalid organization: %s' % org_id)
def _validate_bitbucket(config): def _validate_bitbucket(config, _):
""" Validates the config for BitBucket. """ """ Validates the config for BitBucket. """
trigger_config = config.get('BITBUCKET_TRIGGER_CONFIG') trigger_config = config.get('BITBUCKET_TRIGGER_CONFIG')
if not trigger_config: if not trigger_config:
@ -189,7 +189,7 @@ def _validate_bitbucket(config):
raise Exception('Invaid consumer key or secret') 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. """ """ Validates the Google Login client ID and secret. """
google_login_config = config.get('GOOGLE_LOGIN_CONFIG') google_login_config = config.get('GOOGLE_LOGIN_CONFIG')
if not 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') raise Exception('Invalid client id or client secret')
def _validate_ssl(config): def _validate_ssl(config, _):
""" Validates the SSL configuration (if enabled). """ """ Validates the SSL configuration (if enabled). """
if config.get('PREFERRED_URL_SCHEME', 'http') != 'https': if config.get('PREFERRED_URL_SCHEME', 'http') != 'https':
return return
@ -276,7 +276,7 @@ def _validate_ssl(config):
def _validate_ldap(config): def _validate_ldap(config, password):
""" Validates the LDAP connection. """ """ Validates the LDAP connection. """
if config.get('AUTHENTICATION_TYPE', 'Database') != 'LDAP': if config.get('AUTHENTICATION_TYPE', 'Database') != 'LDAP':
return return
@ -305,37 +305,47 @@ def _validate_ldap(config):
raise Exception(values.get('desc', 'Unknown error')) 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. """ """ Validates the JWT authentication system. """
if config.get('AUTHENTICATION_TYPE', 'Database') != 'JWT': if config.get('AUTHENTICATION_TYPE', 'Database') != 'JWT':
return return
verify_endpoint = config.get('JWT_VERIFY_ENDPOINT') verify_endpoint = config.get('JWT_VERIFY_ENDPOINT')
exists_endpoint = config.get('JWT_EXISTS_ENDPOINT')
issuer = config.get('JWT_AUTH_ISSUER') issuer = config.get('JWT_AUTH_ISSUER')
if not verify_endpoint: if not verify_endpoint:
raise Exception('Missing JWT Verification endpoint') raise Exception('Missing JWT Verification endpoint')
if not exists_endpoint:
raise Exception('Missing JWT Exists endpoint')
if not issuer: if not issuer:
raise Exception('Missing JWT Issuer ID') raise Exception('Missing JWT Issuer ID')
# Try to instatiate the JWT authentication mechanism. This will raise an exception if # Try to instatiate the JWT authentication mechanism. This will raise an exception if
# the key cannot be found. # the key cannot be found.
users = JWTAuthUsers(exists_endpoint, verify_endpoint, issuer, users = JWTAuthUsers(verify_endpoint, issuer, OVERRIDE_CONFIG_DIRECTORY, app.config['HTTPCLIENT'])
OVERRIDE_CONFIG_DIRECTORY,
app.config['HTTPCLIENT'])
# Verify that the superuser exists. If not, raise an exception. # Verify that the superuser exists. If not, raise an exception.
username = get_authenticated_user().username username = get_authenticated_user().username
result = users.user_exists(username) (result, err_msg) = users.verify_user(username, password)
if not result: if not result:
raise Exception(('Verification of superuser %s failed. The user either does not exist ' + raise Exception(('Verification of superuser %s failed: %s. \n\nThe user either does not ' +
'in the remote authentication system OR JWT auth is misconfigured.') % username) 'exist in the remote authentication system ' +
'OR JWT auth is misconfigured.') % (username, err_msg))
_VALIDATORS = { _VALIDATORS = {