Add tests for the new super user config API and make sure both super user API endpoint sets are all guarded against being used in production
This commit is contained in:
parent
575d4c5062
commit
7933bd44fd
4 changed files with 224 additions and 15 deletions
|
@ -4,7 +4,7 @@ import json
|
|||
|
||||
from flask import abort
|
||||
from endpoints.api import (ApiResource, nickname, resource, internal_only, show_if, hide_if,
|
||||
require_fresh_login, request, validate_json_request)
|
||||
require_fresh_login, request, validate_json_request, verify_not_prod)
|
||||
|
||||
from endpoints.common import common_login
|
||||
from app import app, OVERRIDE_CONFIG_YAML_FILENAME, OVERRIDE_CONFIG_DIRECTORY
|
||||
|
@ -21,43 +21,45 @@ import features
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
def database_is_valid():
|
||||
""" Returns whether the database, as configured, is valid. """
|
||||
try:
|
||||
User.select().limit(1)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
def database_has_users():
|
||||
""" Returns whether the database has any users defined. """
|
||||
return bool(list(User.select().limit(1)))
|
||||
|
||||
def config_file_exists():
|
||||
""" Returns whether a configuration file exists. """
|
||||
return os.path.exists(OVERRIDE_CONFIG_YAML_FILENAME)
|
||||
|
||||
|
||||
@resource('/v1/superuser/registrystatus')
|
||||
@internal_only
|
||||
@show_if(features.SUPER_USERS)
|
||||
@hide_if(features.BILLING) # Make sure it is never allowed in prod.
|
||||
class SuperUserRegistryStatus(ApiResource):
|
||||
""" Resource for determining the status of the registry, such as if config exists,
|
||||
if a database is configured, and if it has any defined users.
|
||||
"""
|
||||
@nickname('scRegistryStatus')
|
||||
@verify_not_prod
|
||||
def get(self):
|
||||
""" Returns whether a valid configuration, database and users exist. """
|
||||
current_user = get_authenticated_user()
|
||||
|
||||
return {
|
||||
'dir_exists': os.path.exists(OVERRIDE_CONFIG_DIRECTORY),
|
||||
'file_exists': os.path.exists(OVERRIDE_CONFIG_YAML_FILENAME),
|
||||
'is_testing': app.config['TESTING'],
|
||||
'valid_db': database_is_valid(),
|
||||
'ready': current_user and current_user.username in app.config['SUPER_USERS']
|
||||
'ready': not app.config['TESTING'] and file_exists and bool(app.config['SUPER_USERS'])
|
||||
}
|
||||
|
||||
|
||||
@resource('/v1/superuser/config')
|
||||
@internal_only
|
||||
@show_if(features.SUPER_USERS)
|
||||
@hide_if(features.BILLING) # Make sure it is never allowed in prod.
|
||||
class SuperUserConfig(ApiResource):
|
||||
""" Resource for fetching and updating the current configuration, if any. """
|
||||
schemas = {
|
||||
|
@ -81,6 +83,7 @@ class SuperUserConfig(ApiResource):
|
|||
}
|
||||
|
||||
@require_fresh_login
|
||||
@verify_not_prod
|
||||
@nickname('scGetConfig')
|
||||
def get(self):
|
||||
""" Returns the currently defined configuration, if any. """
|
||||
|
@ -98,12 +101,13 @@ class SuperUserConfig(ApiResource):
|
|||
abort(403)
|
||||
|
||||
@nickname('scUpdateConfig')
|
||||
@verify_not_prod
|
||||
@validate_json_request('UpdateConfig')
|
||||
def put(self):
|
||||
""" Updates the config.yaml file. """
|
||||
# Note: This method is called to set the database configuration before super users exists,
|
||||
# so we also allow it to be called if there is no valid registry configuration setup.
|
||||
if not os.path.exists(OVERRIDE_CONFIG_YAML_FILENAME) or SuperUserPermission().can():
|
||||
if not config_file_exists() or SuperUserPermission().can():
|
||||
config_object = request.get_json()['config']
|
||||
hostname = request.get_json()['hostname']
|
||||
|
||||
|
@ -124,10 +128,10 @@ class SuperUserConfig(ApiResource):
|
|||
@resource('/v1/superuser/config/file/<filename>')
|
||||
@internal_only
|
||||
@show_if(features.SUPER_USERS)
|
||||
@hide_if(features.BILLING) # Make sure it is never allowed in prod.
|
||||
class SuperUserConfigFile(ApiResource):
|
||||
""" Resource for fetching the status of config files and overriding them. """
|
||||
@nickname('scConfigFileExists')
|
||||
@verify_not_prod
|
||||
def get(self, filename):
|
||||
""" Returns whether the configuration file with the given name exists. """
|
||||
if not filename in SSL_FILENAMES:
|
||||
|
@ -141,6 +145,7 @@ class SuperUserConfigFile(ApiResource):
|
|||
abort(403)
|
||||
|
||||
@nickname('scUpdateConfigFile')
|
||||
@verify_not_prod
|
||||
def post(self, filename):
|
||||
""" Updates the configuration file with the given name. """
|
||||
if not filename in SSL_FILENAMES:
|
||||
|
@ -149,7 +154,7 @@ class SuperUserConfigFile(ApiResource):
|
|||
if SuperUserPermission().can():
|
||||
uploaded_file = request.files['file']
|
||||
if not uploaded_file:
|
||||
abort(404)
|
||||
abort(400)
|
||||
|
||||
uploaded_file.save(os.path.join(OVERRIDE_CONFIG_DIRECTORY, filename))
|
||||
return {
|
||||
|
@ -162,7 +167,6 @@ class SuperUserConfigFile(ApiResource):
|
|||
@resource('/v1/superuser/config/createsuperuser')
|
||||
@internal_only
|
||||
@show_if(features.SUPER_USERS)
|
||||
@hide_if(features.BILLING) # Make sure it is never allowed in prod.
|
||||
class SuperUserCreateInitialSuperUser(ApiResource):
|
||||
""" Resource for creating the initial super user. """
|
||||
schemas = {
|
||||
|
@ -193,6 +197,7 @@ class SuperUserCreateInitialSuperUser(ApiResource):
|
|||
}
|
||||
|
||||
@nickname('scCreateInitialSuperuser')
|
||||
@verify_not_prod
|
||||
@validate_json_request('CreateSuperUser')
|
||||
def post(self):
|
||||
""" Creates the initial super user, updates the underlying configuration and
|
||||
|
@ -204,7 +209,7 @@ class SuperUserCreateInitialSuperUser(ApiResource):
|
|||
#
|
||||
# We do this special security check because at the point this method is called, the database
|
||||
# is clean but does not (yet) have any super users for our permissions code to check against.
|
||||
if os.path.exists(OVERRIDE_CONFIG_YAML_FILENAME) and not database_has_users():
|
||||
if config_file_exists() and not database_has_users():
|
||||
data = request.get_json()
|
||||
username = data['username']
|
||||
password = data['password']
|
||||
|
@ -231,7 +236,6 @@ class SuperUserCreateInitialSuperUser(ApiResource):
|
|||
@resource('/v1/superuser/config/validate/<service>')
|
||||
@internal_only
|
||||
@show_if(features.SUPER_USERS)
|
||||
@hide_if(features.BILLING) # Make sure it is never allowed in prod.
|
||||
class SuperUserConfigValidate(ApiResource):
|
||||
""" Resource for validating a block of configuration against an external service. """
|
||||
schemas = {
|
||||
|
@ -251,13 +255,14 @@ class SuperUserConfigValidate(ApiResource):
|
|||
}
|
||||
|
||||
@nickname('scValidateConfig')
|
||||
@verify_not_prod
|
||||
@validate_json_request('ValidateConfig')
|
||||
def post(self, service):
|
||||
""" Validates the given config for the given service. """
|
||||
# Note: This method is called to validate the database configuration before super users exists,
|
||||
# so we also allow it to be called if there is no valid registry configuration setup. Note that
|
||||
# this is also safe since this method does not access any information not given in the request.
|
||||
if not os.path.exists(OVERRIDE_CONFIG_YAML_FILENAME) or SuperUserPermission().can():
|
||||
if not config_file_exists() or SuperUserPermission().can():
|
||||
config = request.get_json()['config']
|
||||
return validate_service_for_config(service, config)
|
||||
|
||||
|
|
Reference in a new issue