Merge pull request #166 from coreos-inc/nonexistent

Remove `user_exists` endpoint from all auth systems
This commit is contained in:
Jake Moshenko 2015-06-23 17:37:09 -04:00
commit 4aae3b870a
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. """
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)

View file

@ -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)

View file

@ -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>

View file

@ -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);
}
};

View file

@ -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)

View file

@ -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 = {