Merge pull request #166 from coreos-inc/nonexistent
Remove `user_exists` endpoint from all auth systems
This commit is contained in:
commit
4aae3b870a
6 changed files with 102 additions and 81 deletions
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -383,20 +383,6 @@
|
|||
</div>
|
||||
</td>
|
||||
</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>
|
||||
<td>Authentication Issuer:</td>
|
||||
<td>
|
||||
|
|
|
@ -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:' +
|
||||
'<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.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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 = {
|
||||
|
|
Reference in a new issue