2015-05-14 20:47:38 +00:00
|
|
|
""" Superuser API. """
|
2014-04-10 04:26:55 +00:00
|
|
|
import logging
|
2014-12-23 16:40:51 +00:00
|
|
|
import os
|
2016-03-28 18:52:20 +00:00
|
|
|
import string
|
2017-05-24 18:34:29 +00:00
|
|
|
import socket
|
2014-04-10 04:26:55 +00:00
|
|
|
|
2017-07-18 15:58:42 +00:00
|
|
|
from datetime import datetime, timedelta
|
2014-10-01 17:55:09 +00:00
|
|
|
from random import SystemRandom
|
2016-03-25 22:44:11 +00:00
|
|
|
|
2017-12-22 21:18:58 +00:00
|
|
|
import pathvalidate
|
|
|
|
|
2016-03-25 22:44:11 +00:00
|
|
|
from flask import request, make_response, jsonify
|
2014-04-10 04:26:55 +00:00
|
|
|
|
2015-07-15 21:25:41 +00:00
|
|
|
import features
|
2014-04-10 04:26:55 +00:00
|
|
|
|
2018-03-20 21:03:35 +00:00
|
|
|
from app import app, avatar, superusers, authentication, config_provider
|
2016-03-23 22:16:03 +00:00
|
|
|
from auth import scopes
|
|
|
|
from auth.auth_context import get_authenticated_user
|
|
|
|
from auth.permissions import SuperUserPermission
|
2017-12-22 21:18:58 +00:00
|
|
|
from data.database import ServiceKeyApprovalType
|
2015-07-15 21:25:41 +00:00
|
|
|
from endpoints.api import (ApiResource, nickname, resource, validate_json_request,
|
|
|
|
internal_only, require_scope, show_if, parse_args,
|
2017-12-22 22:26:49 +00:00
|
|
|
query_param, require_fresh_login, path_param, verify_not_prod,
|
|
|
|
page_support, log_action, format_date, truthy_bool,
|
|
|
|
InvalidRequest, NotFound, Unauthorized, InvalidResponse)
|
2017-12-22 21:18:58 +00:00
|
|
|
from endpoints.api.build import get_logs_or_log_url
|
|
|
|
from endpoints.api.superuser_models_pre_oci import (pre_oci_model, ServiceKeyDoesNotExist,
|
|
|
|
ServiceKeyAlreadyApproved,
|
|
|
|
InvalidRepositoryBuildException)
|
2018-05-18 16:54:38 +00:00
|
|
|
from endpoints.api.logs_models_pre_oci import pre_oci_model as log_model
|
2014-10-01 17:55:09 +00:00
|
|
|
from util.useremails import send_confirmation_email, send_recovery_email
|
2017-01-11 23:45:46 +00:00
|
|
|
from util.security.ssl import load_certificate, CertInvalidException
|
|
|
|
from util.config.validator import EXTRA_CA_DIRECTORY
|
2017-02-01 23:17:25 +00:00
|
|
|
from _init import ROOT_DIR
|
2014-04-10 04:26:55 +00:00
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2015-07-15 21:25:41 +00:00
|
|
|
|
2017-07-31 15:25:56 +00:00
|
|
|
def _validate_logs_arguments(start_time, end_time):
|
2017-07-18 15:58:42 +00:00
|
|
|
if start_time:
|
|
|
|
try:
|
|
|
|
start_time = datetime.strptime(start_time + ' UTC', '%m/%d/%Y %Z')
|
|
|
|
except ValueError:
|
|
|
|
start_time = None
|
|
|
|
|
|
|
|
if not start_time:
|
|
|
|
start_time = datetime.today() - timedelta(7) # One week
|
|
|
|
|
|
|
|
if end_time:
|
|
|
|
try:
|
|
|
|
end_time = datetime.strptime(end_time + ' UTC', '%m/%d/%Y %Z')
|
|
|
|
end_time = end_time + timedelta(days=1)
|
|
|
|
except ValueError:
|
|
|
|
end_time = None
|
|
|
|
|
|
|
|
if not end_time:
|
|
|
|
end_time = datetime.today()
|
|
|
|
|
2017-07-31 15:25:56 +00:00
|
|
|
return start_time, end_time
|
2017-07-18 15:58:42 +00:00
|
|
|
|
|
|
|
|
2014-12-23 16:40:51 +00:00
|
|
|
def get_immediate_subdirectories(directory):
|
|
|
|
return [name for name in os.listdir(directory) if os.path.isdir(os.path.join(directory, name))]
|
|
|
|
|
2015-07-15 21:25:41 +00:00
|
|
|
|
2014-12-23 19:01:00 +00:00
|
|
|
def get_services():
|
|
|
|
services = set(get_immediate_subdirectories(app.config['SYSTEM_SERVICES_PATH']))
|
|
|
|
services = services - set(app.config['SYSTEM_SERVICE_BLACKLIST'])
|
|
|
|
return services
|
|
|
|
|
2014-12-23 16:40:51 +00:00
|
|
|
|
|
|
|
@resource('/v1/superuser/systemlogs/<service>')
|
|
|
|
@internal_only
|
|
|
|
@show_if(features.SUPER_USERS)
|
|
|
|
class SuperUserGetLogsForService(ApiResource):
|
|
|
|
""" Resource for fetching the kinds of system logs in the system. """
|
2017-07-18 15:58:42 +00:00
|
|
|
|
2014-12-23 19:25:04 +00:00
|
|
|
@require_fresh_login
|
2015-01-08 17:53:36 +00:00
|
|
|
@verify_not_prod
|
2014-12-23 16:40:51 +00:00
|
|
|
@nickname('getSystemLogs')
|
2015-06-28 08:22:34 +00:00
|
|
|
@require_scope(scopes.SUPERUSER)
|
2014-12-23 16:40:51 +00:00
|
|
|
def get(self, service):
|
|
|
|
""" Returns the logs for the specific service. """
|
|
|
|
if SuperUserPermission().can():
|
2017-07-31 15:25:56 +00:00
|
|
|
if service not in get_services():
|
2017-12-22 22:26:49 +00:00
|
|
|
raise NotFound()
|
2014-12-23 16:40:51 +00:00
|
|
|
|
2015-07-13 09:45:08 +00:00
|
|
|
logs = []
|
2014-12-23 16:40:51 +00:00
|
|
|
try:
|
2015-06-16 00:55:23 +00:00
|
|
|
with open(app.config['SYSTEM_LOGS_FILE'], 'r') as f:
|
2015-07-13 09:45:08 +00:00
|
|
|
logs = [line for line in f if line.find(service + '[') >= 0]
|
2015-07-15 21:25:41 +00:00
|
|
|
except Exception:
|
2014-12-23 16:40:51 +00:00
|
|
|
logger.exception('Cannot read logs')
|
2017-12-22 22:26:49 +00:00
|
|
|
raise InvalidRequest('Cannot read logs')
|
2014-12-23 16:40:51 +00:00
|
|
|
|
|
|
|
return {
|
2017-05-24 18:34:29 +00:00
|
|
|
'instance': socket.gethostname(),
|
2015-07-13 09:45:08 +00:00
|
|
|
'logs': '\n'.join(logs)
|
2014-12-23 16:40:51 +00:00
|
|
|
}
|
|
|
|
|
2017-12-22 22:26:49 +00:00
|
|
|
raise Unauthorized()
|
2014-12-23 16:40:51 +00:00
|
|
|
|
|
|
|
|
|
|
|
@resource('/v1/superuser/systemlogs/')
|
|
|
|
@internal_only
|
|
|
|
@show_if(features.SUPER_USERS)
|
|
|
|
class SuperUserSystemLogServices(ApiResource):
|
|
|
|
""" Resource for fetching the kinds of system logs in the system. """
|
2017-07-18 15:58:42 +00:00
|
|
|
|
2014-12-23 19:25:04 +00:00
|
|
|
@require_fresh_login
|
2015-01-08 17:53:36 +00:00
|
|
|
@verify_not_prod
|
2014-12-23 16:40:51 +00:00
|
|
|
@nickname('listSystemLogServices')
|
2015-06-28 08:22:34 +00:00
|
|
|
@require_scope(scopes.SUPERUSER)
|
2014-12-23 16:40:51 +00:00
|
|
|
def get(self):
|
|
|
|
""" List the system logs for the current system. """
|
|
|
|
if SuperUserPermission().can():
|
|
|
|
return {
|
2017-05-24 18:34:29 +00:00
|
|
|
'instance': socket.gethostname(),
|
2014-12-23 19:01:00 +00:00
|
|
|
'services': list(get_services())
|
2014-12-23 16:40:51 +00:00
|
|
|
}
|
|
|
|
|
2017-12-22 22:26:49 +00:00
|
|
|
raise Unauthorized()
|
2014-12-23 16:40:51 +00:00
|
|
|
|
|
|
|
|
2015-09-04 20:48:32 +00:00
|
|
|
@resource('/v1/superuser/aggregatelogs')
|
|
|
|
@internal_only
|
|
|
|
class SuperUserAggregateLogs(ApiResource):
|
|
|
|
""" Resource for fetching aggregated logs for the current user. """
|
2017-07-18 15:58:42 +00:00
|
|
|
|
2015-09-04 20:48:32 +00:00
|
|
|
@require_fresh_login
|
|
|
|
@verify_not_prod
|
|
|
|
@nickname('listAllAggregateLogs')
|
2016-01-26 21:27:36 +00:00
|
|
|
@parse_args()
|
2015-09-04 20:48:32 +00:00
|
|
|
@query_param('starttime', 'Earliest time from which to get logs. (%m/%d/%Y %Z)', type=str)
|
|
|
|
@query_param('endtime', 'Latest time to which to get logs. (%m/%d/%Y %Z)', type=str)
|
2016-01-26 21:27:36 +00:00
|
|
|
def get(self, parsed_args):
|
2015-09-04 20:48:32 +00:00
|
|
|
""" Returns the aggregated logs for the current system. """
|
|
|
|
if SuperUserPermission().can():
|
2017-12-22 21:18:58 +00:00
|
|
|
(start_time, end_time) = _validate_logs_arguments(parsed_args['starttime'],
|
|
|
|
parsed_args['endtime'])
|
2015-09-04 20:48:32 +00:00
|
|
|
|
2018-05-18 16:54:38 +00:00
|
|
|
# TODO(LogMigrate): Change to a unified log lookup util lib once we're back on LogEntry only.
|
|
|
|
aggregated_logs = log_model.get_aggregated_logs(start_time, end_time)
|
|
|
|
kinds = log_model.get_log_entry_kinds()
|
2017-07-31 15:25:56 +00:00
|
|
|
return {
|
2018-05-18 16:54:38 +00:00
|
|
|
'aggregated': [log.to_dict(kinds, start_time) for log in aggregated_logs]
|
2017-07-31 15:25:56 +00:00
|
|
|
}
|
2015-09-04 20:48:32 +00:00
|
|
|
|
2017-12-22 22:26:49 +00:00
|
|
|
raise Unauthorized()
|
2015-09-04 20:48:32 +00:00
|
|
|
|
2017-07-18 15:58:42 +00:00
|
|
|
LOGS_PER_PAGE = 20
|
|
|
|
|
2014-04-10 04:26:55 +00:00
|
|
|
@resource('/v1/superuser/logs')
|
|
|
|
@internal_only
|
|
|
|
@show_if(features.SUPER_USERS)
|
|
|
|
class SuperUserLogs(ApiResource):
|
|
|
|
""" Resource for fetching all logs in the system. """
|
2017-07-18 15:58:42 +00:00
|
|
|
|
2014-12-23 19:25:04 +00:00
|
|
|
@require_fresh_login
|
2015-01-08 17:53:36 +00:00
|
|
|
@verify_not_prod
|
2014-04-10 04:26:55 +00:00
|
|
|
@nickname('listAllLogs')
|
2016-01-26 21:27:36 +00:00
|
|
|
@parse_args()
|
2015-09-04 20:48:32 +00:00
|
|
|
@query_param('starttime', 'Earliest time from which to get logs (%m/%d/%Y %Z)', type=str)
|
|
|
|
@query_param('endtime', 'Latest time to which to get logs (%m/%d/%Y %Z)', type=str)
|
|
|
|
@query_param('page', 'The page number for the logs', type=int, default=1)
|
2016-01-26 21:27:36 +00:00
|
|
|
@page_support()
|
2015-06-28 08:22:34 +00:00
|
|
|
@require_scope(scopes.SUPERUSER)
|
2016-01-26 21:27:36 +00:00
|
|
|
def get(self, parsed_args, page_token):
|
2014-12-23 16:40:51 +00:00
|
|
|
""" List the usage logs for the current system. """
|
2014-04-10 04:26:55 +00:00
|
|
|
if SuperUserPermission().can():
|
2016-01-26 21:27:36 +00:00
|
|
|
start_time = parsed_args['starttime']
|
|
|
|
end_time = parsed_args['endtime']
|
2017-07-31 15:25:56 +00:00
|
|
|
(start_time, end_time) = _validate_logs_arguments(start_time, end_time)
|
2014-11-24 21:07:38 +00:00
|
|
|
|
2018-05-18 16:54:38 +00:00
|
|
|
# TODO(LogMigrate): Change to a unified log lookup util lib once we're back on LogEntry only.
|
|
|
|
log_page = log_model.get_logs_query(start_time, end_time, page_token=page_token)
|
|
|
|
kinds = log_model.get_log_entry_kinds()
|
2017-07-31 15:25:56 +00:00
|
|
|
return {
|
2017-12-22 21:18:58 +00:00
|
|
|
'start_time': format_date(start_time),
|
|
|
|
'end_time': format_date(end_time),
|
2018-05-18 16:54:38 +00:00
|
|
|
'logs': [log.to_dict(kinds, include_namespace=True) for log in log_page.logs],
|
2017-12-22 21:18:58 +00:00
|
|
|
}, log_page.next_page_token
|
2014-04-10 04:26:55 +00:00
|
|
|
|
2017-12-22 22:26:49 +00:00
|
|
|
raise Unauthorized()
|
2014-04-10 04:26:55 +00:00
|
|
|
|
|
|
|
|
2015-05-11 22:03:25 +00:00
|
|
|
def org_view(org):
|
|
|
|
return {
|
|
|
|
'name': org.username,
|
2015-10-16 19:17:51 +00:00
|
|
|
'email': org.email,
|
2015-05-11 22:03:25 +00:00
|
|
|
'avatar': avatar.get_data_for_org(org),
|
|
|
|
}
|
|
|
|
|
2017-07-18 15:58:42 +00:00
|
|
|
|
2015-06-30 16:42:19 +00:00
|
|
|
def user_view(user, password=None):
|
|
|
|
user_data = {
|
2016-04-01 17:55:29 +00:00
|
|
|
'kind': 'user',
|
|
|
|
'name': user.username,
|
2014-04-10 04:26:55 +00:00
|
|
|
'username': user.username,
|
|
|
|
'email': user.email,
|
|
|
|
'verified': user.verified,
|
2015-03-30 21:55:04 +00:00
|
|
|
'avatar': avatar.get_data_for_user(user),
|
2015-05-11 21:13:42 +00:00
|
|
|
'super_user': superusers.is_superuser(user.username),
|
2015-06-30 16:42:19 +00:00
|
|
|
'enabled': user.enabled,
|
2014-04-10 04:26:55 +00:00
|
|
|
}
|
|
|
|
|
2015-06-30 16:42:19 +00:00
|
|
|
if password is not None:
|
|
|
|
user_data['encrypted_password'] = authentication.encrypt_user_password(password)
|
|
|
|
|
|
|
|
return user_data
|
|
|
|
|
2017-07-18 15:58:42 +00:00
|
|
|
|
2015-06-28 05:27:39 +00:00
|
|
|
@resource('/v1/superuser/changelog/')
|
2014-10-28 20:33:13 +00:00
|
|
|
@internal_only
|
|
|
|
@show_if(features.SUPER_USERS)
|
2015-06-28 05:27:39 +00:00
|
|
|
class ChangeLog(ApiResource):
|
|
|
|
""" Resource for returning the change log for enterprise customers. """
|
2017-07-18 15:58:42 +00:00
|
|
|
|
2014-10-28 20:33:13 +00:00
|
|
|
@require_fresh_login
|
2015-01-08 17:53:36 +00:00
|
|
|
@verify_not_prod
|
2015-06-28 05:27:39 +00:00
|
|
|
@nickname('getChangeLog')
|
2015-06-28 08:22:34 +00:00
|
|
|
@require_scope(scopes.SUPERUSER)
|
2014-10-28 20:33:13 +00:00
|
|
|
def get(self):
|
2015-06-28 05:27:39 +00:00
|
|
|
""" Returns the change log for this installation. """
|
2014-10-28 20:33:13 +00:00
|
|
|
if SuperUserPermission().can():
|
2017-02-01 23:17:25 +00:00
|
|
|
with open(os.path.join(ROOT_DIR, 'CHANGELOG.md'), 'r') as f:
|
2015-06-28 05:27:39 +00:00
|
|
|
return {
|
|
|
|
'log': f.read()
|
|
|
|
}
|
2014-10-28 20:33:13 +00:00
|
|
|
|
2017-12-22 22:26:49 +00:00
|
|
|
raise Unauthorized()
|
2014-10-28 20:33:13 +00:00
|
|
|
|
|
|
|
|
2015-05-11 22:03:25 +00:00
|
|
|
@resource('/v1/superuser/organizations/')
|
|
|
|
@internal_only
|
|
|
|
@show_if(features.SUPER_USERS)
|
|
|
|
class SuperUserOrganizationList(ApiResource):
|
|
|
|
""" Resource for listing organizations in the system. """
|
2017-07-18 15:58:42 +00:00
|
|
|
|
2015-05-11 22:03:25 +00:00
|
|
|
@require_fresh_login
|
|
|
|
@verify_not_prod
|
|
|
|
@nickname('listAllOrganizations')
|
2015-06-28 08:22:34 +00:00
|
|
|
@require_scope(scopes.SUPERUSER)
|
2015-05-11 22:03:25 +00:00
|
|
|
def get(self):
|
|
|
|
""" Returns a list of all organizations in the system. """
|
|
|
|
if SuperUserPermission().can():
|
|
|
|
return {
|
2017-07-31 15:25:56 +00:00
|
|
|
'organizations': [org.to_dict() for org in pre_oci_model.get_organizations()]
|
2015-05-11 22:03:25 +00:00
|
|
|
}
|
|
|
|
|
2017-12-22 22:26:49 +00:00
|
|
|
raise Unauthorized()
|
2015-05-11 22:03:25 +00:00
|
|
|
|
|
|
|
|
2014-04-10 04:26:55 +00:00
|
|
|
@resource('/v1/superuser/users/')
|
|
|
|
@show_if(features.SUPER_USERS)
|
|
|
|
class SuperUserList(ApiResource):
|
|
|
|
""" Resource for listing users in the system. """
|
2014-10-01 17:55:09 +00:00
|
|
|
schemas = {
|
|
|
|
'CreateInstallUser': {
|
|
|
|
'id': 'CreateInstallUser',
|
|
|
|
'description': 'Data for creating a user',
|
2016-09-08 16:24:47 +00:00
|
|
|
'required': ['username'],
|
2014-10-01 17:55:09 +00:00
|
|
|
'properties': {
|
|
|
|
'username': {
|
|
|
|
'type': 'string',
|
|
|
|
'description': 'The username of the user being created'
|
|
|
|
},
|
|
|
|
|
|
|
|
'email': {
|
|
|
|
'type': 'string',
|
|
|
|
'description': 'The email address of the user being created'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@require_fresh_login
|
2015-01-08 17:53:36 +00:00
|
|
|
@verify_not_prod
|
2014-04-10 04:26:55 +00:00
|
|
|
@nickname('listAllUsers')
|
2017-12-22 21:45:49 +00:00
|
|
|
@parse_args()
|
|
|
|
@query_param('disabled', 'If false, only enabled users will be returned.', type=truthy_bool,
|
|
|
|
default=True)
|
2015-06-28 08:22:34 +00:00
|
|
|
@require_scope(scopes.SUPERUSER)
|
2017-12-22 21:45:49 +00:00
|
|
|
def get(self, parsed_args):
|
2014-04-10 04:26:55 +00:00
|
|
|
""" Returns a list of all users in the system. """
|
|
|
|
if SuperUserPermission().can():
|
2017-12-22 21:45:49 +00:00
|
|
|
users = pre_oci_model.get_active_users(disabled=parsed_args['disabled'])
|
2014-04-10 04:26:55 +00:00
|
|
|
return {
|
2017-07-31 15:25:56 +00:00
|
|
|
'users': [user.to_dict() for user in users]
|
2014-04-10 04:26:55 +00:00
|
|
|
}
|
|
|
|
|
2017-12-22 22:26:49 +00:00
|
|
|
raise Unauthorized()
|
2014-04-10 04:26:55 +00:00
|
|
|
|
2014-10-01 17:55:09 +00:00
|
|
|
@require_fresh_login
|
2015-01-08 17:53:36 +00:00
|
|
|
@verify_not_prod
|
2014-10-01 17:55:09 +00:00
|
|
|
@nickname('createInstallUser')
|
|
|
|
@validate_json_request('CreateInstallUser')
|
2015-06-28 08:22:34 +00:00
|
|
|
@require_scope(scopes.SUPERUSER)
|
2014-10-01 17:55:09 +00:00
|
|
|
def post(self):
|
|
|
|
""" Creates a new user. """
|
2015-10-16 19:14:49 +00:00
|
|
|
# Ensure that we are using database auth.
|
|
|
|
if app.config['AUTHENTICATION_TYPE'] != 'Database':
|
2017-12-22 22:26:49 +00:00
|
|
|
raise InvalidRequest('Cannot create a user in a non-database auth system')
|
2015-10-16 19:14:49 +00:00
|
|
|
|
2014-10-01 17:55:09 +00:00
|
|
|
user_information = request.get_json()
|
|
|
|
if SuperUserPermission().can():
|
|
|
|
# Generate a temporary password for the user.
|
|
|
|
random = SystemRandom()
|
2015-07-15 21:25:41 +00:00
|
|
|
password = ''.join([random.choice(string.ascii_uppercase + string.digits) for _ in range(32)])
|
2014-10-01 17:55:09 +00:00
|
|
|
|
|
|
|
# Create the user.
|
2016-09-08 16:24:47 +00:00
|
|
|
username = user_information['username']
|
|
|
|
email = user_information.get('email')
|
2017-07-31 15:25:56 +00:00
|
|
|
install_user, confirmation_code = pre_oci_model.create_install_user(username, password, email)
|
2014-10-01 17:55:09 +00:00
|
|
|
if features.MAILING:
|
2017-07-31 15:25:56 +00:00
|
|
|
send_confirmation_email(install_user.username, install_user.email, confirmation_code)
|
2014-10-01 17:55:09 +00:00
|
|
|
|
|
|
|
return {
|
|
|
|
'username': username,
|
|
|
|
'email': email,
|
2015-06-30 16:42:19 +00:00
|
|
|
'password': password,
|
|
|
|
'encrypted_password': authentication.encrypt_user_password(password),
|
2014-10-01 17:55:09 +00:00
|
|
|
}
|
|
|
|
|
2017-12-22 22:26:49 +00:00
|
|
|
raise Unauthorized()
|
2014-10-01 17:55:09 +00:00
|
|
|
|
|
|
|
|
|
|
|
@resource('/v1/superusers/users/<username>/sendrecovery')
|
|
|
|
@internal_only
|
|
|
|
@show_if(features.SUPER_USERS)
|
|
|
|
@show_if(features.MAILING)
|
|
|
|
class SuperUserSendRecoveryEmail(ApiResource):
|
|
|
|
""" Resource for sending a recovery user on behalf of a user. """
|
2017-07-18 15:58:42 +00:00
|
|
|
|
2014-10-01 17:55:09 +00:00
|
|
|
@require_fresh_login
|
2015-01-08 17:53:36 +00:00
|
|
|
@verify_not_prod
|
2014-10-01 17:55:09 +00:00
|
|
|
@nickname('sendInstallUserRecoveryEmail')
|
2015-06-28 08:22:34 +00:00
|
|
|
@require_scope(scopes.SUPERUSER)
|
2014-10-01 17:55:09 +00:00
|
|
|
def post(self, username):
|
2015-10-16 19:14:49 +00:00
|
|
|
# Ensure that we are using database auth.
|
|
|
|
if app.config['AUTHENTICATION_TYPE'] != 'Database':
|
2017-12-22 22:26:49 +00:00
|
|
|
raise InvalidRequest('Cannot send a recovery e-mail for non-database auth')
|
2015-10-16 19:14:49 +00:00
|
|
|
|
2014-10-01 17:55:09 +00:00
|
|
|
if SuperUserPermission().can():
|
2017-07-31 15:25:56 +00:00
|
|
|
user = pre_oci_model.get_nonrobot_user(username)
|
|
|
|
if user is None:
|
2017-12-22 22:26:49 +00:00
|
|
|
raise NotFound()
|
2014-10-01 17:55:09 +00:00
|
|
|
|
2015-01-20 17:43:11 +00:00
|
|
|
if superusers.is_superuser(username):
|
2017-12-22 22:26:49 +00:00
|
|
|
raise InvalidRequest('Cannot send a recovery email for a superuser')
|
2014-10-01 17:55:09 +00:00
|
|
|
|
2017-07-31 15:25:56 +00:00
|
|
|
code = pre_oci_model.create_reset_password_email_code(user.email)
|
|
|
|
send_recovery_email(user.email, code)
|
2014-10-01 17:55:09 +00:00
|
|
|
return {
|
|
|
|
'email': user.email
|
|
|
|
}
|
|
|
|
|
2017-12-22 22:26:49 +00:00
|
|
|
raise Unauthorized()
|
2014-10-01 17:55:09 +00:00
|
|
|
|
|
|
|
|
2014-04-10 04:26:55 +00:00
|
|
|
@resource('/v1/superuser/users/<username>')
|
2014-08-19 23:05:28 +00:00
|
|
|
@path_param('username', 'The username of the user being managed')
|
2014-04-10 04:26:55 +00:00
|
|
|
@internal_only
|
|
|
|
@show_if(features.SUPER_USERS)
|
|
|
|
class SuperUserManagement(ApiResource):
|
|
|
|
""" Resource for managing users in the system. """
|
|
|
|
schemas = {
|
|
|
|
'UpdateUser': {
|
|
|
|
'id': 'UpdateUser',
|
|
|
|
'type': 'object',
|
|
|
|
'description': 'Description of updates for a user',
|
|
|
|
'properties': {
|
|
|
|
'password': {
|
|
|
|
'type': 'string',
|
|
|
|
'description': 'The new password for the user',
|
|
|
|
},
|
|
|
|
'email': {
|
|
|
|
'type': 'string',
|
|
|
|
'description': 'The new e-mail address for the user',
|
2015-05-11 22:03:25 +00:00
|
|
|
},
|
|
|
|
'enabled': {
|
|
|
|
'type': 'boolean',
|
|
|
|
'description': 'Whether the user is enabled'
|
2014-04-10 04:26:55 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2014-10-01 17:55:09 +00:00
|
|
|
@require_fresh_login
|
2015-01-08 17:53:36 +00:00
|
|
|
@verify_not_prod
|
2014-04-10 04:26:55 +00:00
|
|
|
@nickname('getInstallUser')
|
2015-06-28 08:22:34 +00:00
|
|
|
@require_scope(scopes.SUPERUSER)
|
2014-04-10 04:26:55 +00:00
|
|
|
def get(self, username):
|
|
|
|
""" Returns information about the specified user. """
|
|
|
|
if SuperUserPermission().can():
|
2017-07-31 15:25:56 +00:00
|
|
|
user = pre_oci_model.get_nonrobot_user(username)
|
|
|
|
if user is None:
|
2017-12-22 22:26:49 +00:00
|
|
|
raise NotFound()
|
2014-11-24 21:07:38 +00:00
|
|
|
|
2017-07-31 15:25:56 +00:00
|
|
|
return user.to_dict()
|
2014-04-10 04:26:55 +00:00
|
|
|
|
2017-12-22 22:26:49 +00:00
|
|
|
raise Unauthorized()
|
2014-04-10 04:26:55 +00:00
|
|
|
|
2014-10-01 17:55:09 +00:00
|
|
|
@require_fresh_login
|
2015-01-08 17:53:36 +00:00
|
|
|
@verify_not_prod
|
2014-04-10 04:26:55 +00:00
|
|
|
@nickname('deleteInstallUser')
|
2015-06-28 08:22:34 +00:00
|
|
|
@require_scope(scopes.SUPERUSER)
|
2014-04-10 04:26:55 +00:00
|
|
|
def delete(self, username):
|
|
|
|
""" Deletes the specified user. """
|
|
|
|
if SuperUserPermission().can():
|
2017-07-31 15:25:56 +00:00
|
|
|
user = pre_oci_model.get_nonrobot_user(username)
|
|
|
|
if user is None:
|
2017-12-22 22:26:49 +00:00
|
|
|
raise NotFound()
|
2014-04-10 04:26:55 +00:00
|
|
|
|
2015-01-20 17:43:11 +00:00
|
|
|
if superusers.is_superuser(username):
|
2017-12-22 22:26:49 +00:00
|
|
|
raise InvalidRequest('Cannot delete a superuser')
|
2014-04-10 04:26:55 +00:00
|
|
|
|
2018-02-23 21:45:16 +00:00
|
|
|
pre_oci_model.mark_user_for_deletion(username)
|
2016-12-06 21:26:28 +00:00
|
|
|
return '', 204
|
2014-04-10 04:26:55 +00:00
|
|
|
|
2017-12-22 22:26:49 +00:00
|
|
|
raise Unauthorized()
|
2014-04-10 04:26:55 +00:00
|
|
|
|
2014-10-01 17:55:09 +00:00
|
|
|
@require_fresh_login
|
2015-01-08 17:53:36 +00:00
|
|
|
@verify_not_prod
|
2014-04-10 04:26:55 +00:00
|
|
|
@nickname('changeInstallUser')
|
|
|
|
@validate_json_request('UpdateUser')
|
2015-06-28 08:22:34 +00:00
|
|
|
@require_scope(scopes.SUPERUSER)
|
2014-04-10 04:26:55 +00:00
|
|
|
def put(self, username):
|
|
|
|
""" Updates information about the specified user. """
|
|
|
|
if SuperUserPermission().can():
|
2017-07-31 15:25:56 +00:00
|
|
|
user = pre_oci_model.get_nonrobot_user(username)
|
|
|
|
if user is None:
|
2017-12-22 22:26:49 +00:00
|
|
|
raise NotFound()
|
2014-04-10 04:26:55 +00:00
|
|
|
|
2015-07-15 21:25:41 +00:00
|
|
|
if superusers.is_superuser(username):
|
2017-12-22 22:26:49 +00:00
|
|
|
raise InvalidRequest('Cannot update a superuser')
|
2014-04-10 04:26:55 +00:00
|
|
|
|
2015-07-15 21:25:41 +00:00
|
|
|
user_data = request.get_json()
|
|
|
|
if 'password' in user_data:
|
2015-10-16 19:14:49 +00:00
|
|
|
# Ensure that we are using database auth.
|
|
|
|
if app.config['AUTHENTICATION_TYPE'] != 'Database':
|
2017-12-22 22:26:49 +00:00
|
|
|
raise InvalidRequest('Cannot change password in non-database auth')
|
2015-10-16 19:14:49 +00:00
|
|
|
|
2017-07-31 15:25:56 +00:00
|
|
|
pre_oci_model.change_password(username, user_data['password'])
|
2014-04-10 04:26:55 +00:00
|
|
|
|
2015-07-15 21:25:41 +00:00
|
|
|
if 'email' in user_data:
|
2015-10-16 19:14:49 +00:00
|
|
|
# Ensure that we are using database auth.
|
|
|
|
if app.config['AUTHENTICATION_TYPE'] != 'Database':
|
2017-12-22 22:26:49 +00:00
|
|
|
raise InvalidRequest('Cannot change e-mail in non-database auth')
|
2015-10-16 19:14:49 +00:00
|
|
|
|
2017-07-31 15:25:56 +00:00
|
|
|
pre_oci_model.update_email(username, user_data['email'], auto_verify=True)
|
2014-11-24 21:07:38 +00:00
|
|
|
|
2015-07-15 21:25:41 +00:00
|
|
|
if 'enabled' in user_data:
|
|
|
|
# Disable/enable the user.
|
2017-07-31 15:25:56 +00:00
|
|
|
pre_oci_model.update_enabled(username, bool(user_data['enabled']))
|
2015-05-11 21:13:42 +00:00
|
|
|
|
2015-10-16 19:38:06 +00:00
|
|
|
if 'superuser' in user_data:
|
|
|
|
config_object = config_provider.get_config()
|
|
|
|
superusers_set = set(config_object['SUPER_USERS'])
|
|
|
|
|
|
|
|
if user_data['superuser']:
|
|
|
|
superusers_set.add(username)
|
|
|
|
elif username in superusers_set:
|
|
|
|
superusers_set.remove(username)
|
|
|
|
|
|
|
|
config_object['SUPER_USERS'] = list(superusers_set)
|
|
|
|
config_provider.save_config(config_object)
|
|
|
|
|
2017-07-31 15:25:56 +00:00
|
|
|
return_value = user.to_dict()
|
|
|
|
if user_data.get('password') is not None:
|
2017-12-22 21:18:58 +00:00
|
|
|
password = user_data.get('password')
|
|
|
|
return_value['encrypted_password'] = authentication.encrypt_user_password(password)
|
2017-07-31 15:25:56 +00:00
|
|
|
|
|
|
|
return return_value
|
2014-04-10 04:26:55 +00:00
|
|
|
|
2017-12-22 22:26:49 +00:00
|
|
|
raise Unauthorized()
|
2015-05-11 22:03:25 +00:00
|
|
|
|
|
|
|
|
2016-06-07 22:12:11 +00:00
|
|
|
@resource('/v1/superuser/takeownership/<namespace>')
|
|
|
|
@path_param('namespace', 'The namespace of the user or organization being managed')
|
|
|
|
@internal_only
|
|
|
|
@show_if(features.SUPER_USERS)
|
|
|
|
class SuperUserTakeOwnership(ApiResource):
|
|
|
|
""" Resource for a superuser to take ownership of a namespace. """
|
2017-07-18 15:58:42 +00:00
|
|
|
|
2016-06-07 22:12:11 +00:00
|
|
|
@require_fresh_login
|
|
|
|
@verify_not_prod
|
|
|
|
@nickname('takeOwnership')
|
|
|
|
@require_scope(scopes.SUPERUSER)
|
|
|
|
def post(self, namespace):
|
|
|
|
""" Takes ownership of the specified organization or user. """
|
|
|
|
if SuperUserPermission().can():
|
|
|
|
# Disallow for superusers.
|
|
|
|
if superusers.is_superuser(namespace):
|
2017-12-22 22:26:49 +00:00
|
|
|
raise InvalidRequest('Cannot take ownership of a superuser')
|
2016-06-07 22:12:11 +00:00
|
|
|
|
|
|
|
authed_user = get_authenticated_user()
|
2017-07-31 15:25:56 +00:00
|
|
|
entity_id, was_user = pre_oci_model.take_ownership(namespace, authed_user)
|
|
|
|
if entity_id is None:
|
2017-12-22 22:26:49 +00:00
|
|
|
raise NotFound()
|
2016-06-07 22:12:11 +00:00
|
|
|
|
|
|
|
# Log the change.
|
|
|
|
log_metadata = {
|
2017-07-31 15:25:56 +00:00
|
|
|
'entity_id': entity_id,
|
2016-06-07 22:12:11 +00:00
|
|
|
'namespace': namespace,
|
|
|
|
'was_user': was_user,
|
|
|
|
'superuser': authed_user.username,
|
|
|
|
}
|
|
|
|
|
|
|
|
log_action('take_ownership', authed_user.username, log_metadata)
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
'namespace': namespace
|
|
|
|
})
|
|
|
|
|
2017-12-22 22:26:49 +00:00
|
|
|
raise Unauthorized()
|
2016-06-07 22:12:11 +00:00
|
|
|
|
|
|
|
|
2015-05-11 22:03:25 +00:00
|
|
|
@resource('/v1/superuser/organizations/<name>')
|
|
|
|
@path_param('name', 'The name of the organizaton being managed')
|
|
|
|
@show_if(features.SUPER_USERS)
|
|
|
|
class SuperUserOrganizationManagement(ApiResource):
|
|
|
|
""" Resource for managing organizations in the system. """
|
|
|
|
schemas = {
|
|
|
|
'UpdateOrg': {
|
|
|
|
'id': 'UpdateOrg',
|
|
|
|
'type': 'object',
|
|
|
|
'description': 'Description of updates for an organization',
|
|
|
|
'properties': {
|
|
|
|
'name': {
|
|
|
|
'type': 'string',
|
|
|
|
'description': 'The new name for the organization',
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
@require_fresh_login
|
|
|
|
@verify_not_prod
|
|
|
|
@nickname('deleteOrganization')
|
2015-06-28 08:22:34 +00:00
|
|
|
@require_scope(scopes.SUPERUSER)
|
2015-05-11 22:03:25 +00:00
|
|
|
def delete(self, name):
|
|
|
|
""" Deletes the specified organization. """
|
|
|
|
if SuperUserPermission().can():
|
2018-02-23 21:45:16 +00:00
|
|
|
pre_oci_model.mark_organization_for_deletion(name)
|
2016-12-06 21:26:28 +00:00
|
|
|
return '', 204
|
2015-05-11 22:03:25 +00:00
|
|
|
|
2017-12-22 22:26:49 +00:00
|
|
|
raise Unauthorized()
|
2015-05-11 22:03:25 +00:00
|
|
|
|
|
|
|
@require_fresh_login
|
|
|
|
@verify_not_prod
|
|
|
|
@nickname('changeOrganization')
|
|
|
|
@validate_json_request('UpdateOrg')
|
2015-06-28 08:22:34 +00:00
|
|
|
@require_scope(scopes.SUPERUSER)
|
2015-05-11 22:03:25 +00:00
|
|
|
def put(self, name):
|
|
|
|
""" Updates information about the specified user. """
|
|
|
|
if SuperUserPermission().can():
|
2015-07-15 21:25:41 +00:00
|
|
|
org_data = request.get_json()
|
2017-07-31 15:25:56 +00:00
|
|
|
old_name = org_data['name'] if 'name' in org_data else None
|
|
|
|
org = pre_oci_model.change_organization_name(name, old_name)
|
|
|
|
return org.to_dict()
|
2015-05-11 22:03:25 +00:00
|
|
|
|
2017-12-22 22:26:49 +00:00
|
|
|
raise Unauthorized()
|
2016-03-23 22:16:03 +00:00
|
|
|
|
2016-03-25 22:44:11 +00:00
|
|
|
|
2016-03-30 20:48:06 +00:00
|
|
|
def key_view(key):
|
|
|
|
return {
|
|
|
|
'name': key.name,
|
|
|
|
'kid': key.kid,
|
|
|
|
'service': key.service,
|
|
|
|
'jwk': key.jwk,
|
|
|
|
'metadata': key.metadata,
|
|
|
|
'created_date': key.created_date,
|
|
|
|
'expiration_date': key.expiration_date,
|
2016-04-12 23:17:19 +00:00
|
|
|
'rotation_duration': key.rotation_duration,
|
2016-03-30 20:48:06 +00:00
|
|
|
'approval': approval_view(key.approval) if key.approval is not None else None,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def approval_view(approval):
|
|
|
|
return {
|
2016-04-29 14:14:50 +00:00
|
|
|
'approver': user_view(approval.approver) if approval.approver else None,
|
2016-03-30 20:48:06 +00:00
|
|
|
'approval_type': approval.approval_type,
|
|
|
|
'approved_date': approval.approved_date,
|
|
|
|
'notes': approval.notes,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-03-25 22:44:11 +00:00
|
|
|
@resource('/v1/superuser/keys')
|
|
|
|
@show_if(features.SUPER_USERS)
|
|
|
|
class SuperUserServiceKeyManagement(ApiResource):
|
|
|
|
""" Resource for managing service keys."""
|
|
|
|
schemas = {
|
|
|
|
'CreateServiceKey': {
|
2016-04-01 17:55:29 +00:00
|
|
|
'id': 'CreateServiceKey',
|
2016-03-25 22:44:11 +00:00
|
|
|
'type': 'object',
|
|
|
|
'description': 'Description of creation of a service key',
|
|
|
|
'required': ['service', 'expiration'],
|
|
|
|
'properties': {
|
|
|
|
'service': {
|
|
|
|
'type': 'string',
|
|
|
|
'description': 'The service authenticating with this key',
|
|
|
|
},
|
|
|
|
'name': {
|
|
|
|
'type': 'string',
|
|
|
|
'description': 'The friendly name of a service key',
|
|
|
|
},
|
|
|
|
'metadata': {
|
|
|
|
'type': 'object',
|
|
|
|
'description': 'The key/value pairs of this key\'s metadata',
|
|
|
|
},
|
2016-04-01 17:55:29 +00:00
|
|
|
'notes': {
|
|
|
|
'type': 'string',
|
|
|
|
'description': 'If specified, the extra notes for the key',
|
|
|
|
},
|
2016-03-25 22:44:11 +00:00
|
|
|
'expiration': {
|
|
|
|
'description': 'The expiration date as a unix timestamp',
|
2016-04-01 17:55:29 +00:00
|
|
|
'anyOf': [{'type': 'number'}, {'type': 'null'}],
|
2016-03-25 22:44:11 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
@verify_not_prod
|
2016-04-08 21:48:44 +00:00
|
|
|
@nickname('listServiceKeys')
|
2016-03-25 22:44:11 +00:00
|
|
|
@require_scope(scopes.SUPERUSER)
|
2016-03-28 18:52:20 +00:00
|
|
|
def get(self):
|
2016-03-25 22:44:11 +00:00
|
|
|
if SuperUserPermission().can():
|
2017-07-31 15:25:56 +00:00
|
|
|
keys = pre_oci_model.list_all_service_keys()
|
2016-04-01 17:55:29 +00:00
|
|
|
|
|
|
|
return jsonify({
|
2017-07-31 15:25:56 +00:00
|
|
|
'keys': [key.to_dict() for key in keys],
|
2016-04-01 17:55:29 +00:00
|
|
|
})
|
|
|
|
|
2017-12-22 22:26:49 +00:00
|
|
|
raise Unauthorized()
|
2016-03-25 22:44:11 +00:00
|
|
|
|
2016-04-01 17:55:29 +00:00
|
|
|
@require_fresh_login
|
2016-03-25 22:44:11 +00:00
|
|
|
@verify_not_prod
|
|
|
|
@nickname('createServiceKey')
|
|
|
|
@require_scope(scopes.SUPERUSER)
|
2016-04-01 17:55:29 +00:00
|
|
|
@validate_json_request('CreateServiceKey')
|
2016-03-28 18:52:20 +00:00
|
|
|
def post(self):
|
2016-03-25 22:44:11 +00:00
|
|
|
if SuperUserPermission().can():
|
|
|
|
body = request.get_json()
|
|
|
|
|
2016-04-01 17:55:29 +00:00
|
|
|
# Ensure we have a valid expiration date if specified.
|
2016-03-28 18:52:20 +00:00
|
|
|
expiration_date = body.get('expiration', None)
|
2016-03-30 17:20:35 +00:00
|
|
|
if expiration_date is not None:
|
2016-03-25 22:44:11 +00:00
|
|
|
try:
|
|
|
|
expiration_date = datetime.utcfromtimestamp(float(expiration_date))
|
2017-12-22 22:26:49 +00:00
|
|
|
except ValueError as ve:
|
|
|
|
raise InvalidRequest('Invalid expiration date: %s' % ve)
|
2016-03-25 22:44:11 +00:00
|
|
|
|
2016-04-12 23:17:19 +00:00
|
|
|
if expiration_date <= datetime.now():
|
2017-12-22 22:26:49 +00:00
|
|
|
raise InvalidRequest('Expiration date cannot be in the past')
|
2016-04-12 23:17:19 +00:00
|
|
|
|
2016-04-01 17:55:29 +00:00
|
|
|
# Create the metadata for the key.
|
2016-03-28 18:52:20 +00:00
|
|
|
user = get_authenticated_user()
|
2016-03-25 22:44:11 +00:00
|
|
|
metadata = body.get('metadata', {})
|
|
|
|
metadata.update({
|
2016-04-01 17:55:29 +00:00
|
|
|
'created_by': 'Quay Superuser Panel',
|
2016-03-25 22:44:11 +00:00
|
|
|
'creator': user.username,
|
2016-03-28 23:00:00 +00:00
|
|
|
'ip': request.remote_addr,
|
2016-03-25 22:44:11 +00:00
|
|
|
})
|
|
|
|
|
2016-04-07 00:03:04 +00:00
|
|
|
# Generate a key with a private key that we *never save*.
|
2017-07-31 15:25:56 +00:00
|
|
|
(private_key, key_id) = pre_oci_model.generate_service_key(body['service'], expiration_date,
|
|
|
|
metadata=metadata,
|
|
|
|
name=body.get('name', ''))
|
2016-04-01 17:55:29 +00:00
|
|
|
# Auto-approve the service key.
|
2017-07-31 15:25:56 +00:00
|
|
|
pre_oci_model.approve_service_key(key_id, user, ServiceKeyApprovalType.SUPERUSER,
|
|
|
|
notes=body.get('notes', ''))
|
2016-04-01 17:55:29 +00:00
|
|
|
|
|
|
|
# Log the creation and auto-approval of the service key.
|
|
|
|
key_log_metadata = {
|
2017-07-31 15:25:56 +00:00
|
|
|
'kid': key_id,
|
2016-04-01 17:55:29 +00:00
|
|
|
'preshared': True,
|
|
|
|
'service': body['service'],
|
|
|
|
'name': body.get('name', ''),
|
|
|
|
'expiration_date': expiration_date,
|
|
|
|
'auto_approved': True,
|
|
|
|
}
|
|
|
|
|
2016-04-05 19:27:45 +00:00
|
|
|
log_action('service_key_create', None, key_log_metadata)
|
|
|
|
log_action('service_key_approve', None, key_log_metadata)
|
2016-04-01 17:55:29 +00:00
|
|
|
|
|
|
|
return jsonify({
|
2017-07-31 15:25:56 +00:00
|
|
|
'kid': key_id,
|
2016-04-01 17:55:29 +00:00
|
|
|
'name': body.get('name', ''),
|
2016-05-02 19:29:31 +00:00
|
|
|
'service': body['service'],
|
2016-04-01 17:55:29 +00:00
|
|
|
'public_key': private_key.publickey().exportKey('PEM'),
|
|
|
|
'private_key': private_key.exportKey('PEM'),
|
|
|
|
})
|
2016-03-25 22:44:11 +00:00
|
|
|
|
2017-12-22 22:26:49 +00:00
|
|
|
raise Unauthorized()
|
2016-03-25 22:44:11 +00:00
|
|
|
|
|
|
|
|
|
|
|
@resource('/v1/superuser/keys/<kid>')
|
2016-03-23 22:16:03 +00:00
|
|
|
@path_param('kid', 'The unique identifier for a service key')
|
|
|
|
@show_if(features.SUPER_USERS)
|
2016-04-05 19:27:45 +00:00
|
|
|
class SuperUserServiceKey(ApiResource):
|
2016-03-23 22:16:03 +00:00
|
|
|
""" Resource for managing service keys. """
|
|
|
|
schemas = {
|
2016-03-25 22:44:11 +00:00
|
|
|
'PutServiceKey': {
|
|
|
|
'id': 'PutServiceKey',
|
2016-03-23 22:16:03 +00:00
|
|
|
'type': 'object',
|
2016-03-25 22:44:11 +00:00
|
|
|
'description': 'Description of updates for a service key',
|
2016-03-23 22:16:03 +00:00
|
|
|
'properties': {
|
2016-03-25 22:44:11 +00:00
|
|
|
'name': {
|
2016-03-23 22:16:03 +00:00
|
|
|
'type': 'string',
|
2016-03-25 22:44:11 +00:00
|
|
|
'description': 'The friendly name of a service key',
|
|
|
|
},
|
|
|
|
'metadata': {
|
|
|
|
'type': 'object',
|
|
|
|
'description': 'The key/value pairs of this key\'s metadata',
|
|
|
|
},
|
|
|
|
'expiration': {
|
|
|
|
'description': 'The expiration date as a unix timestamp',
|
2016-04-01 17:55:29 +00:00
|
|
|
'anyOf': [{'type': 'number'}, {'type': 'null'}],
|
2016-03-23 22:16:03 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2016-04-05 19:27:45 +00:00
|
|
|
@verify_not_prod
|
|
|
|
@nickname('getServiceKey')
|
|
|
|
@require_scope(scopes.SUPERUSER)
|
|
|
|
def get(self, kid):
|
|
|
|
if SuperUserPermission().can():
|
|
|
|
try:
|
2017-07-31 15:25:56 +00:00
|
|
|
key = pre_oci_model.get_service_key(kid, approved_only=False, alive_only=False)
|
|
|
|
return jsonify(key.to_dict())
|
|
|
|
except ServiceKeyDoesNotExist:
|
2017-12-22 22:26:49 +00:00
|
|
|
raise NotFound()
|
2016-04-05 19:27:45 +00:00
|
|
|
|
2017-12-22 22:26:49 +00:00
|
|
|
raise Unauthorized()
|
2016-04-05 19:27:45 +00:00
|
|
|
|
2016-04-01 17:55:29 +00:00
|
|
|
@require_fresh_login
|
2016-03-25 22:44:11 +00:00
|
|
|
@verify_not_prod
|
2016-04-01 17:55:29 +00:00
|
|
|
@nickname('updateServiceKey')
|
2016-03-25 22:44:11 +00:00
|
|
|
@require_scope(scopes.SUPERUSER)
|
|
|
|
@validate_json_request('PutServiceKey')
|
|
|
|
def put(self, kid):
|
|
|
|
if SuperUserPermission().can():
|
|
|
|
body = request.get_json()
|
2016-04-01 17:55:29 +00:00
|
|
|
try:
|
2017-07-31 15:25:56 +00:00
|
|
|
key = pre_oci_model.get_service_key(kid, approved_only=False, alive_only=False)
|
|
|
|
except ServiceKeyDoesNotExist:
|
2017-12-22 22:26:49 +00:00
|
|
|
raise NotFound()
|
2016-03-25 22:44:11 +00:00
|
|
|
|
2016-04-01 17:55:29 +00:00
|
|
|
key_log_metadata = {
|
|
|
|
'kid': key.kid,
|
|
|
|
'service': key.service,
|
|
|
|
'name': body.get('name', key.name),
|
|
|
|
'expiration_date': key.expiration_date,
|
|
|
|
}
|
|
|
|
|
|
|
|
if 'expiration' in body:
|
|
|
|
expiration_date = body['expiration']
|
|
|
|
if expiration_date is not None and expiration_date != '':
|
|
|
|
try:
|
|
|
|
expiration_date = datetime.utcfromtimestamp(float(expiration_date))
|
2017-12-22 22:26:49 +00:00
|
|
|
except ValueError as ve:
|
|
|
|
raise InvalidRequest('Invalid expiration date: %s' % ve)
|
2016-03-25 22:44:11 +00:00
|
|
|
|
2016-04-12 23:17:19 +00:00
|
|
|
if expiration_date <= datetime.now():
|
2017-12-22 22:26:49 +00:00
|
|
|
raise InvalidRequest('Cannot have an expiration date in the past')
|
2016-04-12 23:17:19 +00:00
|
|
|
|
2016-04-01 17:55:29 +00:00
|
|
|
key_log_metadata.update({
|
|
|
|
'old_expiration_date': key.expiration_date,
|
|
|
|
'expiration_date': expiration_date,
|
|
|
|
})
|
|
|
|
|
2016-04-05 19:27:45 +00:00
|
|
|
log_action('service_key_extend', None, key_log_metadata)
|
2017-07-31 15:25:56 +00:00
|
|
|
pre_oci_model.set_key_expiration(kid, expiration_date)
|
2016-04-01 17:55:29 +00:00
|
|
|
|
|
|
|
if 'name' in body or 'metadata' in body:
|
2017-07-31 15:25:56 +00:00
|
|
|
pre_oci_model.update_service_key(kid, body.get('name'), body.get('metadata'))
|
2016-04-05 19:27:45 +00:00
|
|
|
log_action('service_key_modify', None, key_log_metadata)
|
2016-04-01 17:55:29 +00:00
|
|
|
|
2017-07-31 15:25:56 +00:00
|
|
|
updated_key = pre_oci_model.get_service_key(kid, approved_only=False, alive_only=False)
|
|
|
|
return jsonify(updated_key.to_dict())
|
2016-04-01 17:55:29 +00:00
|
|
|
|
2017-12-22 22:26:49 +00:00
|
|
|
raise Unauthorized()
|
2016-04-01 17:55:29 +00:00
|
|
|
|
|
|
|
@require_fresh_login
|
|
|
|
@verify_not_prod
|
|
|
|
@nickname('deleteServiceKey')
|
|
|
|
@require_scope(scopes.SUPERUSER)
|
|
|
|
def delete(self, kid):
|
|
|
|
if SuperUserPermission().can():
|
2016-04-05 19:27:45 +00:00
|
|
|
try:
|
2017-07-31 15:25:56 +00:00
|
|
|
key = pre_oci_model.delete_service_key(kid)
|
|
|
|
except ServiceKeyDoesNotExist:
|
2017-12-22 22:26:49 +00:00
|
|
|
raise NotFound()
|
2016-04-01 17:55:29 +00:00
|
|
|
|
|
|
|
key_log_metadata = {
|
|
|
|
'kid': kid,
|
|
|
|
'service': key.service,
|
|
|
|
'name': key.name,
|
|
|
|
'created_date': key.created_date,
|
|
|
|
'expiration_date': key.expiration_date,
|
|
|
|
}
|
|
|
|
|
2016-04-05 19:27:45 +00:00
|
|
|
log_action('service_key_delete', None, key_log_metadata)
|
|
|
|
return make_response('', 204)
|
2016-03-25 22:44:11 +00:00
|
|
|
|
2017-12-22 22:26:49 +00:00
|
|
|
raise Unauthorized()
|
2016-03-25 22:44:11 +00:00
|
|
|
|
|
|
|
|
|
|
|
@resource('/v1/superuser/approvedkeys/<kid>')
|
|
|
|
@path_param('kid', 'The unique identifier for a service key')
|
|
|
|
@show_if(features.SUPER_USERS)
|
|
|
|
class SuperUserServiceKeyApproval(ApiResource):
|
|
|
|
""" Resource for approving service keys. """
|
|
|
|
|
2016-04-01 17:55:29 +00:00
|
|
|
schemas = {
|
|
|
|
'ApproveServiceKey': {
|
|
|
|
'id': 'ApproveServiceKey',
|
|
|
|
'type': 'object',
|
|
|
|
'description': 'Information for approving service keys',
|
|
|
|
'properties': {
|
|
|
|
'notes': {
|
|
|
|
'type': 'string',
|
|
|
|
'description': 'Optional approval notes',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
@require_fresh_login
|
2016-03-23 22:16:03 +00:00
|
|
|
@verify_not_prod
|
|
|
|
@nickname('approveServiceKey')
|
|
|
|
@require_scope(scopes.SUPERUSER)
|
2016-04-05 19:27:45 +00:00
|
|
|
@validate_json_request('ApproveServiceKey')
|
|
|
|
def post(self, kid):
|
2016-03-23 22:16:03 +00:00
|
|
|
if SuperUserPermission().can():
|
2016-04-01 17:55:29 +00:00
|
|
|
notes = request.get_json().get('notes', '')
|
2016-03-23 22:16:03 +00:00
|
|
|
approver = get_authenticated_user()
|
|
|
|
try:
|
2017-07-31 15:25:56 +00:00
|
|
|
key = pre_oci_model.approve_service_key(kid, approver, ServiceKeyApprovalType.SUPERUSER,
|
|
|
|
notes=notes)
|
2016-04-05 19:27:45 +00:00
|
|
|
|
|
|
|
# Log the approval of the service key.
|
|
|
|
key_log_metadata = {
|
|
|
|
'kid': kid,
|
|
|
|
'service': key.service,
|
|
|
|
'name': key.name,
|
|
|
|
'expiration_date': key.expiration_date,
|
|
|
|
}
|
|
|
|
|
|
|
|
log_action('service_key_approve', None, key_log_metadata)
|
2017-07-31 15:25:56 +00:00
|
|
|
except ServiceKeyDoesNotExist:
|
2017-12-22 22:26:49 +00:00
|
|
|
raise NotFound()
|
2017-07-31 15:25:56 +00:00
|
|
|
except ServiceKeyAlreadyApproved:
|
2016-03-23 22:16:03 +00:00
|
|
|
pass
|
|
|
|
|
2016-04-01 17:55:29 +00:00
|
|
|
return make_response('', 201)
|
2016-03-25 22:44:11 +00:00
|
|
|
|
2017-12-22 22:26:49 +00:00
|
|
|
raise Unauthorized()
|
2016-10-11 19:16:28 +00:00
|
|
|
|
|
|
|
|
2017-01-11 23:45:46 +00:00
|
|
|
@resource('/v1/superuser/customcerts')
|
|
|
|
@internal_only
|
|
|
|
@show_if(features.SUPER_USERS)
|
|
|
|
class SuperUserCustomCertificates(ApiResource):
|
|
|
|
""" Resource for managing custom certificates. """
|
2017-07-18 15:58:42 +00:00
|
|
|
|
2017-01-11 23:45:46 +00:00
|
|
|
@nickname('getCustomCertificates')
|
|
|
|
@require_fresh_login
|
|
|
|
@require_scope(scopes.SUPERUSER)
|
|
|
|
@verify_not_prod
|
|
|
|
def get(self):
|
|
|
|
if SuperUserPermission().can():
|
|
|
|
has_extra_certs_path = config_provider.volume_file_exists(EXTRA_CA_DIRECTORY)
|
|
|
|
extra_certs_found = config_provider.list_volume_directory(EXTRA_CA_DIRECTORY)
|
|
|
|
if extra_certs_found is None:
|
|
|
|
return {
|
|
|
|
'status': 'file' if has_extra_certs_path else 'none',
|
|
|
|
}
|
|
|
|
|
|
|
|
cert_views = []
|
|
|
|
for extra_cert_path in extra_certs_found:
|
|
|
|
try:
|
2017-05-17 12:12:09 +00:00
|
|
|
cert_full_path = config_provider.get_volume_path(EXTRA_CA_DIRECTORY, extra_cert_path)
|
2017-01-11 23:45:46 +00:00
|
|
|
with config_provider.get_volume_file(cert_full_path) as f:
|
|
|
|
certificate = load_certificate(f.read())
|
|
|
|
cert_views.append({
|
|
|
|
'path': extra_cert_path,
|
|
|
|
'names': list(certificate.names),
|
|
|
|
'expired': certificate.expired,
|
|
|
|
})
|
|
|
|
except CertInvalidException as cie:
|
|
|
|
cert_views.append({
|
|
|
|
'path': extra_cert_path,
|
|
|
|
'error': cie.message,
|
|
|
|
})
|
|
|
|
except IOError as ioe:
|
|
|
|
cert_views.append({
|
|
|
|
'path': extra_cert_path,
|
|
|
|
'error': ioe.message,
|
|
|
|
})
|
|
|
|
|
|
|
|
return {
|
|
|
|
'status': 'directory',
|
|
|
|
'certs': cert_views,
|
|
|
|
}
|
|
|
|
|
2017-12-22 22:26:49 +00:00
|
|
|
raise Unauthorized()
|
2017-01-11 23:45:46 +00:00
|
|
|
|
|
|
|
|
|
|
|
@resource('/v1/superuser/customcerts/<certpath>')
|
|
|
|
@internal_only
|
|
|
|
@show_if(features.SUPER_USERS)
|
|
|
|
class SuperUserCustomCertificate(ApiResource):
|
|
|
|
""" Resource for managing a custom certificate. """
|
2017-07-18 15:58:42 +00:00
|
|
|
|
2017-01-11 23:45:46 +00:00
|
|
|
@nickname('uploadCustomCertificate')
|
|
|
|
@require_fresh_login
|
|
|
|
@require_scope(scopes.SUPERUSER)
|
|
|
|
@verify_not_prod
|
|
|
|
def post(self, certpath):
|
|
|
|
if SuperUserPermission().can():
|
|
|
|
uploaded_file = request.files['file']
|
|
|
|
if not uploaded_file:
|
2017-12-22 22:26:49 +00:00
|
|
|
raise InvalidRequest('Missing certificate file')
|
2017-01-11 23:45:46 +00:00
|
|
|
|
2017-03-24 21:00:51 +00:00
|
|
|
# Save the certificate.
|
2017-01-11 23:45:46 +00:00
|
|
|
certpath = pathvalidate.sanitize_filename(certpath)
|
2017-03-24 21:00:51 +00:00
|
|
|
if not certpath.endswith('.crt'):
|
2017-12-22 22:26:49 +00:00
|
|
|
raise InvalidRequest('Invalid certificate file: must have suffix `.crt`')
|
2017-03-24 21:00:51 +00:00
|
|
|
|
2017-03-29 20:06:15 +00:00
|
|
|
logger.debug('Saving custom certificate %s', certpath)
|
2017-07-18 15:58:42 +00:00
|
|
|
cert_full_path = config_provider.get_volume_path(EXTRA_CA_DIRECTORY, certpath)
|
2018-05-10 13:44:18 +00:00
|
|
|
config_provider.save_volume_file(uploaded_file, cert_full_path)
|
2017-03-29 20:06:15 +00:00
|
|
|
logger.debug('Saved custom certificate %s', certpath)
|
2017-03-24 21:00:51 +00:00
|
|
|
|
|
|
|
# Validate the certificate.
|
|
|
|
try:
|
2017-03-29 20:06:15 +00:00
|
|
|
logger.debug('Loading custom certificate %s', certpath)
|
2017-03-24 21:00:51 +00:00
|
|
|
with config_provider.get_volume_file(cert_full_path) as f:
|
|
|
|
load_certificate(f.read())
|
|
|
|
except CertInvalidException:
|
2017-03-29 20:06:15 +00:00
|
|
|
logger.exception('Got certificate invalid error for cert %s', certpath)
|
|
|
|
return '', 204
|
2017-03-24 21:00:51 +00:00
|
|
|
except IOError:
|
2017-03-29 20:06:15 +00:00
|
|
|
logger.exception('Got IO error for cert %s', certpath)
|
|
|
|
return '', 204
|
|
|
|
|
|
|
|
# Call the update script to install the certificate immediately.
|
|
|
|
if not app.config['TESTING']:
|
|
|
|
logger.debug('Calling certs_install.sh')
|
|
|
|
if os.system('/conf/init/certs_install.sh') != 0:
|
2017-12-22 22:26:49 +00:00
|
|
|
raise Exception('Could not install certificates')
|
2017-03-29 20:06:15 +00:00
|
|
|
|
|
|
|
logger.debug('certs_install.sh completed')
|
2017-03-24 21:00:51 +00:00
|
|
|
|
2017-01-11 23:45:46 +00:00
|
|
|
return '', 204
|
|
|
|
|
2017-12-22 22:26:49 +00:00
|
|
|
raise Unauthorized()
|
2017-01-11 23:45:46 +00:00
|
|
|
|
|
|
|
@nickname('deleteCustomCertificate')
|
|
|
|
@require_fresh_login
|
|
|
|
@require_scope(scopes.SUPERUSER)
|
|
|
|
@verify_not_prod
|
|
|
|
def delete(self, certpath):
|
|
|
|
if SuperUserPermission().can():
|
2017-05-17 12:12:09 +00:00
|
|
|
cert_full_path = config_provider.get_volume_path(EXTRA_CA_DIRECTORY, certpath)
|
2017-01-11 23:45:46 +00:00
|
|
|
config_provider.remove_volume_file(cert_full_path)
|
|
|
|
return '', 204
|
|
|
|
|
2017-12-22 22:26:49 +00:00
|
|
|
raise Unauthorized()
|
2017-01-11 23:45:46 +00:00
|
|
|
|
|
|
|
|
2017-01-24 17:15:26 +00:00
|
|
|
@resource('/v1/superuser/<build_uuid>/logs')
|
|
|
|
@path_param('build_uuid', 'The UUID of the build')
|
|
|
|
@show_if(features.SUPER_USERS)
|
|
|
|
class SuperUserRepositoryBuildLogs(ApiResource):
|
|
|
|
""" Resource for loading repository build logs for the superuser. """
|
2017-07-18 15:58:42 +00:00
|
|
|
|
2017-01-24 17:15:26 +00:00
|
|
|
@require_fresh_login
|
|
|
|
@verify_not_prod
|
|
|
|
@nickname('getRepoBuildLogsSuperUser')
|
|
|
|
@require_scope(scopes.SUPERUSER)
|
|
|
|
def get(self, build_uuid):
|
|
|
|
""" Return the build logs for the build specified by the build uuid. """
|
2017-12-22 22:26:49 +00:00
|
|
|
if SuperUserPermission().can():
|
|
|
|
try:
|
|
|
|
repo_build = pre_oci_model.get_repository_build(build_uuid)
|
|
|
|
return get_logs_or_log_url(repo_build)
|
|
|
|
except InvalidRepositoryBuildException as e:
|
|
|
|
raise InvalidResponse(e.message)
|
|
|
|
|
|
|
|
raise Unauthorized()
|
2017-01-24 17:15:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
@resource('/v1/superuser/<build_uuid>/status')
|
|
|
|
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
|
|
|
@path_param('build_uuid', 'The UUID of the build')
|
|
|
|
@show_if(features.SUPER_USERS)
|
|
|
|
class SuperUserRepositoryBuildStatus(ApiResource):
|
|
|
|
""" Resource for dealing with repository build status. """
|
2017-07-18 15:58:42 +00:00
|
|
|
|
2017-01-24 17:15:26 +00:00
|
|
|
@require_fresh_login
|
|
|
|
@verify_not_prod
|
|
|
|
@nickname('getRepoBuildStatusSuperUser')
|
|
|
|
@require_scope(scopes.SUPERUSER)
|
|
|
|
def get(self, build_uuid):
|
|
|
|
""" Return the status for the builds specified by the build uuids. """
|
2017-12-22 22:26:49 +00:00
|
|
|
if SuperUserPermission().can():
|
|
|
|
try:
|
|
|
|
build = pre_oci_model.get_repository_build(build_uuid)
|
|
|
|
except InvalidRepositoryBuildException as e:
|
|
|
|
raise InvalidResponse(e.message)
|
|
|
|
return build.to_dict()
|
|
|
|
|
|
|
|
raise Unauthorized()
|
2017-01-24 17:15:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
@resource('/v1/superuser/<build_uuid>/build')
|
|
|
|
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
|
|
|
@path_param('build_uuid', 'The UUID of the build')
|
|
|
|
@show_if(features.SUPER_USERS)
|
|
|
|
class SuperUserRepositoryBuildResource(ApiResource):
|
|
|
|
""" Resource for dealing with repository builds as a super user. """
|
2017-07-18 15:58:42 +00:00
|
|
|
|
2017-01-24 17:15:26 +00:00
|
|
|
@require_fresh_login
|
|
|
|
@verify_not_prod
|
|
|
|
@nickname('getRepoBuildSuperUser')
|
|
|
|
@require_scope(scopes.SUPERUSER)
|
|
|
|
def get(self, build_uuid):
|
|
|
|
""" Returns information about a build. """
|
2017-12-22 22:26:49 +00:00
|
|
|
if SuperUserPermission().can():
|
|
|
|
try:
|
|
|
|
build = pre_oci_model.get_repository_build(build_uuid)
|
|
|
|
except InvalidRepositoryBuildException:
|
|
|
|
raise NotFound()
|
2017-01-24 17:15:26 +00:00
|
|
|
|
2017-12-22 22:26:49 +00:00
|
|
|
return build.to_dict()
|
2017-01-24 17:15:26 +00:00
|
|
|
|
2017-12-22 22:26:49 +00:00
|
|
|
raise Unauthorized()
|