diff --git a/.gitignore b/.gitignore index 0bc08bcca..e1b88b2cf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,11 @@ __pycache__/ *.pyc +.coverage .idea/ .eggs/ .pytest_cache/ build/ +coverage/ dist/ docs/_build/ docs/magpie.esgf.rst @@ -17,6 +19,7 @@ env/ magpieenv/ magpie/api_docs/ magpie/api_test/ +magpie/ui/swagger-ui/magpie-rest-api.json magpie/bin/ magpie/include/ magpie/lib/ diff --git a/Dockerfile b/Dockerfile index 35ffe91c4..c852ed34a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ FROM ubuntu:16.04 -MAINTAINER Francois-Xavier Derue +MAINTAINER Francis Charette-Migneault RUN apt-get update && apt-get install -y \ build-essential \ diff --git a/Makefile b/Makefile index e845bc435..e332e1ceb 100644 --- a/Makefile +++ b/Makefile @@ -45,7 +45,7 @@ clean-pyc: clean-test: rm -fr .tox/ rm -f .coverage - rm -fr htmlcov/ + rm -fr coverage/ lint: flake8 magpie tests @@ -59,8 +59,8 @@ test-all: coverage: coverage run --source magpie setup.py test coverage report -m - coverage html - $(BROWSER) htmlcov/index.html + coverage html -d coverage + $(BROWSER) coverage/index.html migrate: alembic upgrade head diff --git a/README.rst b/README.rst index 0de8b0dd9..f19605eac 100644 --- a/README.rst +++ b/README.rst @@ -6,6 +6,12 @@ Magpie (the smart-bird) Magpie is service for AuthN/AuthZ accessible via a `RestAPI`_ implemented with the Pyramid web framework. It allows you to manage User/Group/Resource/permission with a postgres database. Behind the scene, it uses `Ziggurat-Foundations`_ and `Authomatic`_. +REST API Documentation +====================== + +The documentation is auto-generated and served under `{HOSTNAME}/magpie/api/` using Swagger-UI with tag `latest`. +For convenience, older API versions are also provided. + Build package ============= diff --git a/bin/gunicorn b/bin/gunicorn index 1c356aff1..64b69ed5f 100755 --- a/bin/gunicorn +++ b/bin/gunicorn @@ -1,6 +1,5 @@ import sys import gunicorn.app.wsgiapp -import sys if __name__ == '__main__': diff --git a/docs/sync.py b/docs/sync.py index c5ff7c2dc..4f7810f71 100644 --- a/docs/sync.py +++ b/docs/sync.py @@ -5,7 +5,7 @@ """ from subprocess import check_call -from urllib.parse import urljoin +from six.moves.urllib.parse import urljoin from shlex import split import argparse diff --git a/magpie/__init__.py b/magpie/__init__.py index f22563490..d70bed5b3 100644 --- a/magpie/__init__.py +++ b/magpie/__init__.py @@ -2,8 +2,12 @@ import os import sys -this_dir = os.path.abspath(os.path.dirname(__file__)) -sys.path.insert(0, this_dir) +MAGPIE_MODULE_DIR = os.path.abspath(os.path.dirname(__file__)) +MAGPIE_ROOT = os.path.dirname(MAGPIE_MODULE_DIR) +sys.path.insert(0, MAGPIE_MODULE_DIR) + +MAGPIE_PROVIDERS_CONFIG_PATH = '{}/providers.cfg'.format(MAGPIE_ROOT) +MAGPIE_INI_FILE_PATH = '{}/magpie.ini'.format(MAGPIE_MODULE_DIR) ADMIN_USER = os.getenv('ADMIN_USER', 'admin') ADMIN_GROUP = os.getenv('ADMIN_GROUP', 'administrators') @@ -12,6 +16,8 @@ USER_GROUP = os.getenv('USER_GROUP', 'users') ANONYMOUS_USER = os.getenv('ANONYMOUS_USER', 'anonymous') +ANONYMOUS_GROUP = ANONYMOUS_USER +ANONYMOUS_PASSWORD = ANONYMOUS_USER ADMIN_PERM = 'admin' #ADMIN_PERM = NO_PERMISSION_REQUIRED @@ -29,7 +35,7 @@ def includeme(config): config.include('cornice_swagger') config.include('pyramid_chameleon') config.include('pyramid_mako') - config.include('definitions') - config.include('api') - config.include('db') - config.include('ui') + config.include('magpie.definitions') + config.include('magpie.api') + config.include('magpie.db') + config.include('magpie.ui') diff --git a/magpie/__meta__.py b/magpie/__meta__.py index cca2bac7b..27eba4487 100644 --- a/magpie/__meta__.py +++ b/magpie/__meta__.py @@ -2,7 +2,9 @@ General meta information on the magpie package. """ - -__version__ = '0.6.2' +__version__ = '0.6.3' __author__ = "Francois-Xavier Derue, Francis Charette-Migneault" +__maintainer__ = "Francis Charette-Migneault" __email__ = 'francis.charette-migneault@crim.ca' +__url__ = 'https://github.com/Ouranosinc/Magpie' +__description__ = "Magpie is a service for AuthN and AuthZ based on Ziggurat-Foundations" diff --git a/magpie/adapter/__init__.py b/magpie/adapter/__init__.py index f3f1ebf30..6afdd780d 100644 --- a/magpie/adapter/__init__.py +++ b/magpie/adapter/__init__.py @@ -9,10 +9,9 @@ import logging logger = logging.getLogger(__name__) - class MagpieAdapter(AdapterInterface): - def servicestore_factory(self, registry, database=None): + def servicestore_factory(self, registry, database=None, headers=None): return MagpieServiceStore(registry=registry) def owssecurity_factory(self, registry): diff --git a/magpie/alembic/versions/20671b28c538_change_all_linking_k.py b/magpie/alembic/versions/20671b28c538_change_all_linking_k.py index 17114fd7c..2f08716c3 100644 --- a/magpie/alembic/versions/20671b28c538_change_all_linking_k.py +++ b/magpie/alembic/versions/20671b28c538_change_all_linking_k.py @@ -13,7 +13,7 @@ from alembic import op from alembic.context import get_context -from definitions.sqlalchemy_definitions import * +from magpie.definitions.sqlalchemy_definitions import * def upgrade(): diff --git a/magpie/alembic/versions/5e7b5346c330_remove_obsolete_personal_groups.py b/magpie/alembic/versions/5e7b5346c330_remove_obsolete_personal_groups.py index 500c215ff..2b2e67d14 100644 --- a/magpie/alembic/versions/5e7b5346c330_remove_obsolete_personal_groups.py +++ b/magpie/alembic/versions/5e7b5346c330_remove_obsolete_personal_groups.py @@ -19,7 +19,7 @@ from sqlalchemy.dialects.postgresql.base import PGDialect from sqlalchemy.orm import sessionmaker from magpie import models, ANONYMOUS_USER, ADMIN_GROUP, USER_GROUP -from definitions.ziggurat_definitions import * +from magpie.definitions.ziggurat_definitions import * Session = sessionmaker() diff --git a/magpie/alembic/versions/a395ef9d3fe6_reference_root_service.py b/magpie/alembic/versions/a395ef9d3fe6_reference_root_service.py index 26ce0645b..00b11a2ca 100644 --- a/magpie/alembic/versions/a395ef9d3fe6_reference_root_service.py +++ b/magpie/alembic/versions/a395ef9d3fe6_reference_root_service.py @@ -15,7 +15,7 @@ from alembic import op from alembic.context import get_context -from definitions.sqlalchemy_definitions import * +from magpie.definitions.sqlalchemy_definitions import * from magpie import models from magpie.api.management.resource.resource_utils import get_resource_root_service diff --git a/magpie/alembic/versions/ae1a3c8c7860_tranfer_group_users_admins_users.py b/magpie/alembic/versions/ae1a3c8c7860_tranfer_group_users_admins_users.py index 6eb621faa..78b2f2e76 100644 --- a/magpie/alembic/versions/ae1a3c8c7860_tranfer_group_users_admins_users.py +++ b/magpie/alembic/versions/ae1a3c8c7860_tranfer_group_users_admins_users.py @@ -15,9 +15,9 @@ from alembic import op from alembic.context import get_context -from definitions.sqlalchemy_definitions import * +from magpie.definitions.sqlalchemy_definitions import * from magpie import models, ANONYMOUS_USER -from definitions.ziggurat_definitions import * +from magpie.definitions.ziggurat_definitions import * Session = sessionmaker() diff --git a/magpie/api/__init__.py b/magpie/api/__init__.py index 4d7f5e2e3..e43fe8427 100644 --- a/magpie/api/__init__.py +++ b/magpie/api/__init__.py @@ -6,10 +6,10 @@ def includeme(config): logger.info('Adding api routes ...') # Add all the admin ui routes - config.include('api.esgf') - config.include('api.home') - config.include('api.login') - config.include('api.management') + config.include('magpie.api.esgf') + config.include('magpie.api.home') + config.include('magpie.api.login') + config.include('magpie.api.management') config.add_route('version', '/version') config.scan() diff --git a/magpie/api/api_except.py b/magpie/api/api_except.py index 044de2664..76507773a 100644 --- a/magpie/api/api_except.py +++ b/magpie/api/api_except.py @@ -1,6 +1,7 @@ from pyramid.httpexceptions import * from sys import exc_info import types +import six # control variables to avoid infinite recursion in case of # major programming error to avoid application hanging @@ -11,7 +12,8 @@ def verify_param(param, paramCompare=None, httpError=HTTPNotAcceptable, httpKWArgs=None, msgOnFail="", content=None, contentType='application/json', notNone=False, notEmpty=False, notIn=False, notEqual=False, - isNone=False, isEmpty=False, isIn=False, isEqual=False, ofType=None, withParam=True): + isNone=False, isEmpty=False, isIn=False, isEqual=False, ofType=None, + withParam=True, paramName=None): """ Evaluate various parameter combinations given the requested flags. Given a failing verification, directly raises the specified `httpError`. @@ -19,6 +21,7 @@ def verify_param(param, paramCompare=None, httpError=HTTPNotAcceptable, httpKWAr Exceptions are generated using the standard output method. :param param: (bool) parameter value to evaluate + :param paramName: (str) name of the tested parameter returned in response if specified for debugging purposes :param paramCompare: other value(s) to test against, can be an iterable (single value resolved as iterable unless None) to test for None type, use `isNone`/`notNone` flags instead or `paramCompare`=[None] @@ -96,9 +99,11 @@ def verify_param(param, paramCompare=None, httpError=HTTPNotAcceptable, httpKWAr status = status or (not type(param) == ofType) if status: if withParam: - content[u'param'] = repr(param) + content[u'param'] = {u'value': str(param) if type(param) in six.string_types else repr(param)} + if paramName is not None: + content[u'param'][u'name'] = str(paramName) if paramCompare is not None: - content[u'paramCompare'] = repr(paramCompare) + content[u'param'][u'compare'] = repr(paramCompare) raise_http(httpError, httpKWArgs=httpKWArgs, detail=msgOnFail, content=content, contentType=contentType) @@ -329,7 +334,7 @@ def generate_response_http_format(httpClass, httpKWArgs, jsonContent, outputType try: # directly output json if asked with 'application/json' if outputType == 'application/json': - httpResponse = httpClass(body=jsonContent, content_type='application/json', **httpKWArgs) + httpResponse = httpClass(body=jsonContent, content_type='application/json; charset=UTF-8', **httpKWArgs) # otherwise json is contained within the html section elif outputType == 'text/html': @@ -363,4 +368,4 @@ def isclass(obj): :param obj: object to evaluate for class type :return: (bool) indicating if `object` is a class """ - return isinstance(obj, (type, types.ClassType)) + return isinstance(obj, (type, six.class_types)) diff --git a/magpie/api/api_generic.py b/magpie/api/api_generic.py new file mode 100644 index 000000000..24c1b61a3 --- /dev/null +++ b/magpie/api/api_generic.py @@ -0,0 +1,53 @@ +from magpie.definitions.pyramid_definitions import * +from magpie.api.api_except import * +from magpie.api.api_rest_schemas import * +from magpie import __meta__, db + + +@VersionAPI.get(tags=[APITag], api_security=SecurityEveryoneAPI, response_schemas=Version_GET_responses) +@view_config(route_name='version', request_method='GET', permission=NO_PERMISSION_REQUIRED) +def get_version(request): + """ + Version information of the API. + """ + return valid_http(httpSuccess=HTTPOk, + content={u'version': __meta__.__version__, u'db_version': db.get_database_revision(request.db)}, + detail=Version_GET_OkResponseSchema.description, contentType='application/json') + + +@notfound_view_config() +def not_found(request): + content = get_request_info(request, default_msg=NotFoundResponseSchema.description) + return raise_http(nothrow=True, httpError=HTTPNotFound, contentType='application/json', + detail=content['detail'], content=content) + + +@exception_view_config() +def internal_server_error(request): + content = get_request_info(request, default_msg=InternalServerErrorResponseSchema.description) + return raise_http(nothrow=True, httpError=HTTPInternalServerError, contentType='application/json', + detail=content['detail'], content=content) + + +@forbidden_view_config() +def unauthorized_access(request): + # if not overridden, default is HTTPForbidden [403], which is for a slightly different situation + # this better reflects the HTTPUnauthorized [401] user access with specified AuthZ headers + # [http://www.restapitutorial.com/httpstatuscodes.html] + content = get_request_info(request, default_msg=UnauthorizedResponseSchema.description) + return raise_http(nothrow=True, httpError=HTTPUnauthorized, contentType='application/json', + detail=content['detail'], content=content) + + +def get_request_info(request, default_msg="undefined"): + content = {u'route_name': str(request.upath_info), u'request_url': str(request.url), u'detail': default_msg} + if hasattr(request, 'exception'): + if hasattr(request.exception, 'json'): + if type(request.exception.json) is dict: + content.update(request.exception.json) + elif isinstance(request.exception, HTTPServerError) and hasattr(request.exception, 'message'): + content.update({u'exception': str(request.exception.message)}) + elif hasattr(request, 'matchdict'): + if request.matchdict is not None and request.matchdict != '': + content.update(request.matchdict) + return content diff --git a/magpie/api/api_requests.py b/magpie/api/api_requests.py index bea74da38..63347dc3c 100644 --- a/magpie/api/api_requests.py +++ b/magpie/api/api_requests.py @@ -1,8 +1,8 @@ from magpie import * -from definitions.pyramid_definitions import * -from definitions.ziggurat_definitions import * -from api.api_except import evaluate_call, verify_param -from api.management.resource.resource_utils import check_valid_service_resource_permission +from magpie.definitions.pyramid_definitions import * +from magpie.definitions.ziggurat_definitions import * +from magpie.api.api_except import evaluate_call, verify_param +from magpie.api.api_rest_schemas import * import models @@ -12,29 +12,34 @@ def get_request_method_content(request): return getattr(request, method_property) -def get_multiformat_any(request, key): +def get_multiformat_any(request, key, default=None): msg = "Key `{key}` could not be extracted from {method} of type `{type}`" \ .format(key=repr(key), method=request.method, type=request.content_type) if request.content_type == 'application/json': - return evaluate_call(lambda: request.json.get(key), + # avoid json parse error if body is empty + if not len(request.body): + return default + return evaluate_call(lambda: request.json.get(key, default), httpError=HTTPInternalServerError, msgOnFail=msg) - return evaluate_call(lambda: get_request_method_content(request).get(key), + return evaluate_call(lambda: get_request_method_content(request).get(key, default), httpError=HTTPInternalServerError, msgOnFail=msg) -def get_multiformat_post(request, key): - return get_multiformat_any(request, key) +def get_multiformat_post(request, key, default=None): + return get_multiformat_any(request, key, default) -def get_multiformat_put(request, key): - return get_multiformat_any(request, key) +def get_multiformat_put(request, key, default=None): + return get_multiformat_any(request, key, default) -def get_multiformat_delete(request, key): - return get_multiformat_any(request, key) +def get_multiformat_delete(request, key, default=None): + return get_multiformat_any(request, key, default) def get_permission_multiformat_post_checked(request, service_resource, permission_name_key='permission_name'): + # import here to avoid circular import error with undefined functions between (api_request, resource_utils) + from magpie.api.management.resource.resource_utils import check_valid_service_resource_permission perm_name = get_value_multiformat_post_checked(request, permission_name_key) check_valid_service_resource_permission(perm_name, service_resource, request.db) return perm_name @@ -43,7 +48,7 @@ def get_permission_multiformat_post_checked(request, service_resource, permissio def get_value_multiformat_post_checked(request, key): val = get_multiformat_any(request, key) verify_param(val, notNone=True, notEmpty=True, httpError=HTTPUnprocessableEntity, - content={str(key): str(val)}, msgOnFail="Invalid value specified.") + paramName=key, msgOnFail=UnprocessableEntityResponseSchema.description) return val @@ -70,9 +75,10 @@ def get_user(request, user_name_or_token): return curr_user else: anonymous = evaluate_call(lambda: UserService.by_user_name(ANONYMOUS_USER, db_session=request.db), - fallback=lambda: request.db.rollback(), - httpError=HTTPForbidden, msgOnFail="Anonymous user query refused by db") - verify_param(anonymous, notNone=True, httpError=HTTPNotFound, msgOnFail="Anonymous user not found in db") + fallback=lambda: request.db.rollback(), httpError=HTTPForbidden, + msgOnFail=User_CheckAnonymous_ForbiddenResponseSchema.description) + verify_param(anonymous, notNone=True, httpError=HTTPNotFound, + msgOnFail=User_CheckAnonymous_NotFoundResponseSchema.description) return anonymous else: authn_policy = request.registry.queryUtility(IAuthenticationPolicy) @@ -83,8 +89,8 @@ def get_user(request, user_name_or_token): raise HTTPForbidden() user = evaluate_call(lambda: UserService.by_user_name(user_name_or_token, db_session=request.db), fallback=lambda: request.db.rollback(), - httpError=HTTPForbidden, msgOnFail="User name query refused by db") - verify_param(user, notNone=True, httpError=HTTPNotFound, msgOnFail="User name not found in db") + httpError=HTTPForbidden, msgOnFail=User_GET_ForbiddenResponseSchema.description) + verify_param(user, notNone=True, httpError=HTTPNotFound, msgOnFail=User_GET_NotFoundResponseSchema.description) return user @@ -97,34 +103,37 @@ def get_group_matchdict_checked(request, group_name_key='group_name'): group_name = get_value_matchdict_checked(request, group_name_key) group = evaluate_call(lambda: GroupService.by_group_name(group_name, db_session=request.db), fallback=lambda: request.db.rollback(), - httpError=HTTPForbidden, msgOnFail="Group query by name refused by db") - verify_param(group, notNone=True, httpError=HTTPNotFound, msgOnFail="Group name not found in db") + httpError=HTTPForbidden, msgOnFail=Group_MatchDictCheck_ForbiddenResponseSchema.description) + verify_param(group, notNone=True, httpError=HTTPNotFound, + msgOnFail=Group_MatchDictCheck_NotFoundResponseSchema.description) return group def get_resource_matchdict_checked(request, resource_name_key='resource_id'): resource_id = get_value_matchdict_checked(request, resource_name_key) resource_id = evaluate_call(lambda: int(resource_id), httpError=HTTPNotAcceptable, - msgOnFail="Resource ID is an invalid literal for `int` type") + msgOnFail=Resource_MatchDictCheck_NotAcceptableResponseSchema.description) resource = evaluate_call(lambda: ResourceService.by_resource_id(resource_id, db_session=request.db), - fallback=lambda: request.db.rollback(), - httpError=HTTPForbidden, msgOnFail="Resource query by id refused by db") - verify_param(resource, notNone=True, httpError=HTTPNotFound, msgOnFail="Resource ID not found in db") + fallback=lambda: request.db.rollback(), httpError=HTTPForbidden, + msgOnFail=Resource_MatchDictCheck_ForbiddenResponseSchema.description) + verify_param(resource, notNone=True, httpError=HTTPNotFound, + msgOnFail=Resource_MatchDictCheck_NotFoundResponseSchema.description) return resource def get_service_matchdict_checked(request, service_name_key='service_name'): service_name = get_value_matchdict_checked(request, service_name_key) service = evaluate_call(lambda: models.Service.by_service_name(service_name, db_session=request.db), - fallback=lambda: request.db.rollback(), - httpError=HTTPForbidden, msgOnFail="Service query by name refused by db") - verify_param(service, notNone=True, httpError=HTTPNotFound, - msgOnFail="Service name not found in db", - content={u'service_name': service_name}) + fallback=lambda: request.db.rollback(), httpError=HTTPForbidden, + msgOnFail=Service_MatchDictCheck_ForbiddenResponseSchema.description) + verify_param(service, notNone=True, httpError=HTTPNotFound, content={u'service_name': service_name}, + msgOnFail=Service_MatchDictCheck_NotFoundResponseSchema.description) return service def get_permission_matchdict_checked(request, service_resource, permission_name_key='permission_name'): + # import here to avoid circular import error with undefined functions between (api_request, resource_utils) + from magpie.api.management.resource.resource_utils import check_valid_service_resource_permission perm_name = get_value_matchdict_checked(request, permission_name_key) check_valid_service_resource_permission(perm_name, service_resource, request.db) return perm_name @@ -133,5 +142,5 @@ def get_permission_matchdict_checked(request, service_resource, permission_name_ def get_value_matchdict_checked(request, key): val = request.matchdict.get(key) verify_param(val, notNone=True, notEmpty=True, httpError=HTTPUnprocessableEntity, - content={str(key): str(val)}, msgOnFail="Invalid value specified") + paramName=key, msgOnFail=UnprocessableEntityResponseSchema.description) return val diff --git a/magpie/api/api_rest_schemas.py b/magpie/api/api_rest_schemas.py index 63ee005ba..6eea55523 100644 --- a/magpie/api/api_rest_schemas.py +++ b/magpie/api/api_rest_schemas.py @@ -1,8 +1,6 @@ -from definitions.cornice_definitions import * -from definitions.pyramid_definitions import view_config -from db import get_database_revision -from __meta__ import __version__ -import requests +from magpie.definitions.cornice_definitions import * +from magpie.definitions.pyramid_definitions import * +from magpie import LOGGED_USER, __meta__ class CorniceSwaggerPredicate(object): @@ -18,27 +16,65 @@ def __call__(self, context, request): return self.schema +TitleAPI = "Magpie REST API" +InfoAPI = { + "description": __meta__.__description__, + "contact": {"name": __meta__.__maintainer__, "email": __meta__.__email__, "url": __meta__.__url__} +} + + # Tags APITag = 'API' -UserTag = 'User' -GroupTag = 'Group' -ResourceTag = 'Resource' -ServiceTag = 'Service' +LoginTag = 'Login' +UsersTag = 'User' +LoggedUserTag = 'Logged User' +GroupsTag = 'Group' +ResourcesTag = 'Resource' +ServicesTag = 'Service' + + +# Security +SecurityDefinitionAPI = {'securityDefinitions': {'cookieAuth': {'type': 'apiKey', 'in': 'cookie', 'name': 'auth_tkt'}}} +SecurityAdministratorAPI = [{'cookieAuth': []}] +SecurityEveryoneAPI = [] + + +def get_security(service, method): + definitions = service.definitions + args = {} + for definition in definitions: + met, view, args = definition + if met == method: + break + return SecurityAdministratorAPI if 'security' not in args else args['security'] # Service Routes +def service_api_route_info(service_api): + return {'name': service_api.name, 'pattern': service_api.path} + + +LoggedUserBase = '/users/{}'.format(LOGGED_USER) + + +SwaggerGenerator = Service( + path='/__api__', + name='swagger_schema') SwaggerAPI = Service( - path='__api__', - name='Magpie REST API', - description="Magpie REST API documentation") + path='/api', + name='swagger', + description="{} documentation".format(TitleAPI)) +UsersAPI = Service( + path='/users', + name='Users') UserAPI = Service( path='/users/{user_name}', name='User') -UsersAPI = Service( - path='/users/{user_name}', - name='Users') -UserGroupAPI = Service( +UserGroupsAPI = Service( path='/users/{user_name}/groups', + name='UserGroups') +UserGroupAPI = Service( + path='/users/{user_name}/groups/{group_name}', name='UserGroup') UserInheritedResourcesAPI = Service( path='/users/{user_name}/inherited_resources', @@ -46,52 +82,1425 @@ def __call__(self, context, request): UserResourcesAPI = Service( path='/users/{user_name}/resources', name='UserResources') -UserResourcesPermissionsAPI = Service( - path='/users/{user_name}/resources', - name='UserResourcesPermissions') +UserResourceInheritedPermissionsAPI = Service( + path='/users/{user_name}/resources/{resource_id}/inherited_permissions', + name='UserResourceInheritedPermissions') +UserResourcePermissionAPI = Service( + path='/users/{user_name}/resources/{resource_id}/permissions/{permission_name}', + name='UserResourcePermission') +UserResourcePermissionsAPI = Service( + path='/users/{user_name}/resources/{resource_id}/permissions', + name='UserResourcePermissions') +UserResourceTypesAPI = Service( + path='/users/{user_name}/resources/types/{resource_type}', + name='UserResourceTypes') UserInheritedServicesAPI = Service( path='/users/{user_name}/inherited_services', name='UserInheritedServices') UserServicesAPI = Service( path='/users/{user_name}/services', name='UserServices') -CurrentUserAPI = Service( - path='/users/current', - name='CurrentUser') +UserServiceAPI = Service( + path='/users/{user_name}/services/{service_name}', + name='UserService') +UserServiceInheritedResourcesAPI = Service( + path='/users/{user_name}/services/{service_name}/inherited_resources', + name='UserServiceInheritedResources') +UserServiceResourcesAPI = Service( + path='/users/{user_name}/services/{service_name}/resources', + name='UserServiceResources') +UserServiceInheritedPermissionsAPI = Service( + path='/users/{user_name}/services/{service_name}/inherited_permissions', + name='UserServiceInheritedPermissions') +UserServicePermissionsAPI = Service( + path='/users/{user_name}/services/{service_name}/permissions', + name='UserServicePermissions') +UserServicePermissionAPI = Service( + path='/users/{user_name}/services/{service_name}/permissions/{permission_name}', + name='UserServicePermission') +LoggedUserAPI = Service( + path=LoggedUserBase, + name='LoggedUser') +LoggedUserGroupsAPI = Service( + path=LoggedUserBase + '/groups', + name='LoggedUserGroups') +LoggedUserGroupAPI = Service( + path=LoggedUserBase + '/groups/{group_name}', + name='LoggedUserGroup') +LoggedUserInheritedResourcesAPI = Service( + path=LoggedUserBase + '/inherited_resources', + name='LoggedUserInheritedResources') +LoggedUserResourcesAPI = Service( + path=LoggedUserBase + '/resources', + name='LoggedUserResources') +LoggedUserResourceInheritedPermissionsAPI = Service( + path=LoggedUserBase + '/resources/{resource_id}/inherited_permissions', + name='LoggedUserResourceInheritedPermissions') +LoggedUserResourcePermissionAPI = Service( + path=LoggedUserBase + '/resources/{resource_id}/permissions/{permission_name}', + name='LoggedUserResourcePermission') +LoggedUserResourcePermissionsAPI = Service( + path=LoggedUserBase + '/resources/{resource_id}/permissions', + name='LoggedUserResourcePermissions') +LoggedUserResourceTypesAPI = Service( + path=LoggedUserBase + '/resources/types/{resource_type}', + name='LoggedUserResourceTypes') +LoggedUserInheritedServicesAPI = Service( + path=LoggedUserBase + '/inherited_services', + name='LoggedUserInheritedServices') +LoggedUserServicesAPI = Service( + path=LoggedUserBase + '/services', + name='LoggedUserServices') +LoggedUserServiceInheritedResourcesAPI = Service( + path=LoggedUserBase + '/services/{service_name}/inherited_resources', + name='LoggedUserServiceInheritedResources') +LoggedUserServiceResourcesAPI = Service( + path=LoggedUserBase + '/services/{service_name}/resources', + name='LoggedUserServiceResources') +LoggedUserServiceInheritedPermissionsAPI = Service( + path=LoggedUserBase + '/services/{service_name}/inherited_permissions', + name='LoggedUserServiceInheritedPermissions') +LoggedUserServicePermissionsAPI = Service( + path=LoggedUserBase + '/services/{service_name}/permissions', + name='LoggedUserServicePermissions') +LoggedUserServicePermissionAPI = Service( + path=LoggedUserBase + '/services/{service_name}/permissions/{permission_name}', + name='LoggedUserServicePermission') +GroupsAPI = Service( + path='/groups', + name='Groups') GroupAPI = Service( path='/groups/{group_name}', name='Group') +GroupUsersAPI = Service( + path='/groups/{group_name}/users', + name='GroupUsers') +GroupServicesAPI = Service( + path='/groups/{group_name}/services', + name='GroupServices') +GroupServicePermissionsAPI = Service( + path='/groups/{group_name}/services/{service_name}/permissions', + name='GroupServicePermissions') +GroupServicePermissionAPI = Service( + path='/groups/{group_name}/services/{service_name}/permissions/{permission_name}', + name='GroupServicePermission') +GroupServiceResourcesAPI = Service( + path='/groups/{group_name}/services/{service_name}/resources', + name='GroupServiceResources') +GroupResourcesAPI = Service( + path='/groups/{group_name}/resources', + name='GroupResources') +GroupResourcePermissionsAPI = Service( + path='/groups/{group_name}/resources/{resource_id}/permissions', + name='GroupResourcePermissions') +GroupResourcePermissionAPI = Service( + path='/groups/{group_name}/resources/{resource_id}/permissions/{permission_name}', + name='GroupResourcePermission') +GroupResourceTypesAPI = Service( + path='/groups/{group_name}/resources/types/{resource_type}', + name='GroupResourceTypes') +ResourcesAPI = Service( + path='/resources', + name='Resources') ResourceAPI = Service( path='/resources/{resource_id}', name='Resource') +ResourcePermissionsAPI = Service( + path='/resources/{resource_id}/permissions', + name='ResourcePermissions') +ServicesAPI = Service( + path='/services', + name='Services') ServiceAPI = Service( path='/services/{service_name}', name='Service') +ServiceTypesAPI = Service( + path='/services/types/{service_type}', + name='ServiceTypes') +ServicePermissionsAPI = Service( + path='/services/{service_name}/permissions', + name='ServicePermissions') +ServiceResourcesAPI = Service( + path='/services/{service_name}/resources', + name='ServiceResources') +ServiceResourceAPI = Service( + path='/services/{service_name}/resources/{resource_id}', + name='ServiceResource') +ServiceResourceTypesAPI = Service( + path='/services/types/{service_type}/resources/types', + name='ServiceResourceTypes') +SessionAPI = Service( + path='/session', + name='Session') VersionAPI = Service( path='/version', name='Version') -#NotFoundAPI = Service(name='NotFound', path='/', description="Route not found") -class BaseSchema(colander.MappingSchema): - code = colander.SchemaNode(colander.Integer(), description="HTTP response code") - type = colander.SchemaNode(colander.String(), description="Response content type") - detail = colander.SchemaNode(colander.String(), description="Response status message") +CodeSchemaNode = colander.SchemaNode(colander.Integer(), description="HTTP response code", example=HTTPOk.code) +TypeSchemaNode = colander.SchemaNode(colander.String(), description="Response content type", example="application/json") +DetailSchemaNode = colander.SchemaNode(colander.String(), description="Response status message") + + +class HeaderSchema(colander.MappingSchema): + content_type = colander.SchemaNode( + colander.String(), + example='application/json' + ) + content_type.name = 'Content-Type' + + +class BaseBodySchema(colander.MappingSchema): + def __init__(self, code=None): + super(BaseBodySchema, self).__init__() + self.code = CodeSchemaNode + self.code.example = code + self.type = TypeSchemaNode + self.detail = DetailSchemaNode + + +class ErrorVerifyParamBodySchema(colander.MappingSchema): + name = colander.SchemaNode( + colander.String(), + description="Name of the failing condition parameter", + missing=colander.drop) + value = colander.SchemaNode( + colander.String(), + description="Value of the failing condition parameter") + compare = colander.SchemaNode( + colander.String(), + description="Test comparison value of the failing condition parameter", + missing=colander.drop) + + +class UnauthorizedResponseSchema(colander.MappingSchema): + description = "Unauthorized. Insufficient user privileges or missing authentication headers." + code = CodeSchemaNode + code.example = 401 + type = TypeSchemaNode + detail = DetailSchemaNode + route_name = colander.SchemaNode(colander.String(), description="Specified route") + request_url = colander.SchemaNode(colander.String(), description="Specified url") -#class NotFoundResponseSchema(colander.MappingSchema): -# code = colander.SchemaNode(colander.Integer(), description="HTTP response code") -# type = colander.SchemaNode(colander.String(), description="Response content type") -# detail = colander.SchemaNode(colander.String(), description="Response status message") -# route_name = colander.SchemaNode(colander.String(), description="Specified route") -# request_url = colander.SchemaNode(colander.String(), description="Specified url") +class NotFoundBodySchema(colander.MappingSchema): + code = colander.SchemaNode( + colander.Integer(), + description="HTTP response code", + example=404) + type = colander.SchemaNode( + colander.String(), + description="Response content type", + example="application/json") + detail = colander.SchemaNode( + colander.String(), + description="Response status message",) + route_name = colander.SchemaNode( + colander.String(), + description="Route called that generated the error", + example="/users/toto") + request_url = colander.SchemaNode( + colander.String(), + description="Request URL that generated the error", + example="http://localhost:2001/magpie/users/toto") + + +class NotFoundResponseSchema(colander.MappingSchema): + description = "The route resource could not be found." + body = NotFoundBodySchema() + + +class UnprocessableEntityResponseSchema(colander.MappingSchema): + description = "Invalid value specified." + body = BaseBodySchema(HTTPUnprocessableEntity.code) -class Version_GET_Schema(colander.MappingSchema): +class InternalServerErrorBodySchema(colander.MappingSchema): code = colander.SchemaNode( colander.Integer(), description="HTTP response code", - example=200) + example=500) + type = colander.SchemaNode( + colander.String(), + description="Response content type", + example="application/json") + detail = colander.SchemaNode( + colander.String(), + description="Response status message", + example="Internal Server Error. Unhandled exception occurred.") + route_name = colander.SchemaNode( + colander.String(), + description="Route called that generated the error", + example="/users/toto") + request_url = colander.SchemaNode( + colander.String(), + description="Request URL that generated the error", + example="http://localhost:2001/magpie/users/toto") + + +class InternalServerErrorResponseSchema(colander.MappingSchema): + description = "Internal Server Error. Unhandled exception occurred." + body = InternalServerErrorBodySchema() + + +class ProvidersListSchema(colander.SequenceSchema): + item = colander.SchemaNode( + colander.String(), + description="Available login providers.", + example=["ziggurat", "openid"], + ) + + +class ResourceTypesListSchema(colander.SequenceSchema): + item = colander.SchemaNode( + colander.String(), + description="Available resource type under root service.", + example=["file", "dictionary"], + ) + + +class GroupNamesListSchema(colander.SequenceSchema): + item = colander.SchemaNode( + colander.String(), + description="Groups the logged in user is member of", + example=["anonymous"] + ) + + +class UserNamesListSchema(colander.SequenceSchema): + item = colander.SchemaNode( + colander.String(), + description="Users registered in the db", + example=["anonymous", "admin", "toto"] + ) + + +class PermissionListSchema(colander.SequenceSchema): + item = colander.SchemaNode( + colander.String(), + description="Permissions applicable to the service/resource", + example=["read", "write"] + ) + + +class UserBodySchema(colander.MappingSchema): + user_name = colander.SchemaNode( + colander.String(), + description="Name of the user.", + example="toto") + email = colander.SchemaNode( + colander.String(), + description="Email of the user.", + example="toto@mail.com") + group_names = GroupNamesListSchema() + + +class ServiceBodySchema(colander.MappingSchema): + resource_id = colander.SchemaNode( + colander.Integer(), + description="Resource identification number", + ) + permission_names = PermissionListSchema() + service_name = colander.SchemaNode( + colander.String(), + description="Name of the service", + example="thredds" + ) + service_type = colander.SchemaNode( + colander.String(), + description="Type of the service", + example="thredds" + ) + public_url = colander.SchemaNode( + colander.String(), + description="Proxy URL available for public access with permissions", + example="http://localhost/twitcher/ows/proxy/thredds" + ) + service_url = colander.SchemaNode( + colander.String(), + description="Private URL of the service (restricted access)", + example="http://localhost:9999/thredds" + ) + + +class ResourceBodySchema(colander.MappingSchema): + resource_id = colander.SchemaNode( + colander.Integer(), + description="Resource identification number", + ) + resource_name = colander.SchemaNode( + colander.String(), + description="Name of the resource", + example="thredds" + ) + resource_type = colander.SchemaNode( + colander.String(), + description="Type of the resource", + example="service" + ) + parent_id = colander.SchemaNode( + colander.Integer(), + description="Parent resource identification number", + default=colander.null, # if no parent + missing=colander.drop # if not returned (basic_info = True) + ) + root_service_id = colander.SchemaNode( + colander.Integer(), + description="Resource tree root service identification number", + default=colander.null, # if no parent + missing=colander.drop # if not returned (basic_info = True) + ) + permission_names = PermissionListSchema() + permission_names.default = colander.null # if no parent + permission_names.missing = colander.drop # if not returned (basic_info = True) + + +# TODO: improve by making recursive resources work (?) +class Resource_ChildrenContainerWithoutChildResourceBodySchema(ResourceBodySchema): + children = colander.MappingSchema(default={}) + + +class Resource_ChildResourceWithoutChildrenBodySchema(colander.MappingSchema): + id = Resource_ChildrenContainerWithoutChildResourceBodySchema() + id.name = '{resource_id}' + + +class Resource_ChildrenContainerWithChildResourceBodySchema(ResourceBodySchema): + children = Resource_ChildResourceWithoutChildrenBodySchema() + + +class Resource_ChildResourceWithChildrenContainerBodySchema(colander.MappingSchema): + id = Resource_ChildrenContainerWithChildResourceBodySchema() + id.name = '{resource_id}' + + +class Resource_ServiceWithChildrenResourcesContainerBodySchema(ServiceBodySchema): + resources = Resource_ChildResourceWithChildrenContainerBodySchema() + + +class Resource_ServiceType_geoserverapi_SchemaNode(colander.MappingSchema): + geoserver_api = Resource_ServiceWithChildrenResourcesContainerBodySchema() + geoserver_api.name = "geoserver-api" + + +class Resource_ServiceType_ncwms_SchemaNode(colander.MappingSchema): + ncwms = Resource_ServiceWithChildrenResourcesContainerBodySchema() + + +class Resource_ServiceType_thredds_SchemaNode(colander.MappingSchema): + thredds = Resource_ServiceWithChildrenResourcesContainerBodySchema() + + +class ResourcesSchemaNode(colander.MappingSchema): + geoserver_api = Resource_ServiceType_geoserverapi_SchemaNode() + geoserver_api.name = "geoserver-api" + ncwms = Resource_ServiceType_ncwms_SchemaNode() + thredds = Resource_ServiceType_thredds_SchemaNode() + + +class Resources_ResponseBodySchema(colander.MappingSchema): + resources = ResourcesSchemaNode() + code = CodeSchemaNode + type = TypeSchemaNode + detail = DetailSchemaNode + + +class Resource_MatchDictCheck_ForbiddenResponseSchema(colander.MappingSchema): + description = "Resource query by id refused by db." + body = BaseBodySchema(code=HTTPForbidden.code) + + +class Resource_MatchDictCheck_NotFoundResponseSchema(colander.MappingSchema): + description = "Resource ID not found in db." + body = BaseBodySchema(code=HTTPNotFound.code) + + +class Resource_MatchDictCheck_NotAcceptableResponseSchema(colander.MappingSchema): + description = "Resource ID is an invalid literal for `int` type." + body = BaseBodySchema(code=HTTPNotAcceptable.code) + + +class Resource_GET_ResponseBodySchema(colander.MappingSchema): + resource_id = Resource_ChildResourceWithChildrenContainerBodySchema() + resource_id.name = '{resource_id}' + code = CodeSchemaNode + type = TypeSchemaNode + detail = DetailSchemaNode + + +class Resource_GET_OkResponseSchema(colander.MappingSchema): + description = "Get resource successful." + body = Resource_GET_ResponseBodySchema() + + +class Resource_GET_InternalServerErrorResponseSchema(colander.MappingSchema): + description = "Failed building resource children json formatted tree." + body = InternalServerErrorBodySchema() + + +class Resource_PUT_RequestBodySchema(colander.MappingSchema): + resource_name = colander.SchemaNode( + colander.String(), + description="New name to apply to the resource to update", + ) + service_push = colander.SchemaNode( + colander.Boolean(), + description="Push service resource update to Phoenix", + missing=False, + ) + + +class Resource_PUT_RequestSchema(colander.MappingSchema): + header = HeaderSchema() + body = Resource_PUT_RequestBodySchema() + + +class Resource_PUT_ResponseBodySchema(colander.MappingSchema): + code = CodeSchemaNode + type = TypeSchemaNode + detail = DetailSchemaNode + resource_id = colander.SchemaNode( + colander.String(), + description="Updated resource identification number." + ) + resource_name = colander.SchemaNode( + colander.String(), + description="Updated resource name (from object)." + ) + old_resource_name = colander.SchemaNode( + colander.String(), + description="Resource name before update." + ) + new_resource_name = colander.SchemaNode( + colander.String(), + description="Resource name after update." + ) + + +class Resource_PUT_OkResponseSchema(colander.MappingSchema): + description = "Update resource successful." + body = Resource_PUT_ResponseBodySchema() + + +class Resource_PUT_ForbiddenResponseSchema(colander.MappingSchema): + description = "Failed to update resource with new name." + body = BaseBodySchema(code=HTTPForbidden.code) + + +class Resource_DELETE_RequestBodySchema(colander.MappingSchema): + service_push = colander.SchemaNode( + colander.Boolean(), + description="Push service update to Phoenix if applicable", + missing=colander.drop, + default=False, + ) + + +class Resource_DELETE_RequestSchema(colander.MappingSchema): + header = HeaderSchema() + body = Resource_DELETE_RequestBodySchema() + + +class Resource_DELETE_OkResponseSchema(colander.MappingSchema): + description = "Delete resource successful." + body = BaseBodySchema(code=HTTPOk.code) + + +class Resource_DELETE_ForbiddenResponseSchema(colander.MappingSchema): + description = "Delete resource from db failed." + body = BaseBodySchema(code=HTTPForbidden.code) + + +class Resources_GET_OkResponseSchema(colander.MappingSchema): + description = "Get resources successful." + body = Resources_ResponseBodySchema() + + +class Resources_POST_BodySchema(colander.MappingSchema): + resource_name = colander.SchemaNode( + colander.String(), + description="Name of the resource to create" + ) + resource_type = colander.SchemaNode( + colander.String(), + description="Type of the resource to create" + ) + parent_id = colander.SchemaNode( + colander.String(), + description="ID of parent resource under which the new resource should be created", + missing=colander.drop + ) + + +class Resources_POST_RequestBodySchema(colander.MappingSchema): + header = HeaderSchema() + body = Resources_POST_BodySchema() + + +class Resource_POST_ResponseBodySchema(colander.MappingSchema): + resource_id = Resource_ChildResourceWithChildrenContainerBodySchema() + resource_id.name = '{resource_id}' + code = CodeSchemaNode + type = TypeSchemaNode + detail = DetailSchemaNode + + +class Resources_POST_OkResponseSchema(colander.MappingSchema): + description = "Create resource successful." + body = Resource_POST_ResponseBodySchema() + + +class Resources_POST_BadRequestResponseSchema(colander.MappingSchema): + description = "Invalid [`resource_name`|`resource_type`|`parent_id`] specified for child resource creation." + body = BaseBodySchema(code=HTTPBadRequest.code) + + +class Resources_POST_ForbiddenResponseSchema(colander.MappingSchema): + description = "Failed to insert new resource in service tree using parent id." + body = BaseBodySchema(code=HTTPForbidden.code) + + +class Resources_POST_NotFoundResponseSchema(colander.MappingSchema): + description = "Could not find specified resource parent id." + body = BaseBodySchema(code=HTTPNotFound.code) + + +class Resources_POST_ConflictResponseSchema(colander.MappingSchema): + description = "Resource name already exists at requested tree level for creation." + body = BaseBodySchema(code=HTTPConflict.code) + + +class ResourcePermissions_GET_ResponseBodySchema(colander.MappingSchema): + code = CodeSchemaNode + type = TypeSchemaNode + detail = DetailSchemaNode + permission_names = PermissionListSchema() + + +class ResourcePermissions_GET_OkResponseSchema(colander.MappingSchema): + description = "Get resource permissions successful." + body = ResourcePermissions_GET_ResponseBodySchema() + + +class ResourcePermissions_GET_NotAcceptableResponseSchema(colander.MappingSchema): + description = "Invalid resource type to extract permissions." + body = BaseBodySchema(code=HTTPNotAcceptable.code) + + +class ServiceResourcesBodySchema(ServiceBodySchema): + children = ResourcesSchemaNode() + + +class ServiceType_access_SchemaNode(colander.MappingSchema): + frontend = ServiceBodySchema(missing=colander.drop) + geoserver_web = ServiceBodySchema(missing=colander.drop) + geoserver_web.name = "geoserver-web" + magpie = ServiceBodySchema(missing=colander.drop) + + +class ServiceType_geoserverapi_SchemaNode(colander.MappingSchema): + geoserver_api = ServiceBodySchema(missing=colander.drop) + geoserver_api.name = "geoserver-api" + + +class ServiceType_geoserverwms_SchemaNode(colander.MappingSchema): + geoserverwms = ServiceBodySchema(missing=colander.drop) + + +class ServiceType_ncwms_SchemaNode(colander.MappingSchema): + ncwms = ServiceBodySchema(missing=colander.drop) + ncwms.name = "ncWMS2" + + +class ServiceType_projectapi_SchemaNode(colander.MappingSchema): + project_api = ServiceBodySchema(missing=colander.drop) + project_api.name = "project-api" + + +class ServiceType_thredds_SchemaNode(colander.MappingSchema): + thredds = ServiceBodySchema(missing=colander.drop) + + +class ServiceType_wfs_SchemaNode(colander.MappingSchema): + geoserver = ServiceBodySchema(missing=colander.drop) + + +class ServiceType_wps_SchemaNode(colander.MappingSchema): + lb_flyingpigeon = ServiceBodySchema(missing=colander.drop) + flyingpigeon = ServiceBodySchema(missing=colander.drop) + project = ServiceBodySchema(missing=colander.drop) + catalog = ServiceBodySchema(missing=colander.drop) + malleefowl = ServiceBodySchema(missing=colander.drop) + hummingbird = ServiceBodySchema(missing=colander.drop) + + +class ServicesSchemaNode(colander.MappingSchema): + access = ServiceType_access_SchemaNode() + geoserver_api = ServiceType_geoserverapi_SchemaNode(missing=colander.drop) + geoserver_api.name = "geoserver-api" + geoserverwms = ServiceType_geoserverwms_SchemaNode(missing=colander.drop) + ncwms = ServiceType_ncwms_SchemaNode() + project_api = ServiceType_projectapi_SchemaNode(missing=colander.drop) + project_api.name = "project-api" + thredds = ServiceType_thredds_SchemaNode() + wfs = ServiceType_wfs_SchemaNode(missing=colander.drop) + wps = ServiceType_wps_SchemaNode(missing=colander.drop) + + +class Service_FailureBodyResponseSchema(colander.MappingSchema): + code = CodeSchemaNode + type = TypeSchemaNode + detail = DetailSchemaNode + service_name = colander.SchemaNode( + colander.String(), + description="Service name extracted from path" + ) + + +class Service_MatchDictCheck_ForbiddenResponseSchema(colander.MappingSchema): + description = "Service query by name refused by db." + body = BaseBodySchema(code=HTTPForbidden.code) + + +class Service_MatchDictCheck_NotFoundResponseSchema(colander.MappingSchema): + description = "Service name not found in db." + body = Service_FailureBodyResponseSchema(code=HTTPNotFound.code) + + +class Service_GET_ResponseBodySchema(colander.MappingSchema): + service_name = ServiceBodySchema() + service_name.name = '{service_name}' + code = CodeSchemaNode + type = TypeSchemaNode + detail = DetailSchemaNode + + +class Service_GET_OkResponseSchema(colander.MappingSchema): + description = "Get service successful." + body = Service_GET_ResponseBodySchema() + + +class Services_GET_ResponseBodySchema(colander.MappingSchema): + services = ServicesSchemaNode() + code = CodeSchemaNode + type = TypeSchemaNode + detail = DetailSchemaNode + detail.example = "Get services successful." + + +class Services_GET_OkResponseSchema(colander.MappingSchema): + description = "Get services successful." + body = Services_GET_ResponseBodySchema() + + +class Services_GET_NotAcceptableResponseSchema(colander.MappingSchema): + description = "Invalid `service_type` value does not correspond to any of the existing service types." + body = Services_GET_ResponseBodySchema() + + +class Services_POST_BodySchema(colander.MappingSchema): + service_name = colander.SchemaNode( + colander.String(), + description="Name of the service to create", + example="my_service" + ) + service_type = colander.SchemaNode( + colander.String(), + description="Type of the service to create", + example="wps" + ) + service_url = colander.SchemaNode( + colander.String(), + description="Private URL of the service to create", + example="http://localhost:9000/my_service" + ) + + +class Services_POST_RequestBodySchema(colander.MappingSchema): + header = HeaderSchema() + body = Services_POST_BodySchema() + + +class Services_POST_ResponseBodySchema(colander.MappingSchema): + code = CodeSchemaNode + type = TypeSchemaNode + detail = DetailSchemaNode + + +class Services_POST_CreatedResponseSchema(colander.MappingSchema): + description = "Service registration to db successful." + body = Services_POST_ResponseBodySchema() + + +class Services_POST_BadRequestResponseSchema(colander.MappingSchema): + description = "Invalid `service_type` value does not correspond to any of the existing service types." + body = Services_POST_ResponseBodySchema(code=HTTPBadRequest.code) + + +class Services_POST_ForbiddenResponseSchema(colander.MappingSchema): + description = "Service registration forbidden by db." + body = Services_POST_ResponseBodySchema(code=HTTPForbidden.code) + + +class Services_POST_ConflictResponseSchema(colander.MappingSchema): + description = "Specified `service_name` value already exists." + body = Services_POST_ResponseBodySchema(code=HTTPConflict.code) + + +class Service_PUT_ResponseBodySchema(colander.MappingSchema): + service_name = colander.SchemaNode( + colander.String(), + description="New service name to apply to service specified in path", + missing=colander.drop, + default=colander.null, + example="my_service_new_name" + ) + service_url = colander.SchemaNode( + colander.String(), + description="New service private URL to apply to service specified in path", + missing=colander.drop, + default=colander.null, + example="http://localhost:9000/new_service_name" + ) + service_push = colander.SchemaNode( + colander.Boolean(), + description="Push service update to Phoenix if applicable", + missing=colander.drop, + default=False, + ) + + +class Service_PUT_RequestBodySchema(colander.MappingSchema): + header = HeaderSchema() + body = Service_PUT_ResponseBodySchema() + + +class Service_SuccessBodyResponseSchema(colander.MappingSchema): + code = CodeSchemaNode + type = TypeSchemaNode + detail = DetailSchemaNode + service = ServiceBodySchema() + + +class Service_PUT_OkResponseSchema(colander.MappingSchema): + description = "Update service successful." + body = Service_SuccessBodyResponseSchema(code=HTTPOk.code) + + +class Service_PUT_BadRequestResponseSchema(colander.MappingSchema): + description = "Logged service values are already equal to update values." + body = Service_FailureBodyResponseSchema(code=HTTPBadRequest.code) + + +class Service_PUT_ForbiddenResponseSchema(colander.MappingSchema): + description = "Update service failed during value assignment." + body = Service_FailureBodyResponseSchema(code=HTTPForbidden.code) + + +class Service_PUT_ConflictResponseSchema(colander.MappingSchema): + description = "Specified `service_name` already exists." + body = Service_FailureBodyResponseSchema(code=HTTPConflict.code) + + +# delete service use same method as direct resource delete +Service_DELETE_RequestSchema = Resource_DELETE_RequestSchema + + +class Service_DELETE_OkResponseSchema(colander.MappingSchema): + description = "Delete service successful." + body = ServiceBodySchema(code=HTTPOk.code) + + +class Service_DELETE_ForbiddenResponseSchema(colander.MappingSchema): + description = "Delete service from db refused by db." + body = Service_FailureBodyResponseSchema(code=HTTPForbidden.code) + + +class ServicePermissions_ResponseBodySchema(colander.MappingSchema): + permission_names = PermissionListSchema() + code = CodeSchemaNode + type = TypeSchemaNode + detail = DetailSchemaNode + + +class ServicePermissions_GET_OkResponseSchema(colander.MappingSchema): + description = "Get service permissions successful." + body = ServicePermissions_ResponseBodySchema() + + +class ServicePermissions_GET_NotAcceptableResponseSchema(colander.MappingSchema): + description = "Invalid service type specified by service." + body = ServicePermissions_ResponseBodySchema() + + +# create service's resource use same method as direct resource create +ServiceResources_POST_BodySchema = Resources_POST_BodySchema +ServiceResources_POST_RequestBodySchema = Resources_POST_RequestBodySchema +ServiceResources_POST_OkResponseSchema = Resources_POST_OkResponseSchema +ServiceResources_POST_BadRequestResponseSchema = Resources_POST_BadRequestResponseSchema +ServiceResources_POST_ForbiddenResponseSchema = Resources_POST_ForbiddenResponseSchema +ServiceResources_POST_NotFoundResponseSchema = Resources_POST_NotFoundResponseSchema +ServiceResources_POST_ConflictResponseSchema = Resources_POST_ConflictResponseSchema + + +# delete service's resource use same method as direct resource delete +ServiceResource_DELETE_RequestSchema = Resource_DELETE_RequestSchema +ServiceResource_DELETE_ForbiddenResponseSchema = Resource_DELETE_ForbiddenResponseSchema +ServiceResource_DELETE_OkResponseSchema = Resource_DELETE_OkResponseSchema + + +class ServiceResources_GET_ResponseBodySchema(colander.MappingSchema): + service_name = Resource_ServiceWithChildrenResourcesContainerBodySchema() + service_name.name = '{service_name}' + code = CodeSchemaNode + type = TypeSchemaNode + detail = DetailSchemaNode + + +class ServiceResources_GET_OkResponseSchema(colander.MappingSchema): + description = "Get service resources successful." + body = ServiceResources_GET_ResponseBodySchema() + + +class ServiceResourceTypes_GET_ResponseBodySchema(colander.MappingSchema): + resource_types = ResourceTypesListSchema() + code = CodeSchemaNode + type = TypeSchemaNode + detail = DetailSchemaNode + + +class ServiceResourceTypes_GET_OkResponseSchema(colander.MappingSchema): + description = "Get service type resource types successful." + body = ServiceResourceTypes_GET_ResponseBodySchema() + + +class ServiceResourceTypes_GET_FailureBodyResponseSchema(colander.MappingSchema): + service_type = colander.SchemaNode( + colander.String(), + description="Service type retrieved from route path." + ) + code = CodeSchemaNode + type = TypeSchemaNode + detail = DetailSchemaNode + + +class ServiceResourceTypes_GET_ForbiddenResponseSchema(colander.MappingSchema): + description = "Failed to obtain resource types for specified service type." + body = ServiceResourceTypes_GET_FailureBodyResponseSchema(code=HTTPForbidden.code) + + +class ServiceResourceTypes_GET_NotFoundResponseSchema(colander.MappingSchema): + description = "Invalid `service_type` does not exist to obtain its resource types." + body = ServiceResourceTypes_GET_FailureBodyResponseSchema(code=HTTPNotFound.code) + + +class Users_GET_ResponseBodySchema(colander.MappingSchema): + code = CodeSchemaNode + type = TypeSchemaNode + detail = DetailSchemaNode + user_names = UserNamesListSchema() + + +class Users_GET_OkResponseSchema(colander.MappingSchema): + description = "Get users successful." + body = Users_GET_ResponseBodySchema() + + +class Users_GET_ForbiddenResponseSchema(colander.MappingSchema): + description = "Get users query refused by db." + body = BaseBodySchema(code=HTTPForbidden.code) + + +class Users_CheckInfo_ResponseBodySchema(colander.MappingSchema): + code = CodeSchemaNode + type = TypeSchemaNode + detail = DetailSchemaNode + param = ErrorVerifyParamBodySchema() + + +class Users_CheckInfo_Name_BadRequestResponseSchema(colander.MappingSchema): + description = "Invalid `user_name` value specified." + body = Users_CheckInfo_ResponseBodySchema() + + +class Users_CheckInfo_Email_BadRequestResponseSchema(colander.MappingSchema): + description = "Invalid `email` value specified." + body = Users_CheckInfo_ResponseBodySchema() + + +class Users_CheckInfo_Password_BadRequestResponseSchema(colander.MappingSchema): + description = "Invalid `password` value specified." + body = Users_CheckInfo_ResponseBodySchema() + + +class Users_CheckInfo_GroupName_BadRequestResponseSchema(colander.MappingSchema): + description = "Invalid `group_name` value specified." + body = Users_CheckInfo_ResponseBodySchema() + + +class Users_CheckInfo_Login_ConflictResponseSchema(colander.MappingSchema): + description = "Invalid `user_name` already logged in." + body = Users_CheckInfo_ResponseBodySchema() + + +class User_Check_ForbiddenResponseSchema(colander.MappingSchema): + description = "User check query was refused by db." + body = BaseBodySchema(code=HTTPForbidden.code) + + +class User_Check_ConflictResponseSchema(colander.MappingSchema): + description = "User name matches an already existing user name." + body = BaseBodySchema(code=HTTPForbidden.code) + + +class User_POST_RequestBodySchema(colander.MappingSchema): + user_name = colander.SchemaNode( + colander.String(), + description="New name to apply to the user", + example="john", + ) + email = colander.SchemaNode( + colander.String(), + description="New email to apply to the user", + example="john@mail.com", + ) + password = colander.SchemaNode( + colander.String(), + description="New password to apply to the user", + example="itzaseekit", + ) + group_name = colander.SchemaNode( + colander.String(), + description="New password to apply to the user", + example="users", + ) + + +class Users_POST_RequestSchema(colander.MappingSchema): + header = HeaderSchema() + body = User_POST_RequestBodySchema() + + +class Users_POST_ResponseBodySchema(colander.MappingSchema): + code = CodeSchemaNode + type = TypeSchemaNode + detail = DetailSchemaNode + user = UserBodySchema() + + +class Users_POST_OkResponseSchema(colander.MappingSchema): + description = "Add user to db successful." + body = Users_POST_ResponseBodySchema() + + +class Users_POST_ForbiddenResponseSchema(colander.MappingSchema): + description = "Failed to add user to db." + body = BaseBodySchema(code=HTTPForbidden.code) + + +class UserNew_POST_ForbiddenResponseSchema(colander.MappingSchema): + description = "New user query was refused by db." + body = BaseBodySchema(code=HTTPForbidden.code) + + +class User_PUT_RequestBodySchema(colander.MappingSchema): + user_name = colander.SchemaNode( + colander.String(), + description="New name to apply to the user", + missing=colander.drop, + example="john", + ) + email = colander.SchemaNode( + colander.String(), + description="New email to apply to the user", + missing=colander.drop, + example="john@mail.com", + ) + password = colander.SchemaNode( + colander.String(), + description="New password to apply to the user", + missing=colander.drop, + example="itzaseekit", + ) + + +class User_PUT_RequestSchema(colander.MappingSchema): + header = HeaderSchema() + body = User_PUT_RequestBodySchema() + + +class Users_PUT_OkResponseSchema(colander.MappingSchema): + description = "Update user successful." + body = BaseBodySchema(code=HTTPOk.code) + + +# PUT method uses same sub-function as POST method (same responses) +User_PUT_ForbiddenResponseSchema = Users_POST_ForbiddenResponseSchema + + +class User_PUT_ConflictResponseSchema(colander.MappingSchema): + description = "New name user already exists." + body = BaseBodySchema(code=HTTPConflict.code) + + +class User_GET_ResponseBodySchema(colander.MappingSchema): + code = CodeSchemaNode + type = TypeSchemaNode + detail = DetailSchemaNode + user = UserBodySchema() + + +class User_GET_OkResponseSchema(colander.MappingSchema): + description = "Get user successful." + body = User_GET_ResponseBodySchema() + + +class User_CheckAnonymous_ForbiddenResponseSchema(colander.MappingSchema): + description = "Anonymous user query refused by db." + body = BaseBodySchema(code=HTTPForbidden.code) + + +class User_CheckAnonymous_NotFoundResponseSchema(colander.MappingSchema): + description = "Anonymous user not found in db." + body = BaseBodySchema(code=HTTPNotFound.code) + + +class User_GET_ForbiddenResponseSchema(colander.MappingSchema): + description = "User name query refused by db." + body = BaseBodySchema(code=HTTPForbidden.code) + + +class User_GET_NotFoundResponseSchema(colander.MappingSchema): + description = "User name not found in db." + body = BaseBodySchema(code=HTTPNotFound.code) + + +class User_DELETE_RequestSchema(colander.MappingSchema): + header = HeaderSchema() + body = colander.MappingSchema(default={}) + + +class User_DELETE_OkResponseSchema(colander.MappingSchema): + description = "Delete user successful." + body = BaseBodySchema(code=HTTPForbidden.code) + + +class User_DELETE_ForbiddenResponseSchema(colander.MappingSchema): + description = "Delete user by name refused by db." + body = BaseBodySchema(code=HTTPForbidden.code) + + +class UserGroup_GET_ForbiddenResponseSchema(colander.MappingSchema): + description = "Group query was refused by db." + body = BaseBodySchema(code=HTTPForbidden.code) + + +class UserGroup_GET_NotAcceptableResponseSchema(colander.MappingSchema): + description = "Group for new user doesn't exist." + body = BaseBodySchema(code=HTTPNotAcceptable.code) + + +class UserGroup_Check_ForbiddenResponseSchema(colander.MappingSchema): + description = "Failed to add user-group to db." + body = BaseBodySchema(code=HTTPForbidden.code) + + +class UserGroups_GET_ResponseBodySchema(BaseBodySchema): + group_names = GroupNamesListSchema() + + +class UserGroups_GET_OkResponseSchema(colander.MappingSchema): + description = "Get user groups successful." + body = UserGroups_GET_ResponseBodySchema() + + +class UserGroups_POST_RequestBodySchema(colander.MappingSchema): + user_name = colander.SchemaNode( + colander.String(), + description="Name of the user in the user-group relationship", + example="toto", + ) + group_name = colander.SchemaNode( + colander.String(), + description="Name of the group in the user-group relationship", + example="users", + ) + + +class UserGroups_POST_RequestSchema(colander.MappingSchema): + header = HeaderSchema() + body = UserGroups_POST_RequestBodySchema() + + +class UserGroups_POST_ResponseBodySchema(BaseBodySchema): + user_name = colander.SchemaNode( + colander.String(), + description="Name of the user in the user-group relationship", + example="toto", + ) + group_name = colander.SchemaNode( + colander.String(), + description="Name of the group in the user-group relationship", + example="users", + ) + + +class UserGroups_POST_OkResponseSchema(colander.MappingSchema): + description = "Create user-group assignation successful." + body = UserGroups_POST_ResponseBodySchema(code=HTTPOk.code) + + +class UserGroups_POST_ConflictResponseSchema(colander.MappingSchema): + description = "User already belongs to this group." + body = BaseBodySchema(code=HTTPConflict.code) + + +class UserGroup_DELETE_RequestSchema(colander.MappingSchema): + header = HeaderSchema() + body = colander.MappingSchema(default={}) + + +class UserGroup_DELETE_OkResponseSchema(colander.MappingSchema): + description = "Delete user-group successful." + body = BaseBodySchema(code=HTTPOk.code) + + +class UserGroup_DELETE_NotFoundResponseSchema(colander.MappingSchema): + description = "Invalid user-group combination for delete." + body = BaseBodySchema(code=HTTPNotFound.code) + + +class UserResources_GET_ResponseBodySchema(BaseBodySchema): + resources = ResourcesSchemaNode() + + +class UserResources_GET_OkResponseSchema(colander.MappingSchema): + description = "Get user resources successful." + body = UserResources_GET_ResponseBodySchema() + + +class UserResources_GET_NotFoundResponseBodySchema(BaseBodySchema): + user_name = colander.SchemaNode(colander.String(), description="User name value read from path") + resource_types = ResourceTypesListSchema(description="Resource types searched for") + + +class UserResources_GET_NotFoundResponseSchema(colander.MappingSchema): + description = "Failed to populate user resources." + body = UserResources_GET_NotFoundResponseBodySchema(code=HTTPNotFound.code) + + +class UserResourcePermissions_GET_ResponseBodySchema(BaseBodySchema): + permission_names = PermissionListSchema() + + +class UserResourcePermissions_GET_OkResponseSchema(colander.MappingSchema): + description = "Get user resource permissions successful." + body = UserResourcePermissions_GET_ResponseBodySchema(code=HTTPNotFound.code) + + +class UserResourcePermissions_GET_NotAcceptableParamResponseSchema(colander.MappingSchema): + name = colander.SchemaNode(colander.String(), description='name of the parameter tested', example='resource_type') + value = colander.SchemaNode(colander.String(), description='value of the parameter tested') + compare = colander.SchemaNode(colander.String(), description='comparison value of the parameter tested', + missing=colander.drop) + + +class UserResourcePermissions_GET_NotAcceptableResponseBodySchema(colander.MappingSchema): + param = UserResourcePermissions_GET_NotAcceptableParamResponseSchema() + + +class UserResourcePermissions_GET_NotAcceptableRootServiceResponseSchema(colander.MappingSchema): + description = "Invalid `resource` specified for resource permission retrieval." + body = UserResourcePermissions_GET_NotAcceptableResponseBodySchema(code=HTTPNotAcceptable.code) + + +class UserResourcePermissions_GET_NotAcceptableResourceResponseSchema(colander.MappingSchema): + description = "Invalid `resource` specified for resource permission retrieval." + body = UserResourcePermissions_GET_NotAcceptableResponseBodySchema(code=HTTPNotAcceptable.code) + + +class UserResourcePermissions_GET_NotAcceptableResourceTypeResponseSchema(colander.MappingSchema): + description = "Invalid `resource_type` for corresponding service resource permission retrieval." + body = UserResourcePermissions_GET_NotAcceptableResponseBodySchema(code=HTTPNotAcceptable.code) + + +class UserResourcePermissions_POST_RequestBodySchema(BaseBodySchema): + resource_id = colander.SchemaNode( + colander.Integer(), + description="resource_id of the created user-resource-permission reference.") + user_id = colander.SchemaNode( + colander.Integer(), + description="user_id of the created user-resource-permission reference.") + permission_name = colander.SchemaNode( + colander.String(), + description="permission_name of the created user-resource-permission reference.") + + +class UserResourcePermissions_POST_RequestSchema(colander.MappingSchema): + header = HeaderSchema() + body = UserResourcePermissions_POST_RequestBodySchema() + + +class UserResourcePermissions_POST_ResponseBodySchema(BaseBodySchema): + resource_id = colander.SchemaNode( + colander.Integer(), + description="resource_id of the created user-resource-permission reference.") + user_id = colander.SchemaNode( + colander.Integer(), + description="user_id of the created user-resource-permission reference.") + permission_name = colander.SchemaNode( + colander.String(), + description="permission_name of the created user-resource-permission reference.") + + +class UserResourcePermissions_POST_ParamResponseBodySchema(BaseBodySchema): + name = colander.SchemaNode(colander.String(), description="Specified parameter.", example=u'permission_name') + value = colander.SchemaNode(colander.String(), description="Specified parameter value.") + + +class UserResourcePermissions_POST_BadResponseBodySchema(BaseBodySchema): + resource_type = colander.SchemaNode(colander.String(), description="Specified resource_type.") + resource_name = colander.SchemaNode(colander.String(), description="Specified resource_name.") + param = UserResourcePermissions_POST_ParamResponseBodySchema() + + +class UserResourcePermissions_POST_CreatedResponseSchema(colander.MappingSchema): + description = "Create user resource permission successful." + body = UserResourcePermissions_POST_ResponseBodySchema(code=HTTPCreated.code) + + +class UserResourcePermissions_POST_BadRequestResponseSchema(colander.MappingSchema): + description = "Permission not allowed for specified `resource_type`." + body = UserResourcePermissions_POST_BadResponseBodySchema(code=HTTPNotAcceptable.code) + + +class UserResourcePermissions_POST_NotAcceptableResponseSchema(colander.MappingSchema): + description = "Failed to create permission using specified `resource_id` and `user_id`." + body = UserResourcePermissions_POST_BadResponseBodySchema(code=HTTPNotAcceptable.code) + + +class UserResourcePermissions_POST_ConflictResponseSchema(colander.MappingSchema): + description = "Permission already exist on resource for user, cannot add to db." + body = UserResourcePermissions_POST_ResponseBodySchema(code=HTTPConflict.code) + + +# using same definitions +UserResourcePermissions_DELETE_BadResponseBodySchema = UserResourcePermissions_POST_ResponseBodySchema +UserResourcePermissions_DELETE_BadRequestResponseSchema = UserResourcePermissions_POST_BadRequestResponseSchema + + +class UserResourcePermission_DELETE_RequestSchema(colander.MappingSchema): + body = colander.MappingSchema(default={}) + + +class UserResourcePermissions_DELETE_OkResponseSchema(colander.MappingSchema): + description = "Delete user resource permission successful." + body = BaseBodySchema(code=HTTPOk.code) + + +class UserResourcePermissions_DELETE_NotFoundResponseSchema(colander.MappingSchema): + description = "Could not find user resource permission to delete from db." + body = UserResourcePermissions_DELETE_BadResponseBodySchema(code=HTTPOk.code) + + +class UserServiceResources_GET_ResponseBodySchema(colander.MappingSchema): + service = ServiceResourcesBodySchema() + + +class UserServiceResources_GET_OkResponseSchema(BaseBodySchema): + description = "Get user service resources successful." + body = UserServiceResources_GET_ResponseBodySchema() + + +class UserServicePermissions_POST_RequestBodySchema(colander.MappingSchema): + permission_name = colander.SchemaNode(colander.String(), description="Name of the permission to create.") + + +class UserServicePermissions_POST_RequestSchema(colander.MappingSchema): + header = HeaderSchema() + body = UserServicePermissions_POST_RequestBodySchema() + + +class UserServicePermission_DELETE_RequestSchema(colander.MappingSchema): + header = HeaderSchema() + body = colander.MappingSchema(default={}) + + +class UserServices_GET_ResponseBodySchema(BaseBodySchema): + services = ServicesSchemaNode() + + +class UserServices_GET_OkResponseSchema(colander.MappingSchema): + description = "Get user services successful." + body = UserServices_GET_ResponseBodySchema + + +class UserServicePermissions_GET_ResponseBodySchema(BaseBodySchema): + permission_names = PermissionListSchema() + + +class UserServicePermissions_GET_OkResponseSchema(colander.MappingSchema): + description = "Get user service permissions successful." + body = UserServicePermissions_GET_ResponseBodySchema() + + +class UserServicePermissions_GET_NotFoundResponseSchema(colander.MappingSchema): + description = "Could not find permissions using specified `service_name` and `user_name`." + body = BaseBodySchema(code=HTTPNotFound.code) + + +class Group_MatchDictCheck_ForbiddenResponseSchema(colander.MappingSchema): + description = "Group query by name refused by db." + body = BaseBodySchema(code=HTTPForbidden.code) + + +class Group_MatchDictCheck_NotFoundResponseSchema(colander.MappingSchema): + description = "Group name not found in db." + body = BaseBodySchema(code=HTTPNotFound.code) + + +class GroupServiceResources_GET_ResponseBodySchema(BaseBodySchema): + service = ServiceResourcesBodySchema() + + +class GroupServiceResources_GET_OkResponseSchema(colander.MappingSchema): + description = "Get group service resources successful." + body = GroupServiceResources_GET_ResponseBodySchema() + + +class Session_GET_ResponseBodySchema(BaseBodySchema): + user = UserBodySchema(missing=colander.drop) + authenticated = colander.SchemaNode( + colander.Boolean(), + description="Indicates if any user session is currently authenticated (user logged in).") + + +class Session_GET_OkResponseSchema(colander.MappingSchema): + description = "Get session successful." + body = Session_GET_ResponseBodySchema() + + +class Session_GET_InternalServerErrorResponseSchema(colander.MappingSchema): + description = "Failed to get session details." + body = InternalServerErrorResponseSchema() + + +class Providers_GET_ResponseBodySchema(colander.MappingSchema): + provider_names = ProvidersListSchema() + internal_providers = ProvidersListSchema() + external_providers = ProvidersListSchema() + + +class Providers_GET_OkResponseSchema(BaseBodySchema): + description = "Get providers successful." + body = Providers_GET_ResponseBodySchema() + + +class Version_GET_ResponseBodySchema(colander.MappingSchema): + code = colander.SchemaNode( + colander.Integer(), + description="HTTP response code", + example=HTTPOk.code) type = colander.SchemaNode( colander.String(), description="Response content type", @@ -103,7 +1512,7 @@ class Version_GET_Schema(colander.MappingSchema): version = colander.SchemaNode( colander.String(), description="Magpie version string", - example=__version__) + example=__meta__.__version__) db_version = colander.SchemaNode( colander.String(), description="Database version string", @@ -111,28 +1520,286 @@ class Version_GET_Schema(colander.MappingSchema): class Version_GET_OkResponseSchema(colander.MappingSchema): - body = Version_GET_Schema() - - -# NOT REQUIRED field -#field = colader.SchemaNode(colander.String(), missing=colader.drop) + description = "Get version successful." + body = Version_GET_ResponseBodySchema() -# Responses Schemas -###HTTP200_User_ResponseSchema +# Responses for specific views +Resource_GET_responses = { + '200': Resource_GET_OkResponseSchema(), + '401': UnauthorizedResponseSchema(), + '403': Resource_MatchDictCheck_ForbiddenResponseSchema(), + '404': Resource_MatchDictCheck_NotFoundResponseSchema(), + '406': Resource_MatchDictCheck_NotAcceptableResponseSchema(), + '422': UnprocessableEntityResponseSchema(), + '500': Resource_GET_InternalServerErrorResponseSchema() +} +Resource_PUT_responses = { + '200': Resource_PUT_OkResponseSchema(), + '403': Resource_PUT_ForbiddenResponseSchema(), + '404': Resource_MatchDictCheck_NotFoundResponseSchema(), + '406': Resource_MatchDictCheck_NotAcceptableResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +Resources_GET_responses = { + '200': Resources_GET_OkResponseSchema(), + '401': UnauthorizedResponseSchema(), + '500': Resource_GET_InternalServerErrorResponseSchema() +} +Resources_POST_responses = { + '200': Resources_POST_OkResponseSchema(), + '400': Resources_POST_BadRequestResponseSchema(), + '401': UnauthorizedResponseSchema(), + '403': Resources_POST_ForbiddenResponseSchema(), + '404': Resources_POST_NotFoundResponseSchema(), + '409': Resources_POST_ConflictResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +Resources_DELETE_responses = { + '200': Resource_DELETE_OkResponseSchema(), + '401': UnauthorizedResponseSchema(), + '403': Resource_DELETE_ForbiddenResponseSchema(), + '404': Resource_MatchDictCheck_NotFoundResponseSchema(), + '406': Resource_MatchDictCheck_NotAcceptableResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +ResourcePermissions_GET_responses = { + '200': ResourcePermissions_GET_OkResponseSchema(), + '401': UnauthorizedResponseSchema(), + '403': Resource_MatchDictCheck_ForbiddenResponseSchema(), + '404': Resource_MatchDictCheck_NotFoundResponseSchema(), + '406': ResourcePermissions_GET_NotAcceptableResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +ServiceTypes_GET_responses = { + '200': Services_GET_OkResponseSchema(), + '401': UnauthorizedResponseSchema(), + '406': Services_GET_NotAcceptableResponseSchema(), +} +Services_GET_responses = { + '200': Services_GET_OkResponseSchema(), + '401': UnauthorizedResponseSchema(), + '406': Services_GET_NotAcceptableResponseSchema(), +} +Services_POST_responses = { + '201': Services_POST_CreatedResponseSchema(), + '400': Services_POST_BadRequestResponseSchema(), + '401': UnauthorizedResponseSchema(), + '403': Services_POST_ForbiddenResponseSchema(), + '409': Services_POST_ConflictResponseSchema(), +} +Service_GET_responses = { + '200': Service_GET_OkResponseSchema(), + '401': UnauthorizedResponseSchema(), + '403': Service_MatchDictCheck_ForbiddenResponseSchema(), + '404': Service_MatchDictCheck_NotFoundResponseSchema(), +} +Service_PUT_responses = { + '200': Service_PUT_OkResponseSchema(), + '400': Service_PUT_BadRequestResponseSchema(), + '401': UnauthorizedResponseSchema(), + '403': Service_PUT_ForbiddenResponseSchema(), + '409': Service_PUT_ConflictResponseSchema(), +} +Service_DELETE_responses = { + '200': Service_DELETE_OkResponseSchema(), + '401': UnauthorizedResponseSchema(), + '403': Service_DELETE_ForbiddenResponseSchema(), + '404': Service_MatchDictCheck_NotFoundResponseSchema(), +} +ServicePermissions_GET_responses = { + '200': ServicePermissions_GET_OkResponseSchema(), + '401': UnauthorizedResponseSchema(), + '403': Service_MatchDictCheck_ForbiddenResponseSchema(), + '404': Service_MatchDictCheck_NotFoundResponseSchema(), + '406': ServicePermissions_GET_NotAcceptableResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +ServiceResources_GET_responses = { + '200': ServiceResources_GET_OkResponseSchema(), + '401': UnauthorizedResponseSchema(), + '403': Service_MatchDictCheck_ForbiddenResponseSchema(), + '404': Service_MatchDictCheck_NotFoundResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +ServiceResources_POST_responses = { + '200': ServiceResources_POST_OkResponseSchema(), + '400': ServiceResources_POST_BadRequestResponseSchema(), + '401': UnauthorizedResponseSchema(), + '403': ServiceResources_POST_ForbiddenResponseSchema(), + '404': ServiceResources_POST_NotFoundResponseSchema(), + '409': ServiceResources_POST_ConflictResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +ServiceResource_GET_responses = { + '200': ServiceResourceTypes_GET_OkResponseSchema(), + '401': UnauthorizedResponseSchema(), + '403': ServiceResourceTypes_GET_ForbiddenResponseSchema(), + '404': ServiceResourceTypes_GET_NotFoundResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +ServiceResource_DELETE_responses = { + '200': ServiceResource_DELETE_OkResponseSchema(), + '401': UnauthorizedResponseSchema(), + '403': ServiceResource_DELETE_ForbiddenResponseSchema(), + '404': Resource_MatchDictCheck_NotFoundResponseSchema(), + '406': Resource_MatchDictCheck_NotAcceptableResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +UserResources_GET_responses = { + '200': UserResources_GET_OkResponseSchema(), + '403': User_CheckAnonymous_ForbiddenResponseSchema(), + '404': UserResources_GET_NotFoundResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +UserGroups_GET_responses = { + '200': UserGroups_GET_OkResponseSchema(), + '403': User_CheckAnonymous_ForbiddenResponseSchema(), + '404': User_CheckAnonymous_NotFoundResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +UserGroups_POST_responses = { + '200': UserGroups_POST_OkResponseSchema(), + '401': UnauthorizedResponseSchema(), + '403': User_CheckAnonymous_ForbiddenResponseSchema(), + '404': User_CheckAnonymous_NotFoundResponseSchema(), + '409': UserGroups_POST_ConflictResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +UserGroup_DELETE_responses = { + '200': UserGroup_DELETE_OkResponseSchema(), + '401': UnauthorizedResponseSchema(), + '403': User_CheckAnonymous_ForbiddenResponseSchema(), + '404': User_CheckAnonymous_NotFoundResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +UserResourcePermissions_GET_responses = { + '200': UserResourcePermissions_GET_OkResponseSchema(), + '403': Resource_MatchDictCheck_ForbiddenResponseSchema(), + '404': Resource_MatchDictCheck_NotFoundResponseSchema(), + '406': Resource_MatchDictCheck_NotAcceptableResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +UserResourcePermissions_POST_responses = { + '201': UserResourcePermissions_POST_CreatedResponseSchema(), + '400': UserResourcePermissions_POST_BadRequestResponseSchema(), + '401': UnauthorizedResponseSchema(), + '406': UserResourcePermissions_POST_NotAcceptableResponseSchema(), + '409': UserResourcePermissions_POST_ConflictResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +UserResourcePermission_DELETE_responses = { + '200': UserResourcePermissions_DELETE_OkResponseSchema(), + '400': UserResourcePermissions_DELETE_BadRequestResponseSchema(), + '401': UnauthorizedResponseSchema(), + '404': UserResourcePermissions_DELETE_NotFoundResponseSchema(), + '406': UserResourcePermissions_GET_NotAcceptableResourceResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +UserServices_GET_responses = { + '200': UserServices_GET_OkResponseSchema(), + '403': User_GET_ForbiddenResponseSchema(), + '404': User_GET_NotFoundResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +UserServicePermissions_GET_responses = { + '200': UserServicePermissions_GET_OkResponseSchema(), + '403': User_GET_ForbiddenResponseSchema(), + '404': UserServicePermissions_GET_NotFoundResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +UserServiceResources_GET_responses = { + '200': UserServiceResources_GET_OkResponseSchema(), + '403': User_GET_ForbiddenResponseSchema(), + '404': Service_MatchDictCheck_NotFoundResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +UserServicePermissions_POST_responses = UserResourcePermissions_POST_responses +UserServicePermission_DELETE_responses = UserResourcePermission_DELETE_responses +LoggedUserResources_GET_responses = { + '200': UserResources_GET_OkResponseSchema(), + '403': User_CheckAnonymous_ForbiddenResponseSchema(), + '404': UserResources_GET_NotFoundResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +LoggedUserGroups_GET_responses = { + '200': UserGroups_GET_OkResponseSchema(), + '403': User_CheckAnonymous_ForbiddenResponseSchema(), + '404': User_CheckAnonymous_NotFoundResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +LoggedUserGroups_POST_responses = { + '200': UserGroups_POST_OkResponseSchema(), + '401': UnauthorizedResponseSchema(), + '403': User_CheckAnonymous_ForbiddenResponseSchema(), + '404': User_CheckAnonymous_NotFoundResponseSchema(), + '409': UserGroups_POST_ConflictResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +LoggedUserGroup_DELETE_responses = { + '200': UserGroup_DELETE_OkResponseSchema(), + '401': UnauthorizedResponseSchema(), + '403': User_CheckAnonymous_ForbiddenResponseSchema(), + '404': User_CheckAnonymous_NotFoundResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +LoggedUserResourcePermissions_GET_responses = { + '200': UserResourcePermissions_GET_OkResponseSchema(), + '403': Resource_MatchDictCheck_ForbiddenResponseSchema(), + '404': Resource_MatchDictCheck_NotFoundResponseSchema(), + '406': Resource_MatchDictCheck_NotAcceptableResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +LoggedUserResourcePermissions_POST_responses = { + '201': UserResourcePermissions_POST_CreatedResponseSchema(), + '400': UserResourcePermissions_POST_BadRequestResponseSchema(), + '401': UnauthorizedResponseSchema(), + '406': UserResourcePermissions_POST_NotAcceptableResponseSchema(), + '409': UserResourcePermissions_POST_ConflictResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +LoggedUserResourcePermission_DELETE_responses = { + '200': UserResourcePermissions_DELETE_OkResponseSchema(), + '400': UserResourcePermissions_DELETE_BadRequestResponseSchema(), + '401': UnauthorizedResponseSchema(), + '404': UserResourcePermissions_DELETE_NotFoundResponseSchema(), + '406': UserResourcePermissions_GET_NotAcceptableResourceResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +LoggedUserServices_GET_responses = { + '200': UserServices_GET_OkResponseSchema(), + '403': User_GET_ForbiddenResponseSchema(), + '404': User_GET_NotFoundResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +LoggedUserServicePermissions_GET_responses = { + '200': UserServicePermissions_GET_OkResponseSchema(), + '403': User_GET_ForbiddenResponseSchema(), + '404': UserServicePermissions_GET_NotFoundResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +LoggedUserServiceResources_GET_responses = { + '200': UserServiceResources_GET_OkResponseSchema(), + '403': User_GET_ForbiddenResponseSchema(), + '404': Service_MatchDictCheck_NotFoundResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +} +LoggedUserServicePermissions_POST_responses = LoggedUserResourcePermissions_POST_responses +LoggedUserServicePermission_DELETE_responses = LoggedUserResourcePermission_DELETE_responses +Version_GET_responses = { + '200': Version_GET_OkResponseSchema() +} -#class OkResponseSchema(colander.MappingSchema): -# body = BaseSchema() - -# return JSON Swagger specifications of Magpie REST API on route '/magpie/__api__' -# using all Cornice Services and Schemas -@SwaggerAPI.get(tags=[APITag]) -def api_spec(request): +# use Cornice Services and Schemas to return swagger specifications +def api_schema(request): + """ + Return JSON Swagger specifications of Magpie REST API. + """ generator = CorniceSwagger(get_services()) - json_api_spec = generator('Magpie REST API', __version__) + # function docstrings are used to create the route's summary in Swagger-UI + generator.summary_docstrings = True + generator.default_security = get_security + generator.swagger = SecurityDefinitionAPI + json_api_spec = generator.generate(title=TitleAPI, version=__meta__.__version__, info=InfoAPI, base_path='/magpie') return json_api_spec - - -#def openapi_spec_generate_json(request): -# api_json = requests.post() diff --git a/magpie/api/esgf/esgfopenid.py b/magpie/api/esgf/esgfopenid.py index 65b24de23..511078b18 100644 --- a/magpie/api/esgf/esgfopenid.py +++ b/magpie/api/esgf/esgfopenid.py @@ -7,8 +7,8 @@ This providers are dependent on the |pyopenid|_ package. """ -import urllib2 import ssl +from six.moves.urllib.request import urlopen from authomatic.providers.openid import OpenID from openid.fetchers import setDefaultFetcher, Urllib2Fetcher @@ -21,10 +21,8 @@ class MyFetcher(Urllib2Fetcher): @staticmethod - def _urlopen(req): - return urllib2.urlopen(req, context=ssl._create_unverified_context()) - - urlopen = _urlopen + def urlopen(req): + return urlopen(req, context=ssl._create_unverified_context()) class ESGFOpenID(OpenID): diff --git a/magpie/api/login/login.py b/magpie/api/login/login.py index 9c2e4cb3d..340058e82 100644 --- a/magpie/api/login/login.py +++ b/magpie/api/login/login.py @@ -3,12 +3,14 @@ from authomatic import Authomatic, provider_id from security import authomatic -from definitions.pyramid_definitions import * -from definitions.ziggurat_definitions import * +from magpie.definitions.pyramid_definitions import * +from magpie.definitions.ziggurat_definitions import * from magpie import * -from api.api_requests import * -from api.api_except import * -from api.management.user.user_utils import create_user +from magpie.api.api_except import * +from magpie.api.api_requests import * +from magpie.api.api_rest_schemas import * +from magpie.api.management.user.user_formats import * +from magpie.api.management.user.user_utils import create_user import requests @@ -49,9 +51,10 @@ def sign_in_external(request): provider_name = get_value_multiformat_post_checked(request, 'provider_name') user_name = get_value_multiformat_post_checked(request, 'user_name') - verify_param(provider_name, paramCompare=providers, isIn=True, httpError=HTTPNotAcceptable, - msgOnFail="Invalid: `provider_name` not found within available providers.", - content={u'provider_name': str(provider_name), u'providers': providers}) + verify_param(provider_name, paramName=u'provider_name', paramCompare=providers, isIn=True, + httpError=HTTPNotAcceptable, content={u'provider_name': str(provider_name), u'providers': providers}, + msgOnFail="Invalid: `provider_name` not found within available providers.") + if provider_name == 'openid': query_field = dict(id=user_name) elif provider_name == 'github': @@ -68,13 +71,14 @@ def sign_in_external(request): @view_config(route_name='signin', request_method='POST', permission=NO_PERMISSION_REQUIRED) def sign_in(request): + """Signs in a user session.""" provider_name = get_value_multiformat_post_checked(request, 'provider_name') user_name = get_value_multiformat_post_checked(request, 'user_name') password = get_multiformat_post(request, 'password') # no check since password is None for external login# - verify_param(provider_name, paramCompare=providers, isIn=True, httpError=HTTPNotAcceptable, - msgOnFail="Invalid `provider_name` not found within available providers", - content={u'provider_name': str(provider_name), u'providers': providers}) + verify_param(provider_name, paramName=u'provider_name', paramCompare=providers, isIn=True, + httpError=HTTPNotAcceptable, content={u'provider_name': str(provider_name), u'providers': providers}, + msgOnFail="Invalid `provider_name` not found within available providers") if provider_name in internal_providers: signin_internal_url = '{}/signin_internal'.format(request.application_url) @@ -103,7 +107,7 @@ def login_success_ziggurat(request): def new_user_external(external_user_name, external_id, email, provider_name, db_session): - """create new user with an External Identity""" + """Create new user with an External Identity""" local_user_name = external_user_name + '_' + provider_name local_user_name = local_user_name.replace(" ", '_') create_user(local_user_name, password=None, email=email, group_name=USER_GROUP, db_session=db_session) @@ -158,7 +162,8 @@ def login_failure(request, reason=None): @view_config(context=ZigguratSignOut, permission=NO_PERMISSION_REQUIRED) -def sign_out_ziggurat(request): +def sign_out(request): + """Signs out the current user session.""" return valid_http(httpSuccess=HTTPOk, detail="Sign out successful.", httpKWArgs={'headers': forget(request)}) @@ -204,29 +209,32 @@ def authomatic_login(request): return response +@SessionAPI.get(schema=Session_GET_ResponseBodySchema(), tags=[LoginTag], response_schemas={ + '200': Session_GET_OkResponseSchema(), + '500': Session_GET_InternalServerErrorResponseSchema() +}) @view_config(route_name='session', permission=NO_PERMISSION_REQUIRED) def get_session(request): + """Get information about current session.""" def _get_session(req): authn_policy = req.registry.queryUtility(IAuthenticationPolicy) principals = authn_policy.effective_principals(req) if Authenticated in principals: user = request.user - json_resp = {u'authenticated': True, - u'user_name': user.user_name, - u'user_email': user.email, - u'group_names': [group.group_name for group in user.groups]} + json_resp = {u'authenticated': True, u'user': format_user(user)} else: json_resp = {u'authenticated': False} return json_resp session_json = evaluate_call(lambda: _get_session(request), httpError=HTTPInternalServerError, - msgOnFail="Failed to get session details") - return valid_http(httpSuccess=HTTPOk, detail="Get session successful", content=session_json) + msgOnFail=Session_GET_InternalServerErrorResponseSchema.description) + return valid_http(httpSuccess=HTTPOk, detail=Session_GET_OkResponseSchema.description, content=session_json) @view_config(route_name='providers', request_method='GET', permission=NO_PERMISSION_REQUIRED) def get_providers(request): - return valid_http(httpSuccess=HTTPOk, detail="Get providers successful", - content={u'provider_names': providers, - u'internal_providers': internal_providers, - u'external_providers': external_providers}) + """Get list of login providers.""" + return valid_http(httpSuccess=HTTPOk, detail=Providers_GET_OkResponseSchema.description, + content={u'provider_names': sorted(providers), + u'internal_providers': sorted(internal_providers), + u'external_providers': sorted(external_providers)}) diff --git a/magpie/api/management/__init__.py b/magpie/api/management/__init__.py index ec4699c8a..0b8fbadbe 100644 --- a/magpie/api/management/__init__.py +++ b/magpie/api/management/__init__.py @@ -1,5 +1,5 @@ def includeme(config): - config.include('api.management.group') - config.include('api.management.user') - config.include('api.management.service') - config.include('api.management.resource') + config.include('magpie.api.management.group') + config.include('magpie.api.management.user') + config.include('magpie.api.management.service') + config.include('magpie.api.management.resource') diff --git a/magpie/api/management/group/__init__.py b/magpie/api/management/group/__init__.py index bd62cd4d2..f9f298443 100644 --- a/magpie/api/management/group/__init__.py +++ b/magpie/api/management/group/__init__.py @@ -1,3 +1,4 @@ +from magpie.api.api_rest_schemas import * import logging logger = logging.getLogger(__name__) @@ -5,17 +6,16 @@ def includeme(config): logger.info('Adding api group ...') # Add all the rest api routes - config.add_route('groups', '/groups') - config.add_route('group', '/groups/{group_name}') - config.add_route('group_users', '/groups/{group_name}/users') - config.add_route('group_services', '/groups/{group_name}/services') - config.add_route('group_service_permissions', '/groups/{group_name}/services/{service_name}/permissions') - config.add_route('group_service_permission', '/groups/{group_name}/services/{service_name}/permissions/{permission_name}') - config.add_route('group_service_resources', '/groups/{group_name}/services/{service_name}/resources') - - config.add_route('group_resources', '/groups/{group_name}/resources') - config.add_route('group_resource_permissions', '/groups/{group_name}/resources/{resource_id}/permissions') - config.add_route('group_resource_permission', '/groups/{group_name}/resources/{resource_id}/permissions/{permission_name}') - config.add_route('group_resources_type', '/groups/{group_name}/resources/types/{resource_type}') + config.add_route(**service_api_route_info(GroupsAPI)) + config.add_route(**service_api_route_info(GroupAPI)) + config.add_route(**service_api_route_info(GroupUsersAPI)) + config.add_route(**service_api_route_info(GroupServicesAPI)) + config.add_route(**service_api_route_info(GroupServicePermissionsAPI)) + config.add_route(**service_api_route_info(GroupServicePermissionAPI)) + config.add_route(**service_api_route_info(GroupServiceResourcesAPI)) + config.add_route(**service_api_route_info(GroupResourcesAPI)) + config.add_route(**service_api_route_info(GroupResourcePermissionsAPI)) + config.add_route(**service_api_route_info(GroupResourcePermissionAPI)) + config.add_route(**service_api_route_info(GroupResourceTypesAPI)) config.scan() diff --git a/magpie/api/management/group/group_formats.py b/magpie/api/management/group/group_formats.py index 92fa73355..a997f77e1 100644 --- a/magpie/api/management/group/group_formats.py +++ b/magpie/api/management/group/group_formats.py @@ -1,4 +1,4 @@ -from api.api_requests import * +from magpie.api.api_requests import * def format_group(group, basic_info=False): @@ -18,5 +18,5 @@ def fmt_grp(grp, info): return evaluate_call( lambda: fmt_grp(group, basic_info), httpError=HTTPInternalServerError, - msgOnFail="Failed to format group", content={u'group': repr(group)} + msgOnFail="Failed to format group.", content={u'group': repr(group)} ) diff --git a/magpie/api/management/group/group_utils.py b/magpie/api/management/group/group_utils.py index bc18e28d1..b714c3bd1 100644 --- a/magpie/api/management/group/group_utils.py +++ b/magpie/api/management/group/group_utils.py @@ -1,12 +1,12 @@ from models import resource_tree_service, resource_type_dict from services import service_type_dict -from api.api_requests import * -from api.api_except import * -from api.management.resource.resource_utils import check_valid_service_resource_permission -from api.management.resource.resource_formats import format_resource -from api.management.service.service_formats import format_service_resources, format_service -from api.management.group.group_formats import format_group -from definitions.ziggurat_definitions import * +from magpie.api.api_requests import * +from magpie.api.api_except import * +from magpie.api.management.resource.resource_utils import check_valid_service_resource_permission +from magpie.api.management.resource.resource_formats import format_resource +from magpie.api.management.service.service_formats import format_service_resources, format_service +from magpie.api.management.group.group_formats import format_group +from magpie.definitions.ziggurat_definitions import * def get_all_groups(db_session): diff --git a/magpie/api/management/group/group_views.py b/magpie/api/management/group/group_views.py index e77fa076d..20816ea30 100644 --- a/magpie/api/management/group/group_views.py +++ b/magpie/api/management/group/group_views.py @@ -1,16 +1,18 @@ -from api.management.group.group_utils import * -from definitions.ziggurat_definitions import * -from definitions.pyramid_definitions import view_config +from magpie.api.management.group.group_utils import * +from magpie.definitions.ziggurat_definitions import * +from magpie.definitions.pyramid_definitions import view_config -@view_config(route_name='groups', request_method='GET') +@view_config(route_name=GroupsAPI.name, request_method='GET') def get_groups(request): + """Get list of group names.""" group_names = get_all_groups(request.db) return valid_http(httpSuccess=HTTPOk, detail="Get groups successful", content={u'group_names': group_names}) -@view_config(route_name='groups', request_method='POST') +@view_config(route_name=GroupsAPI.name, request_method='POST') def create_group(request): + """Create a group.""" group_name = get_value_multiformat_post_checked(request, 'group_name') group = GroupService.by_group_name(group_name, db_session=request.db) group_content_error = {u'group_name': str(group_name)} @@ -26,8 +28,9 @@ def create_group(request): content={u'group': format_group(new_group)}) -@view_config(route_name='group', request_method='PUT') +@view_config(route_name=GroupAPI.name, request_method='PUT') def edit_group(request): + """Update a group by name.""" group = get_group_matchdict_checked(request, group_name_key='group_name') new_group_name = get_multiformat_post(request, 'group_name') verify_param(new_group_name, notNone=True, notEmpty=True, httpError=HTTPNotAcceptable, @@ -44,24 +47,28 @@ def edit_group(request): return valid_http(httpSuccess=HTTPOk, detail="Update group successful.") -@view_config(route_name='group', request_method='DELETE') +@view_config(route_name=GroupAPI.name, request_method='DELETE') def delete_group(request): + """Delete a group by name.""" group = get_group_matchdict_checked(request) evaluate_call(lambda: request.db.delete(group), fallback=lambda: request.db.rollback(), httpError=HTTPForbidden, msgOnFail="Delete group forbidden by db") return valid_http(httpSuccess=HTTPOk, detail="Delete group successful") -@view_config(route_name='group_users', request_method='GET') +@view_config(route_name=GroupUsersAPI.name, request_method='GET') def get_group_users(request): + """List all user from a group.""" group = get_group_matchdict_checked(request) user_names = evaluate_call(lambda: [user.user_name for user in group.users], httpError=HTTPForbidden, msgOnFail="Failed to obtain group user names from db") - return valid_http(httpSuccess=HTTPOk, detail="Get group users successful", content={u'user_names': user_names}) + return valid_http(httpSuccess=HTTPOk, detail="Get group users successful", + content={u'user_names': sorted(user_names)}) -@view_config(route_name='group_services', request_method='GET') +@view_config(route_name=GroupServicesAPI.name, request_method='GET') def get_group_services_view(request): + """List all services a group has permission on.""" group = get_group_matchdict_checked(request) res_perm_dict = get_group_resources_permissions_dict(group, resource_types=[u'service'], db_session=request.db) grp_svc_json = evaluate_call(lambda: get_group_services(res_perm_dict, request.db), @@ -70,8 +77,9 @@ def get_group_services_view(request): return valid_http(httpSuccess=HTTPOk, detail="Get group services successful", content={u'services': grp_svc_json}) -@view_config(route_name='group_service_permissions', request_method='GET') +@view_config(route_name=GroupServicePermissionsAPI.name, request_method='GET') def get_group_service_permissions_view(request): + """List all permissions a group has on a specific service.""" group = get_group_matchdict_checked(request) service = get_service_matchdict_checked(request) svc_perms_found = evaluate_call(lambda: get_group_service_permissions(group, service, request.db), @@ -82,24 +90,27 @@ def get_group_service_permissions_view(request): content={u'permission_names': svc_perms_found}) -@view_config(route_name='group_service_permissions', request_method='POST') +@view_config(route_name=GroupServicePermissionsAPI.name, request_method='POST') def create_group_service_permission(request): + """Create a permission on a specific resource for a group.""" group = get_group_matchdict_checked(request) service = get_service_matchdict_checked(request) perm_name = get_permission_multiformat_post_checked(request, service) return create_group_resource_permission(perm_name, service, group, db_session=request.db) -@view_config(route_name='group_service_permission', request_method='DELETE') +@view_config(route_name=GroupServicePermissionAPI.name, request_method='DELETE') def delete_group_service_permission(request): + """Delete a permission from a specific resource for a group.""" group = get_group_matchdict_checked(request) service = get_service_matchdict_checked(request) perm_name = get_permission_matchdict_checked(request, service) return delete_group_resource_permission(perm_name, service, group, db_session=request.db) -@view_config(route_name='group_resources', request_method='GET') +@view_config(route_name=GroupResourcesAPI.name, request_method='GET') def get_group_resources_view(request): + """List all resources a group has permission on.""" group = get_group_matchdict_checked(request) grp_res_json = evaluate_call(lambda: get_group_resources(group, request.db), fallback=lambda: request.db.rollback(), httpError=HTTPInternalServerError, content={u'group': repr(group)}, @@ -107,8 +118,9 @@ def get_group_resources_view(request): return valid_http(httpSuccess=HTTPOk, detail="Get group resources successful", content={u'resources': grp_res_json}) -@view_config(route_name='group_resource_permissions', request_method='GET') +@view_config(route_name=GroupResourcePermissionsAPI.name, request_method='GET') def get_group_resource_permissions_view(request): + """List all permissions a group has on a specific resource.""" group = get_group_matchdict_checked(request) resource = get_resource_matchdict_checked(request) perm_names = get_group_resource_permissions(group, resource, db_session=request.db) @@ -116,24 +128,34 @@ def get_group_resource_permissions_view(request): content={u'permission_names': perm_names}) -@view_config(route_name='group_resource_permissions', request_method='POST') +@view_config(route_name=GroupResourcePermissionsAPI.name, request_method='POST') def create_group_resource_permission_view(request): + """Create a permission on a specific resource for a group.""" group = get_group_matchdict_checked(request) resource = get_resource_matchdict_checked(request) perm_name = get_permission_multiformat_post_checked(request, resource) return create_group_resource_permission(perm_name, resource, group, db_session=request.db) -@view_config(route_name='group_resource_permission', request_method='DELETE') +@view_config(route_name=GroupResourcePermissionAPI.name, request_method='DELETE') def delete_group_resource_permission_view(request): + """Delete a permission from a specific resource for a group.""" group = get_group_matchdict_checked(request) resource = get_resource_matchdict_checked(request) perm_name = get_permission_matchdict_checked(request, resource) return delete_group_resource_permission(perm_name, resource, group, db_session=request.db) -@view_config(route_name='group_service_resources', request_method='GET') +@GroupServiceResourcesAPI.get(tags=[GroupsTag], response_schemas={ + '200': GroupServiceResources_GET_OkResponseSchema(), + '401': UnauthorizedResponseSchema(), + '403': Group_MatchDictCheck_ForbiddenResponseSchema(), + '404': Group_MatchDictCheck_NotFoundResponseSchema(), + '422': UnprocessableEntityResponseSchema() +}) +@view_config(route_name=GroupServiceResourcesAPI.name, request_method='GET') def get_group_service_resources_view(request): + """List all resources under a service a group has permission on.""" group = get_group_matchdict_checked(request) service = get_service_matchdict_checked(request) svc_perms = get_group_service_permissions(group=group, service=service, db_session=request.db) @@ -145,5 +167,5 @@ def get_group_service_resources_view(request): resources_perms_dict=res_perms, display_all=False ) - return valid_http(httpSuccess=HTTPOk, detail="Get group service resources successful", + return valid_http(httpSuccess=HTTPOk, detail=GroupServiceResources_GET_OkResponseSchema.description, content={u'service': svc_res_json}) diff --git a/magpie/api/management/resource/__init__.py b/magpie/api/management/resource/__init__.py index b390d4f9f..9e6e96e21 100644 --- a/magpie/api/management/resource/__init__.py +++ b/magpie/api/management/resource/__init__.py @@ -1,3 +1,4 @@ +from magpie.api.api_rest_schemas import * import logging logger = logging.getLogger(__name__) @@ -6,8 +7,8 @@ def includeme(config): logger.info('Adding api resource ...') # Add all the rest api routes - config.add_route('resources', '/resources') - config.add_route('resource', '/resources/{resource_id}') - config.add_route('resource_permissions', '/resources/{resource_id}/permissions') + config.add_route(**service_api_route_info(ResourcesAPI)) + config.add_route(**service_api_route_info(ResourceAPI)) + config.add_route(**service_api_route_info(ResourcePermissionsAPI)) config.scan() diff --git a/magpie/api/management/resource/resource_formats.py b/magpie/api/management/resource/resource_formats.py index 414768feb..c7cf11a25 100644 --- a/magpie/api/management/resource/resource_formats.py +++ b/magpie/api/management/resource/resource_formats.py @@ -1,7 +1,7 @@ -from definitions.pyramid_definitions import * +from magpie.definitions.pyramid_definitions import * from models import resource_tree_service, Service from services import service_type_dict -from api.api_except import evaluate_call +from magpie.api.api_except import evaluate_call def format_resource(resource, permissions=None, basic_info=False): @@ -19,14 +19,14 @@ def fmt_res(res, perms, info): u'parent_id': res.parent_id, u'root_service_id': res.root_service_id, u'children': {}, - u'permission_names': list() if perms is None else perms + u'permission_names': list() if perms is None else sorted(perms) } return evaluate_call( lambda: fmt_res(resource, permissions, basic_info), httpError=HTTPInternalServerError, - msgOnFail="Failed to format resource", - content={u'service': repr(resource), u'permissions': repr(permissions), u'basic_info': str(basic_info)} + msgOnFail="Failed to format resource.", + content={u'resource': repr(resource), u'permissions': repr(permissions), u'basic_info': str(basic_info)} ) diff --git a/magpie/api/management/resource/resource_utils.py b/magpie/api/management/resource/resource_utils.py index f0fb59eab..c1499a9cf 100644 --- a/magpie/api/management/resource/resource_utils.py +++ b/magpie/api/management/resource/resource_utils.py @@ -1,10 +1,14 @@ import models +from common import * from models import resource_factory, resource_type_dict, resource_tree_service from services import service_type_dict -from definitions.pyramid_definitions import * -from definitions.ziggurat_definitions import * -from api.api_except import verify_param, evaluate_call, raise_http, valid_http -from api.management.resource.resource_formats import format_resource +from register import sync_services_phoenix +from magpie.definitions.pyramid_definitions import * +from magpie.definitions.ziggurat_definitions import * +from magpie.api.api_rest_schemas import * +from magpie.api.api_requests import * +from magpie.api.api_except import verify_param, evaluate_call, raise_http, valid_http +from magpie.api.management.resource.resource_formats import format_resource def check_valid_service_resource_permission(permission_name, service_resource, db_session): @@ -18,8 +22,10 @@ def check_valid_service_resource_permission(permission_name, service_resource, d svc_res_perms = get_resource_permissions(service_resource, db_session=db_session) svc_res_type = service_resource.resource_type svc_res_name = service_resource.resource_name - verify_param(permission_name, paramCompare=svc_res_perms, isIn=True, httpError=HTTPBadRequest, - msgOnFail="Permission not allowed for {0} `{1}`".format(svc_res_type, svc_res_name)) + verify_param(permission_name, paramName=u'permission_name', paramCompare=svc_res_perms, isIn=True, + httpError=HTTPBadRequest, + content={u'resource_type': str(svc_res_type), u'resource_name': str(svc_res_name)}, + msgOnFail=UserResourcePermissions_POST_BadRequestResponseSchema.description) def check_valid_service_resource(parent_resource, resource_type, db_session): @@ -39,13 +45,14 @@ def check_valid_service_resource(parent_resource, resource_type, db_session): root_service = get_resource_root_service(parent_resource, db_session=db_session) verify_param(root_service, notNone=True, httpError=HTTPInternalServerError, msgOnFail="Failed retrieving `root_service` from db") - verify_param(root_service.resource_type, isEqual=True, httpError=HTTPInternalServerError, paramCompare=u'service', + verify_param(root_service.resource_type, isEqual=True, httpError=HTTPInternalServerError, + paramName=u'resource_type', paramCompare=u'service', msgOnFail="Invalid `root_service` retrieved from db is not a service") verify_param(service_type_dict[root_service.type].child_resource_allowed, isEqual=True, paramCompare=True, httpError=HTTPNotAcceptable, msgOnFail="Child resource not allowed for specified service type `{}`".format(root_service.type)) verify_param(resource_type, isIn=True, httpError=HTTPNotAcceptable, - paramCompare=service_type_dict[root_service.type].resource_types, + paramName=u'resource_type', paramCompare=service_type_dict[root_service.type].resource_types, msgOnFail="Invalid `resource_type` specified for service type `{}`".format(root_service.type)) return root_service @@ -84,8 +91,8 @@ def get_service_or_resource_types(service_resource): def get_resource_permissions(resource, db_session): - verify_param(resource, notNone=True, httpError=HTTPNotAcceptable, - msgOnFail="Invalid `resource` specified for resource permission retrieval") + verify_param(resource, notNone=True, httpError=HTTPNotAcceptable, paramName=u'resource', + msgOnFail=UserResourcePermissions_GET_NotAcceptableResourceResponseSchema.description) # directly access the service resource if resource.root_service_id is None: service = resource @@ -93,12 +100,13 @@ def get_resource_permissions(resource, db_session): # otherwise obtain root level service to infer sub-resource permissions service = models.Service.by_resource_id(resource.root_service_id, db_session=db_session) - verify_param(service.resource_type, isEqual=True, paramCompare=u'service', httpError=HTTPNotAcceptable, - msgOnFail="Invalid `root_service` specified for resource permission retrieval") + verify_param(service.resource_type, isEqual=True, httpError=HTTPNotAcceptable, + paramName=u'resource_type', paramCompare=u'service', + msgOnFail=UserResourcePermissions_GET_NotAcceptableRootServiceResponseSchema.description) service_obj = service_type_dict[service.type] - verify_param(resource.resource_type, isIn=True, paramCompare=service_obj.resource_types, - httpError=HTTPNotAcceptable, - msgOnFail="Invalid `resource_type` for corresponding service resource permission retrieval") + verify_param(resource.resource_type, isIn=True, httpError=HTTPNotAcceptable, + paramName=u'resource_type', paramCompare=service_obj.resource_types, + msgOnFail=UserResourcePermissions_GET_NotAcceptableResourceTypeResponseSchema.description) return service_obj.resource_types_permissions[resource.resource_type] @@ -119,15 +127,15 @@ def get_resource_root_service(resource, db_session): def create_resource(resource_name, resource_type, parent_id, db_session): - verify_param(resource_name, notNone=True, notEmpty=True, httpError=HTTPBadRequest, - msgOnFail="Invalid `resource_name` '" + str(resource_name) + "' specified for child resource creation") - verify_param(resource_type, notNone=True, notEmpty=True, httpError=HTTPBadRequest, - msgOnFail="Invalid `resource_type` '" + str(resource_type) + "' specified for child resource creation") - verify_param(parent_id, notNone=True, notEmpty=True, httpError=HTTPBadRequest, - msgOnFail="Invalid `parent_id` '" + str(parent_id) + "' specified for child resource creation") + verify_param(resource_name, paramName=u'resource_name', notNone=True, notEmpty=True, httpError=HTTPBadRequest, + msgOnFail="Invalid `resource_name` specified for child resource creation.") + verify_param(resource_type, paramName=u'resource_type', notNone=True, notEmpty=True, httpError=HTTPBadRequest, + msgOnFail="Invalid `resource_type` specified for child resource creation.") + verify_param(parent_id, paramName=u'parent_id', notNone=True, notEmpty=True, httpError=HTTPBadRequest, + msgOnFail="Invalid `parent_id` specified for child resource creation.") parent_resource = evaluate_call(lambda: ResourceService.by_resource_id(parent_id, db_session=db_session), fallback=lambda: db_session.rollback(), httpError=HTTPNotFound, - msgOnFail="Could not find specified resource parent id", + msgOnFail=Resources_POST_NotFoundResponseSchema.description, content={u'parent_id': str(parent_id), u'resource_name': str(resource_name), u'resource_type': str(resource_type)}) @@ -142,8 +150,8 @@ def create_resource(resource_name, resource_type, parent_id, db_session): tree_struct = resource_tree_service.from_parent_deeper(parent_id, limit_depth=1, db_session=db_session) tree_struct_dict = resource_tree_service.build_subtree_strut(tree_struct) direct_children = tree_struct_dict[u'children'] - verify_param(resource_name, notIn=True, httpError=HTTPConflict, - msgOnFail="Resource name already exists at requested tree level for creation", + verify_param(resource_name, paramName=u'resource_name', notIn=True, httpError=HTTPConflict, + msgOnFail=Resources_POST_ConflictResponseSchema.description, paramCompare=[child_dict[u'node'].resource_name for child_dict in direct_children.values()]) def add_resource_in_tree(new_res, db): @@ -153,6 +161,27 @@ def add_resource_in_tree(new_res, db): evaluate_call(lambda: add_resource_in_tree(new_resource, db_session), fallback=lambda: db_session.rollback(), - httpError=HTTPForbidden, msgOnFail="Failed to insert new resource in service tree using parent id") - return valid_http(httpSuccess=HTTPCreated, detail="Create resource successful", - content=format_resource(new_resource, basic_info=True)) + httpError=HTTPForbidden, msgOnFail=Resources_POST_ForbiddenResponseSchema.description) + return valid_http(httpSuccess=HTTPCreated, detail=Resources_POST_OkResponseSchema.description, + content={u'resource': format_resource(new_resource, basic_info=True)}) + + +def delete_resource(request): + resource = get_resource_matchdict_checked(request) + service_push = str2bool(get_multiformat_post(request, 'service_push')) + res_content = {u'resource': format_resource(resource, basic_info=True)} + evaluate_call(lambda: resource_tree_service.delete_branch(resource_id=resource.resource_id, db_session=request.db), + fallback=lambda: request.db.rollback(), httpError=HTTPForbidden, + msgOnFail="Delete resource branch from tree service failed.", content=res_content) + + def remove_service_magpie_and_phoenix(res, svc_push, db): + if res.resource_type != 'service': + svc_push = False + db.delete(res) + if svc_push: + sync_services_phoenix(db.query(models.Service)) + + evaluate_call(lambda: remove_service_magpie_and_phoenix(resource, service_push, request.db), + fallback=lambda: request.db.rollback(), httpError=HTTPForbidden, + msgOnFail=Resource_DELETE_ForbiddenResponseSchema.description, content=res_content) + return valid_http(httpSuccess=HTTPOk, detail=Resource_DELETE_OkResponseSchema.description) diff --git a/magpie/api/management/resource/resource_views.py b/magpie/api/management/resource/resource_views.py index c6e3c6a1f..a6362eb69 100644 --- a/magpie/api/management/resource/resource_views.py +++ b/magpie/api/management/resource/resource_views.py @@ -1,16 +1,18 @@ -from api.api_requests import * -from api.management.service.service_utils import get_services_by_type -from api.management.service.service_formats import format_service_resources -from api.management.resource.resource_utils import * -from api.management.resource.resource_formats import * -from definitions.pyramid_definitions import view_config +from magpie.api.api_requests import * +from magpie.api.management.service.service_utils import get_services_by_type +from magpie.api.management.service.service_formats import format_service_resources +from magpie.api.management.resource.resource_utils import * +from magpie.api.management.resource.resource_formats import * +from magpie.definitions.pyramid_definitions import view_config from common import str2bool from register import sync_services_phoenix from services import service_type_dict -@view_config(route_name='resources', request_method='GET') +@ResourcesAPI.get(tags=[ResourcesTag], response_schemas=Resources_GET_responses) +@view_config(route_name=ResourcesAPI.name, request_method='GET') def get_resources_view(request): + """List all registered resources.""" res_json = {} for svc_type in service_type_dict.keys(): services = get_services_by_type(svc_type, db_session=request.db) @@ -18,52 +20,45 @@ def get_resources_view(request): for svc in services: res_json[svc_type][svc.resource_name] = format_service_resources(svc, request.db, display_all=True) res_json = {u'resources': res_json} - return valid_http(httpSuccess=HTTPOk, detail="Get resources successful", content=res_json) + return valid_http(httpSuccess=HTTPOk, detail=Resources_GET_OkResponseSchema.description, content=res_json) -@view_config(route_name='resource', request_method='GET') +@ResourceAPI.get(tags=[ResourcesTag], response_schemas=Resource_GET_responses) +@view_config(route_name=ResourceAPI.name, request_method='GET') def get_resource_view(request): + """Get resource information.""" resource = get_resource_matchdict_checked(request) res_json = evaluate_call(lambda: format_resource_with_children(resource, db_session=request.db), fallback=lambda: request.db.rollback(), httpError=HTTPInternalServerError, - msgOnFail="Failed building resource children json formatted tree", - content=format_resource(resource, basic_info=True)) - return valid_http(httpSuccess=HTTPOk, detail="Get resource successful", content={resource.resource_id: res_json}) + msgOnFail=Resource_GET_InternalServerErrorResponseSchema.description, + content={u'resource': format_resource(resource, basic_info=True)}) + return valid_http(httpSuccess=HTTPOk, detail=Resource_GET_OkResponseSchema.description, + content={resource.resource_id: res_json}) -@view_config(route_name='resources', request_method='POST') +@ResourcesAPI.post(schema=Resources_POST_RequestBodySchema, tags=[ResourcesTag], + response_schemas=Resources_POST_responses) +@view_config(route_name=ResourcesAPI.name, request_method='POST') def create_resource_view(request): + """Register a new resource.""" resource_name = get_value_multiformat_post_checked(request, 'resource_name') resource_type = get_value_multiformat_post_checked(request, 'resource_type') parent_id = get_value_multiformat_post_checked(request, 'parent_id') return create_resource(resource_name, resource_type, parent_id, request.db) -@view_config(route_name='service_resource', request_method='DELETE') -@view_config(route_name='resource', request_method='DELETE') -def delete_resources(request): - resource = get_resource_matchdict_checked(request) - service_push = str2bool(get_multiformat_post(request, 'service_push')) - res_content = format_resource(resource, basic_info=True) - evaluate_call(lambda: resource_tree_service.delete_branch(resource_id=resource.resource_id, db_session=request.db), - fallback=lambda: request.db.rollback(), httpError=HTTPForbidden, - msgOnFail="Delete resource branch from tree service failed", content=res_content) +@ResourceAPI.delete(schema=Resource_DELETE_RequestSchema(), tags=[ResourcesTag], + response_schemas=Resources_DELETE_responses) +@view_config(route_name=ResourceAPI.name, request_method='DELETE') +def delete_resource_view(request): + """Unregister a resource.""" + return delete_resource(request) - def remove_service_magpie_and_phoenix(res, svc_push, db): - if res.resource_type != 'service': - svc_push = False - db.delete(res) - if svc_push: - sync_services_phoenix(db.query(models.Service)) - - evaluate_call(lambda: remove_service_magpie_and_phoenix(resource, service_push, request.db), - fallback=lambda: request.db.rollback(), httpError=HTTPForbidden, - msgOnFail="Delete resource from db failed", content=res_content) - return valid_http(httpSuccess=HTTPOk, detail="Delete resource successful") - -@view_config(route_name='resource', request_method='PUT') +@ResourceAPI.put(schema=Resource_PUT_RequestSchema(), tags=[ResourcesTag], response_schemas=Resource_PUT_responses) +@view_config(route_name=ResourceAPI.name, request_method='PUT') def update_resource(request): + """Update a resource information.""" resource = get_resource_matchdict_checked(request, 'resource_id') service_push = str2bool(get_multiformat_post(request, 'service_push')) res_old_name = resource.resource_name @@ -77,21 +72,23 @@ def rename_service_magpie_and_phoenix(res, new_name, svc_push, db): sync_services_phoenix(db.query(models.Service)) evaluate_call(lambda: rename_service_magpie_and_phoenix(resource, res_new_name, service_push, request.db), - fallback=lambda: request.db.rollback(), - msgOnFail="Failed to update resource with new name", + fallback=lambda: request.db.rollback(), httpError=HTTPForbidden, + msgOnFail=Resource_PUT_ForbiddenResponseSchema.description, content={u'resource_id': resource.resource_id, u'resource_name': resource.resource_name, u'old_resource_name': res_old_name, u'new_resource_name': res_new_name}) - return valid_http(httpSuccess=HTTPOk, detail="Update resource successful", + return valid_http(httpSuccess=HTTPOk, detail=Resource_PUT_OkResponseSchema.description, content={u'resource_id': resource.resource_id, u'resource_name': resource.resource_name, u'old_resource_name': res_old_name, u'new_resource_name': res_new_name}) -@view_config(route_name='resource_permissions', request_method='GET') +@ResourcePermissionsAPI.get(tags=[ResourcesTag], response_schemas=ResourcePermissions_GET_responses) +@view_config(route_name=ResourcePermissionsAPI.name, request_method='GET') def get_resource_permissions_view(request): + """List all applicable permissions for a resource.""" resource = get_resource_matchdict_checked(request, 'resource_id') res_perm = evaluate_call(lambda: get_resource_permissions(resource, db_session=request.db), fallback=lambda: request.db.rollback(), httpError=HTTPNotAcceptable, - msgOnFail="Invalid resource type to extract permissions", - content=format_resource(resource, basic_info=True)) - return valid_http(httpSuccess=HTTPOk, detail="Get resource permissions successful", - content={u'permission_names': res_perm}) + msgOnFail=ResourcePermissions_GET_NotAcceptableResponseSchema.description, + content={u'resource': format_resource(resource, basic_info=True)}) + return valid_http(httpSuccess=HTTPOk, detail=ResourcePermissions_GET_OkResponseSchema.description, + content={u'permission_names': sorted(res_perm)}) diff --git a/magpie/api/management/service/__init__.py b/magpie/api/management/service/__init__.py index 625aed110..e459959b7 100644 --- a/magpie/api/management/service/__init__.py +++ b/magpie/api/management/service/__init__.py @@ -1,3 +1,4 @@ +from magpie.api.api_rest_schemas import * import logging logger = logging.getLogger(__name__) @@ -6,12 +7,12 @@ def includeme(config): logger.info('Adding api service ...') # Add all the rest api routes - config.add_route('services', '/services') - config.add_route('service', '/services/{service_name}') - config.add_route('services_type', '/services/types/{service_type}') - config.add_route('service_permissions', '/services/{service_name}/permissions') - config.add_route('service_resources', '/services/{service_name}/resources') - config.add_route('service_resource', '/services/{service_name}/resources/{resource_id}') - config.add_route('service_type_resource_types', '/services/types/{service_type}/resources/types') + config.add_route(**service_api_route_info(ServicesAPI)) + config.add_route(**service_api_route_info(ServiceAPI)) + config.add_route(**service_api_route_info(ServiceTypesAPI)) + config.add_route(**service_api_route_info(ServicePermissionsAPI)) + config.add_route(**service_api_route_info(ServiceResourcesAPI)) + config.add_route(**service_api_route_info(ServiceResourceAPI)) + config.add_route(**service_api_route_info(ServiceResourceTypesAPI)) config.scan() diff --git a/magpie/api/management/service/service_formats.py b/magpie/api/management/service/service_formats.py index 62f743954..015fa2a6d 100644 --- a/magpie/api/management/service/service_formats.py +++ b/magpie/api/management/service/service_formats.py @@ -1,9 +1,9 @@ from register import get_twitcher_protected_service_url from services import service_type_dict -from definitions.pyramid_definitions import * -from api.api_except import evaluate_call -from api.management.resource.resource_utils import crop_tree_with_permission -from api.management.resource.resource_formats import get_resource_children, format_resource_tree +from magpie.definitions.pyramid_definitions import * +from magpie.api.api_except import evaluate_call +from magpie.api.management.resource.resource_utils import crop_tree_with_permission +from magpie.api.management.resource.resource_formats import get_resource_children, format_resource_tree def format_service(service, permissions=None): @@ -14,13 +14,13 @@ def fmt_svc(svc, perms): u'service_name': str(svc.resource_name), u'service_type': str(svc.type), u'resource_id': svc.resource_id, - u'permission_names': service_type_dict[svc.type].permission_names if perms is None else perms + u'permission_names': sorted(service_type_dict[svc.type].permission_names if perms is None else perms) } return evaluate_call( lambda: fmt_svc(service, permissions), httpError=HTTPInternalServerError, - msgOnFail="Failed to format service", + msgOnFail="Failed to format service.", content={u'service': repr(service), u'permissions': repr(permissions)} ) diff --git a/magpie/api/management/service/service_utils.py b/magpie/api/management/service/service_utils.py index dfcde4179..1d7617748 100644 --- a/magpie/api/management/service/service_utils.py +++ b/magpie/api/management/service/service_utils.py @@ -1,9 +1,9 @@ from magpie import models from register import SERVICES_PHOENIX_ALLOWED -from definitions.ziggurat_definitions import * +from magpie.definitions.ziggurat_definitions import * from services import service_type_dict -from api.api_except import * -from api.management.group.group_utils import create_group_resource_permission +from magpie.api.api_except import * +from magpie.api.management.group.group_utils import create_group_resource_permission import os diff --git a/magpie/api/management/service/service_views.py b/magpie/api/management/service/service_views.py index f65a01c7a..5f16c7a50 100644 --- a/magpie/api/management/service/service_views.py +++ b/magpie/api/management/service/service_views.py @@ -1,24 +1,37 @@ -from api.management.resource.resource_utils import create_resource -from api.management.service.service_formats import * -from api.management.service.service_utils import * -from api.api_requests import * -from definitions.pyramid_definitions import view_config +from magpie.api.management.resource.resource_utils import create_resource, delete_resource +from magpie.api.management.service.service_formats import * +from magpie.api.management.service.service_utils import * +from magpie.api.api_requests import * +from magpie.api.api_rest_schemas import * +from magpie.definitions.pyramid_definitions import view_config from common import str2bool from register import sync_services_phoenix from models import resource_tree_service from services import service_type_dict -@view_config(route_name='services_type', request_method='GET') -@view_config(route_name='services', request_method='GET') +@ServiceTypesAPI.get(tags=[ServicesTag], response_schemas=ServiceTypes_GET_responses) +@view_config(route_name=ServiceTypesAPI.name, request_method='GET') +def get_services_by_type_view(request): + """List all registered services from a specific type.""" + return get_services_runner(request) + + +@ServicesAPI.get(tags=[ServicesTag], response_schemas=Services_GET_responses) +@view_config(route_name=ServicesAPI.name, request_method='GET') def get_services_view(request): + """List all registered services.""" + return get_services_runner(request) + + +def get_services_runner(request): service_type_filter = request.matchdict.get('service_type') # no check because None/empty is for 'all services' json_response = {} if not service_type_filter: service_types = service_type_dict.keys() else: verify_param(service_type_filter, paramCompare=service_type_dict.keys(), isIn=True, httpError=HTTPNotAcceptable, - msgOnFail="Invalid `service_type` value does not correspond to any of the existing service types", + msgOnFail=Services_GET_NotAcceptableResponseSchema.description, content={u'service_type': str(service_type_filter)}, contentType='application/json') service_types = [service_type_filter] @@ -28,27 +41,32 @@ def get_services_view(request): for service in services: json_response[service_type][service.resource_name] = format_service(service) - return valid_http(httpSuccess=HTTPOk, detail="Get services successful", content={u'services': json_response}) + return valid_http(httpSuccess=HTTPOk, detail=Services_GET_OkResponseSchema.description, + content={u'services': json_response}) -@view_config(route_name='services', request_method='POST') +@ServicesAPI.post(schema=Services_POST_RequestBodySchema(), tags=[ServicesTag], + response_schemas=Services_POST_responses) +@view_config(route_name=ServicesAPI.name, request_method='POST') def register_service(request): + """Registers a new service.""" service_name = get_value_multiformat_post_checked(request, 'service_name') service_url = get_value_multiformat_post_checked(request, 'service_url') service_type = get_value_multiformat_post_checked(request, 'service_type') service_push = str2bool(get_multiformat_post(request, 'service_push')) verify_param(service_type, isIn=True, paramCompare=service_type_dict.keys(), httpError=HTTPBadRequest, - msgOnFail="Specified `service_type` value does not correspond to any of the available types.") + msgOnFail=Services_POST_BadRequestResponseSchema.description) if models.Service.by_service_name(service_name, db_session=request.db): verify_param(service_name, notIn=True, httpError=HTTPConflict, paramCompare=[models.Service.by_service_name(service_name, db_session=request.db).resource_name], - msgOnFail="Specified `service_name` value '" + str(service_name) + "' already exists.") + msgOnFail=Services_POST_ConflictResponseSchema.description, + content={u'service_name': str(service_name)}) service = evaluate_call(lambda: models.Service(resource_name=str(service_name), resource_type=u'service', url=str(service_url), type=str(service_type)), fallback=lambda: request.db.rollback(), httpError=HTTPForbidden, - msgOnFail="Service creation for registration failed", + msgOnFail="Service creation for registration failed.", content={u'service_name': str(service_name), u'resource_type': u'service', u'service_url': str(service_url), u'service_type': str(service_type)}) @@ -59,15 +77,17 @@ def add_service_magpie_and_phoenix(svc, svc_push, db): evaluate_call(lambda: add_service_magpie_and_phoenix(service, service_push, request.db), fallback=lambda: request.db.rollback(), httpError=HTTPForbidden, - msgOnFail="Service registration forbidden by db", content=format_service(service)) - return valid_http(httpSuccess=HTTPCreated, detail="Service registration to db successful", - content=format_service(service)) + msgOnFail=Services_POST_ForbiddenResponseSchema.description, content=format_service(service)) + return valid_http(httpSuccess=HTTPCreated, detail=Services_POST_CreatedResponseSchema.description, + content={u'service': format_service(service)}) -@view_config(route_name='service', request_method='PUT') +@ServiceAPI.put(schema=Service_PUT_RequestBodySchema(), tags=[ServicesTag], response_schemas=Service_PUT_responses) +@view_config(route_name=ServiceAPI.name, request_method='PUT') def update_service(request): + """Update a service information.""" service = get_service_matchdict_checked(request) - service_push = str2bool(get_multiformat_post(request, 'service_push')) + service_push = str2bool(get_multiformat_post(request, 'service_push', default=False)) def select_update(new_value, old_value): return new_value if new_value is not None and not new_value == '' else old_value @@ -76,7 +96,7 @@ def select_update(new_value, old_value): svc_name = select_update(get_multiformat_post(request, 'service_name'), service.resource_name) svc_url = select_update(get_multiformat_post(request, 'service_url'), service.url) verify_param(svc_name == service.resource_name and svc_url == service.url, notEqual=True, paramCompare=True, - httpError=HTTPBadRequest, msgOnFail="Current service values are already equal to update values") + httpError=HTTPBadRequest, msgOnFail=Service_PUT_BadRequestResponseSchema.description) if svc_name != service.resource_name: all_svc_names = list() @@ -84,7 +104,8 @@ def select_update(new_value, old_value): for svc in get_services_by_type(svc_type, db_session=request.db): all_svc_names.extend(svc.resource_name) verify_param(svc_name, notIn=True, paramCompare=all_svc_names, httpError=HTTPConflict, - msgOnFail="Specified `service_name` value '" + str(svc_name) + "' already exists") + msgOnFail=Service_PUT_ConflictResponseSchema.description, + content={u'service_name': str(svc_name)}) def update_service_magpie_and_phoenix(svc, new_name, new_url, svc_push, db_session): svc.resource_name = new_name @@ -99,26 +120,31 @@ def update_service_magpie_and_phoenix(svc, new_name, new_url, svc_push, db_sessi err_svc_content = {u'service': old_svc_content, u'new_service_name': svc_name, u'new_service_url': svc_url} evaluate_call(lambda: update_service_magpie_and_phoenix(service, svc_name, svc_url, service_push, request.db), fallback=lambda: request.db.rollback(), - httpError=HTTPForbidden, msgOnFail="Update service failed during value assignment", + httpError=HTTPForbidden, msgOnFail=Service_PUT_ForbiddenResponseSchema.description, content=err_svc_content) - return valid_http(httpSuccess=HTTPOk, detail="Update service successful", content=format_service(service)) + return valid_http(httpSuccess=HTTPOk, detail=Service_PUT_OkResponseSchema.description, + content={u'service': format_service(service)}) -@view_config(route_name='service', request_method='GET') +@ServiceAPI.get(tags=[ServicesTag], response_schemas=Service_GET_responses) +@view_config(route_name=ServiceAPI.name, request_method='GET') def get_service(request): + """Get a service information.""" service = get_service_matchdict_checked(request) - return valid_http(httpSuccess=HTTPOk, detail="Get service successful", - content={str(service.resource_name): format_service(service)}) + return valid_http(httpSuccess=HTTPOk, detail=Service_GET_OkResponseSchema.name, + content={service.resource_name: format_service(service)}) -@view_config(route_name='service', request_method='DELETE') +@ServiceAPI.delete(schema=Service_DELETE_RequestSchema(), tags=[ServicesTag], response_schemas=Service_DELETE_responses) +@view_config(route_name=ServiceAPI.name, request_method='DELETE') def unregister_service(request): + """Unregister a service.""" service = get_service_matchdict_checked(request) - service_push = str2bool(get_multiformat_delete(request, 'service_push')) + service_push = str2bool(get_multiformat_delete(request, 'service_push', default=False)) svc_content = format_service(service) evaluate_call(lambda: resource_tree_service.delete_branch(resource_id=service.resource_id, db_session=request.db), fallback=lambda: request.db.rollback(), httpError=HTTPForbidden, - msgOnFail="Delete service from resource tree failed", content=svc_content) + msgOnFail="Delete service from resource tree failed.", content=svc_content) def remove_service_magpie_and_phoenix(svc, svc_push, db_session): db_session.delete(svc) @@ -127,31 +153,46 @@ def remove_service_magpie_and_phoenix(svc, svc_push, db_session): evaluate_call(lambda: remove_service_magpie_and_phoenix(service, service_push, request.db), fallback=lambda: request.db.rollback(), httpError=HTTPForbidden, - msgOnFail="Delete service from db failed", content=svc_content) - return valid_http(httpSuccess=HTTPOk, detail="Delete service successful") + msgOnFail=Service_DELETE_ForbiddenResponseSchema.description, content=svc_content) + return valid_http(httpSuccess=HTTPOk, detail=Service_DELETE_OkResponseSchema.description) -@view_config(route_name='service_permissions', request_method='GET') +@ServicePermissionsAPI.get(tags=[ServicesTag], response_schemas=ServicePermissions_GET_responses) +@view_config(route_name=ServicePermissionsAPI.name, request_method='GET') def get_service_permissions(request): + """List all applicable permissions for a service.""" service = get_service_matchdict_checked(request) svc_content = format_service(service) - svc_perms = evaluate_call(lambda: service_type_dict[service.type].permission_names, fallback=request.db.rollback(), - httpError=HTTPNotAcceptable, msgOnFail="Invalid service type specified by service", - content=svc_content) - return valid_http(httpSuccess=HTTPOk, detail="Get service permissions successful", - content={u'permission_names': svc_perms}) + svc_perms = evaluate_call(lambda: service_type_dict[service.type].permission_names, + fallback=request.db.rollback(), httpError=HTTPNotAcceptable, content=svc_content, + msgOnFail=ServicePermissions_GET_NotAcceptableResponseSchema.description) + return valid_http(httpSuccess=HTTPOk, detail=ServicePermissions_GET_OkResponseSchema.description, + content={u'permission_names': sorted(svc_perms)}) + + +@ServiceResourceAPI.delete(schema=ServiceResource_DELETE_RequestSchema(), tags=[ServicesTag], + response_schemas=ServiceResource_DELETE_responses) +@view_config(route_name=ServiceResourceAPI.name, request_method='DELETE') +def delete_service_resource_view(request): + """Unregister a resource.""" + return delete_resource(request) -@view_config(route_name='service_resources', request_method='GET') +@ServiceResourcesAPI.get(tags=[ServicesTag], response_schemas=ServiceResources_GET_responses) +@view_config(route_name=ServiceResourcesAPI.name, request_method='GET') def get_service_resources_view(request): + """List all resources registered under a service.""" service = get_service_matchdict_checked(request) svc_res_json = format_service_resources(service, db_session=request.db, display_all=True) - return valid_http(httpSuccess=HTTPOk, detail="Get service resources successful", + return valid_http(httpSuccess=HTTPOk, detail=ServiceResources_GET_OkResponseSchema.description, content={str(service.resource_name): svc_res_json}) -@view_config(route_name='service_resources', request_method='POST') +@ServiceResourcesAPI.post(schema=ServiceResources_POST_RequestBodySchema, tags=[ServicesTag], + response_schemas=ServiceResources_POST_responses) +@view_config(route_name=ServiceResourcesAPI.name, request_method='POST') def create_service_direct_resource(request): + """Register a new resource directly under a service.""" service = get_service_matchdict_checked(request) resource_name = get_multiformat_post(request, 'resource_name') resource_type = get_multiformat_post(request, 'resource_type') @@ -161,13 +202,15 @@ def create_service_direct_resource(request): return create_resource(resource_name, resource_type, parent_id=parent_id, db_session=request.db) -@view_config(route_name='service_type_resource_types', request_method='GET') +@ServiceResourceTypesAPI.get(tags=[ServicesTag], response_schemas=ServiceResource_GET_responses) +@view_config(route_name=ServiceResourceTypesAPI.name, request_method='GET') def get_service_type_resource_types(request): + """List all resources under a specific service type.""" service_type = get_value_matchdict_checked(request, 'service_type') verify_param(service_type, paramCompare=service_type_dict.keys(), isIn=True, httpError=HTTPNotFound, - msgOnFail="Invalid `service_type` does not exist to obtain its resource types") + msgOnFail=ServiceResourceTypes_GET_NotFoundResponseSchema.description) resource_types = evaluate_call(lambda: service_type_dict[service_type].resource_types, httpError=HTTPForbidden, content={u'service_type': str(service_type)}, - msgOnFail="Failed to obtain resource types for specified service type") - return valid_http(httpSuccess=HTTPOk, detail="Get service type resource types successful", + msgOnFail=ServiceResourceTypes_GET_ForbiddenResponseSchema.description) + return valid_http(httpSuccess=HTTPOk, detail=ServiceResourceTypes_GET_OkResponseSchema.description, content={u'resource_types': resource_types}) diff --git a/magpie/api/management/user/__init__.py b/magpie/api/management/user/__init__.py index 1b3bcf702..00a999174 100644 --- a/magpie/api/management/user/__init__.py +++ b/magpie/api/management/user/__init__.py @@ -1,3 +1,4 @@ +from magpie.api.api_rest_schemas import * import logging logger = logging.getLogger(__name__) @@ -6,22 +7,39 @@ def includeme(config): logger.info('Adding api user ...') # Add all the rest api routes - config.add_route('users', '/users') - config.add_route('user', '/users/{user_name}') - config.add_route('user_groups', 'users/{user_name}/groups') - config.add_route('user_group', '/users/{user_name}/groups/{group_name}') - config.add_route('user_services', '/users/{user_name}/services') - config.add_route('user_inherited_services', '/users/{user_name}/inherited_services') - config.add_route('user_service_permissions', '/users/{user_name}/services/{service_name}/permissions') - config.add_route('user_service_permission', '/users/{user_name}/services/{service_name}/permissions/{permission_name}') - config.add_route('user_service_inherited_permissions', '/users/{user_name}/services/{service_name}/inherited_permissions') - config.add_route('user_service_resources', '/users/{user_name}/services/{service_name}/resources') - config.add_route('user_service_inherited_resources', '/users/{user_name}/services/{service_name}/inherited_resources') - config.add_route('user_resources', '/users/{user_name}/resources') - config.add_route('user_inherited_resources', '/users/{user_name}/inherited_resources') - config.add_route('user_resources_type', '/users/{user_name}/resources/types/{resource_type}') - config.add_route('user_resource_permissions', '/users/{user_name}/resources/{resource_id}/permissions') - config.add_route('user_resource_permission', '/users/{user_name}/resources/{resource_id}/permissions/{permission_name}') - config.add_route('user_resource_inherited_permissions', '/users/{user_name}/resource/{resource_id}/inherited_permissions') + config.add_route(**service_api_route_info(UsersAPI)) + config.add_route(**service_api_route_info(UserAPI)) + config.add_route(**service_api_route_info(UserGroupsAPI)) + config.add_route(**service_api_route_info(UserGroupAPI)) + config.add_route(**service_api_route_info(UserServicesAPI)) + config.add_route(**service_api_route_info(UserInheritedServicesAPI)) + config.add_route(**service_api_route_info(UserServicePermissionsAPI)) + config.add_route(**service_api_route_info(UserServicePermissionAPI)) + config.add_route(**service_api_route_info(UserServiceInheritedPermissionsAPI)) + config.add_route(**service_api_route_info(UserServiceResourcesAPI)) + config.add_route(**service_api_route_info(UserServiceInheritedResourcesAPI)) + config.add_route(**service_api_route_info(UserResourcesAPI)) + config.add_route(**service_api_route_info(UserInheritedResourcesAPI)) + config.add_route(**service_api_route_info(UserResourceTypesAPI)) + config.add_route(**service_api_route_info(UserResourcePermissionsAPI)) + config.add_route(**service_api_route_info(UserResourcePermissionAPI)) + config.add_route(**service_api_route_info(UserResourceInheritedPermissionsAPI)) + # Logged User routes + config.add_route(**service_api_route_info(LoggedUserAPI)) + #config.add_route(**service_api_route_info(LoggedUserGroupsAPI)) + #config.add_route(**service_api_route_info(LoggedUserGroupAPI)) + #config.add_route(**service_api_route_info(LoggedUserServicesAPI)) + #config.add_route(**service_api_route_info(LoggedUserInheritedServicesAPI)) + #config.add_route(**service_api_route_info(LoggedUserServicePermissionsAPI)) + #config.add_route(**service_api_route_info(LoggedUserServicePermissionAPI)) + #config.add_route(**service_api_route_info(LoggedUserServiceInheritedPermissionsAPI)) + #config.add_route(**service_api_route_info(LoggedUserServiceResourcesAPI)) + #config.add_route(**service_api_route_info(LoggedUserServiceInheritedResourcesAPI)) + #config.add_route(**service_api_route_info(LoggedUserResourcesAPI)) + #config.add_route(**service_api_route_info(LoggedUserInheritedResourcesAPI)) + #config.add_route(**service_api_route_info(LoggedUserResourceTypesAPI)) + #config.add_route(**service_api_route_info(LoggedUserResourcePermissionsAPI)) + #config.add_route(**service_api_route_info(LoggedUserResourcePermissionAPI)) + #config.add_route(**service_api_route_info(LoggedUserResourceInheritedPermissionsAPI)) config.scan() diff --git a/magpie/api/management/user/user_formats.py b/magpie/api/management/user/user_formats.py new file mode 100644 index 000000000..54e1bddef --- /dev/null +++ b/magpie/api/management/user/user_formats.py @@ -0,0 +1,18 @@ +from magpie.definitions.pyramid_definitions import HTTPInternalServerError +from magpie.api.api_except import evaluate_call + + +def format_user(user, group_names=None): + def fmt_usr(usr, grp_names): + return { + u'user_name': str(usr.user_name), + u'email': str(usr.email), + u'group_names': sorted(list(grp_names) if grp_names else [grp.group_name for grp in user.groups]), + } + + return evaluate_call( + lambda: fmt_usr(user, group_names), + httpError=HTTPInternalServerError, + msgOnFail="Failed to format user.", + content={u'service': repr(user)} + ) diff --git a/magpie/api/management/user/user_utils.py b/magpie/api/management/user/user_utils.py index 17abff835..9e44b54e4 100644 --- a/magpie/api/management/user/user_utils.py +++ b/magpie/api/management/user/user_utils.py @@ -1,7 +1,9 @@ from magpie import * -from api.api_except import * -from api.management.resource.resource_utils import check_valid_service_resource_permission -from definitions.ziggurat_definitions import * +from magpie.api.api_except import * +from magpie.api.api_rest_schemas import * +from magpie.api.management.resource.resource_utils import check_valid_service_resource_permission +from magpie.api.management.user.user_formats import * +from magpie.definitions.ziggurat_definitions import * from services import service_type_dict import models @@ -11,14 +13,15 @@ def create_user(user_name, password, email, group_name, db_session): # Check that group already exists group_check = evaluate_call(lambda: GroupService.by_group_name(group_name, db_session=db), - httpError=HTTPForbidden, msgOnFail="Group query was refused by db") - verify_param(group_check, notNone=True, httpError=HTTPNotAcceptable, msgOnFail="Group for new user doesn't exist") + httpError=HTTPForbidden, msgOnFail=UserGroup_GET_ForbiddenResponseSchema.description) + verify_param(group_check, notNone=True, httpError=HTTPNotAcceptable, + msgOnFail=UserGroup_Check_ForbiddenResponseSchema.description) # Check if user already exists user_check = evaluate_call(lambda: UserService.by_user_name(user_name=user_name, db_session=db), - httpError=HTTPForbidden, msgOnFail="User check query was refused by db") + httpError=HTTPForbidden, msgOnFail=User_Check_ForbiddenResponseSchema.description) verify_param(user_check, isNone=True, httpError=HTTPConflict, - msgOnFail="User name matches an already existing user name") + msgOnFail=User_Check_ConflictResponseSchema.description) # Create user with specified name and group to assign user_model = models.User(user_name=user_name, email=email) @@ -26,31 +29,32 @@ def create_user(user_name, password, email, group_name, db_session): user_model.set_password(password) user_model.regenerate_security_code() evaluate_call(lambda: db.add(user_model), fallback=lambda: db.rollback(), - httpError=HTTPForbidden, msgOnFail="Failed to add user to db") + httpError=HTTPForbidden, msgOnFail=Users_POST_ForbiddenResponseSchema.description) # Assign user to default group and own group new_user = evaluate_call(lambda: UserService.by_user_name(user_name, db_session=db), - httpError=HTTPForbidden, msgOnFail="New user query was refused by db") + httpError=HTTPForbidden, msgOnFail=UserNew_POST_ForbiddenResponseSchema.description) group_entry = models.UserGroup(group_id=group_check.id, user_id=new_user.id) evaluate_call(lambda: db.add(group_entry), fallback=lambda: db.rollback(), - httpError=HTTPForbidden, msgOnFail="Failed to add user-group to db") + httpError=HTTPForbidden, msgOnFail=UserGroup_GET_ForbiddenResponseSchema.description) - return valid_http(httpSuccess=HTTPCreated, detail="Add user to db successful") + return valid_http(httpSuccess=HTTPCreated, detail=Users_POST_OkResponseSchema.description, + content={u'user': format_user(new_user, [group_name])}) def create_user_resource_permission(permission_name, resource, user_id, db_session): check_valid_service_resource_permission(permission_name, resource, db_session) resource_id = resource.resource_id new_perm = models.UserResourcePermission(resource_id=resource_id, user_id=user_id) - verify_param(new_perm, notNone=True, httpError=HTTPNotAcceptable, - content={u'resource_id': str(resource_id), u'user_id': str(user_id)}, - msgOnFail="Failed to create permission using specified `resource_id` and `user_id`") + verify_param(new_perm, notNone=True, httpError=HTTPNotAcceptable, paramName=u'permission_name', + content={u'resource_id': resource_id, u'user_id': user_id}, + msgOnFail=UserResourcePermissions_POST_NotAcceptableResponseSchema.description) new_perm.perm_name = permission_name evaluate_call(lambda: db_session.add(new_perm), fallback=lambda: db_session.rollback(), - httpError=HTTPConflict, msgOnFail="Permission already exist on service for user, cannot add to db", + httpError=HTTPConflict, msgOnFail=UserResourcePermissions_POST_ConflictResponseSchema.description, content={u'resource_id': resource_id, u'user_id': user_id, u'permission_name': permission_name}) - return valid_http(httpSuccess=HTTPCreated, detail="Create user resource permission successful", - content={u'resource_id': resource_id}) + return valid_http(httpSuccess=HTTPCreated, detail=UserResourcePermissions_POST_CreatedResponseSchema.description, + content={u'resource_id': resource_id, u'user_id': user_id, u'permission_name': permission_name}) def delete_user_resource_permission(permission_name, resource, user_id, db_session): @@ -58,9 +62,9 @@ def delete_user_resource_permission(permission_name, resource, user_id, db_sessi resource_id = resource.resource_id del_perm = UserResourcePermissionService.get(user_id, resource_id, permission_name, db_session) evaluate_call(lambda: db_session.delete(del_perm), fallback=lambda: db_session.rollback(), - httpError=HTTPNotFound, msgOnFail="Could not find user resource permission to delete from db", + httpError=HTTPNotFound, msgOnFail=UserResourcePermissions_DELETE_NotFoundResponseSchema.description, content={u'resource_id': resource_id, u'user_id': user_id, u'permission_name': permission_name}) - return valid_http(httpSuccess=HTTPOk, detail="Delete user resource permission successful") + return valid_http(httpSuccess=HTTPOk, detail=UserResourcePermissions_DELETE_OkResponseSchema.description) def filter_user_permission(resource_permission_tuple_list, user): @@ -93,7 +97,7 @@ def get_user_service_permissions(user, service, db_session, inherited_permission def get_user_resources_permissions_dict(user, db_session, resource_types=None, resource_ids=None, inherited_permissions=True): verify_param(user, notNone=True, httpError=HTTPNotFound, - msgOnFail="Invalid user specified to obtain resource permissions") + msgOnFail="Invalid user specified to obtain resource permissions.") res_perm_tuple_list = user.resources_with_possible_perms(resource_ids=resource_ids, resource_types=resource_types, db_session=db_session) if not inherited_permissions: @@ -122,24 +126,24 @@ def get_user_service_resources_permissions_dict(user, service, db_session, inher def check_user_info(user_name, email, password, group_name): verify_param(user_name, notNone=True, notEmpty=True, httpError=HTTPBadRequest, - msgOnFail="Invalid `user_name` value specified") + paramName=u'user_name', msgOnFail=Users_CheckInfo_Name_BadRequestResponseSchema.description) verify_param(len(user_name), isIn=True, httpError=HTTPBadRequest, - paramCompare=range(1, 1 + USER_NAME_MAX_LENGTH), + paramName=u'user_name', paramCompare=range(1, 1 + USER_NAME_MAX_LENGTH), msgOnFail="Invalid `user_name` length specified " + - "(>{length} characters)".format(length=USER_NAME_MAX_LENGTH)) + "(>{length} characters).".format(length=USER_NAME_MAX_LENGTH)) verify_param(email, notNone=True, notEmpty=True, httpError=HTTPBadRequest, - msgOnFail="Invalid `email` value specified") + paramName=u'email', msgOnFail=Users_CheckInfo_Email_BadRequestResponseSchema.description) verify_param(password, notNone=True, notEmpty=True, httpError=HTTPBadRequest, - msgOnFail="Invalid `password` value specified") + paramName=u'password', msgOnFail=Users_CheckInfo_Password_BadRequestResponseSchema.description) verify_param(group_name, notNone=True, notEmpty=True, httpError=HTTPBadRequest, - msgOnFail="Invalid `group_name` value specified") + paramName=u'group_name', msgOnFail=Users_CheckInfo_GroupName_BadRequestResponseSchema.description) verify_param(user_name, paramCompare=[LOGGED_USER], notIn=True, httpError=HTTPConflict, - msgOnFail="Invalid `user_name` already logged in") + paramName=u'user_name', msgOnFail=Users_CheckInfo_Login_ConflictResponseSchema.description) def get_user_groups_checked(request, user): - verify_param(user, notNone=True, httpError=HTTPNotFound, msgOnFail="User name not found in db") + verify_param(user, notNone=True, httpError=HTTPNotFound, msgOnFail="User name not found in db.") db = request.db group_names = evaluate_call(lambda: [group.group_name for group in user.groups], fallback=lambda: db.rollback(), - httpError=HTTPInternalServerError, msgOnFail="Failed to obtain groups of user") - return group_names + httpError=HTTPInternalServerError, msgOnFail="Failed to obtain groups of user.") + return sorted(group_names) diff --git a/magpie/api/management/user/user_views.py b/magpie/api/management/user/user_views.py index 422325888..9cd926ded 100644 --- a/magpie/api/management/user/user_views.py +++ b/magpie/api/management/user/user_views.py @@ -1,12 +1,38 @@ -from definitions.pyramid_definitions import * -from definitions.ziggurat_definitions import * -from api.api_requests import * -from api.management.user.user_utils import * -from api.management.service.service_formats import format_service, format_service_resources - - -@view_config(route_name='users', request_method='POST') +from magpie.definitions.pyramid_definitions import * +from magpie.definitions.ziggurat_definitions import * +from magpie.api.api_requests import * +from magpie.api.api_rest_schemas import * +from magpie.api.management.user.user_formats import * +from magpie.api.management.user.user_utils import * +from magpie.api.management.service.service_formats import format_service, format_service_resources + + +@UsersAPI.get(tags=[UsersTag], response_schemas={ + '200': Users_GET_OkResponseSchema(), + '401': UnauthorizedResponseSchema(), + '403': Users_GET_ForbiddenResponseSchema(), +}) +@view_config(route_name=UsersAPI.name, request_method='GET') +def get_users(request): + """List all registered user names.""" + user_name_list = evaluate_call(lambda: [user.user_name for user in models.User.all(db_session=request.db)], + fallback=lambda: request.db.rollback(), + httpError=HTTPForbidden, msgOnFail=Users_GET_ForbiddenResponseSchema.description) + return valid_http(httpSuccess=HTTPOk, content={u'user_names': sorted(user_name_list)}, + detail=Users_GET_OkResponseSchema.description) + + +@UsersAPI.post(schema=Users_POST_RequestSchema(), tags=[UsersTag], response_schemas={ + '200': Users_POST_OkResponseSchema(), + '400': Users_CheckInfo_Name_BadRequestResponseSchema(), + '401': UnauthorizedResponseSchema(), + '403': Users_POST_ForbiddenResponseSchema(), + '406': UserGroup_GET_NotAcceptableResponseSchema(), + '409': Users_CheckInfo_Login_ConflictResponseSchema(), +}) +@view_config(route_name=UsersAPI.name, request_method='POST') def create_user_view(request): + """Create a new user.""" user_name = get_multiformat_post(request, 'user_name') email = get_multiformat_post(request, 'email') password = get_multiformat_post(request, 'password') @@ -15,8 +41,25 @@ def create_user_view(request): return create_user(user_name, password, email, group_name, db_session=request.db) -@view_config(route_name='user', request_method='PUT') +@UsersAPI.put(schema=User_PUT_RequestSchema(), tags=[UsersTag], response_schemas={ + '200': Users_PUT_OkResponseSchema(), + '400': Users_CheckInfo_Name_BadRequestResponseSchema(), + '401': UnauthorizedResponseSchema(), + '403': UserGroup_GET_ForbiddenResponseSchema(), + '406': UserGroup_GET_NotAcceptableResponseSchema(), + '409': Users_CheckInfo_Login_ConflictResponseSchema(), +}) +@LoggedUserAPI.put(schema=User_PUT_RequestSchema(), tags=[LoggedUserTag], response_schemas={ + '200': Users_PUT_OkResponseSchema(), + '400': Users_CheckInfo_Name_BadRequestResponseSchema(), + '401': UnauthorizedResponseSchema(), + '403': UserGroup_GET_ForbiddenResponseSchema(), + '406': UserGroup_GET_NotAcceptableResponseSchema(), + '409': Users_CheckInfo_Login_ConflictResponseSchema(), +}) +@view_config(route_name=UserAPI.name, request_method='PUT') def update_user_view(request): + """Update user information by user name.""" user = get_user_matchdict_checked(request, user_name_key='user_name') new_user_name = get_multiformat_post(request, 'user_name') new_email = get_multiformat_post(request, 'email') @@ -27,7 +70,7 @@ def update_user_view(request): if user.user_name != new_user_name: evaluate_call(lambda: models.User.by_user_name(new_user_name, db_session=request.db), fallback=lambda: request.db.rollback(), - httpError=HTTPConflict, msgOnFail="New name user already exists") + httpError=HTTPConflict, msgOnFail=User_PUT_ConflictResponseSchema.description) user.user_name = new_user_name if user.email != new_email: user.email = new_email @@ -35,57 +78,97 @@ def update_user_view(request): user.set_password(new_password) user.regenerate_security_code() - return valid_http(httpSuccess=HTTPOk, detail="Update user successful.") - + return valid_http(httpSuccess=HTTPOk, detail=Users_PUT_OkResponseSchema.description) -@view_config(route_name='users', request_method='GET') -def get_users(request): - user_name_list = evaluate_call(lambda: [user.user_name for user in models.User.all(db_session=request.db)], - fallback=lambda: request.db.rollback(), - httpError=HTTPForbidden, msgOnFail="Get users query refused by db") - return valid_http(httpSuccess=HTTPOk, detail="Get users successful", content={u'user_names': user_name_list}) - -@view_config(route_name='user', request_method='GET', permission=NO_PERMISSION_REQUIRED) +@UserAPI.get(tags=[UsersTag], api_security=SecurityEveryoneAPI, response_schemas={ + '200': User_GET_OkResponseSchema(), + '403': User_CheckAnonymous_ForbiddenResponseSchema(), + '404': User_CheckAnonymous_NotFoundResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +}) +@view_config(route_name=UserAPI.name, request_method='GET', permission=NO_PERMISSION_REQUIRED) def get_user_view(request): + """Get user information by name.""" user = get_user_matchdict_checked(request) - json_response = {u'user_name': user.user_name, - u'email': user.email, - u'group_names': [group.group_name for group in user.groups]} - return valid_http(httpSuccess=HTTPOk, detail="Get user successful", content=json_response) - - -@view_config(route_name='user', request_method='DELETE') + return valid_http(httpSuccess=HTTPOk, detail=User_GET_OkResponseSchema.description, + content={u'user': format_user(user)}) + + +@LoggedUserAPI.get(tags=[LoggedUserTag], api_security=SecurityEveryoneAPI, response_schemas={ + '200': User_GET_OkResponseSchema(), + '403': User_CheckAnonymous_ForbiddenResponseSchema(), + '404': User_CheckAnonymous_NotFoundResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +}) +@view_config(route_name=LoggedUserAPI.name, request_method='GET', permission=NO_PERMISSION_REQUIRED) +def get_logged_user_view(request): + """Get logged user information.""" + user = get_user(request, LOGGED_USER) + return valid_http(httpSuccess=HTTPOk, detail=User_GET_OkResponseSchema.description, + content={u'user': format_user(user)}) + + +@UserAPI.delete(schema=User_DELETE_RequestSchema(), tags=[UsersTag], response_schemas={ + '200': User_DELETE_OkResponseSchema(), + '401': UnauthorizedResponseSchema(), + '403': User_CheckAnonymous_ForbiddenResponseSchema(), + '404': User_CheckAnonymous_NotFoundResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +}) +@LoggedUserAPI.delete(schema=User_DELETE_RequestSchema(), tags=[LoggedUserTag], response_schemas={ + '200': User_DELETE_OkResponseSchema(), + '401': UnauthorizedResponseSchema(), + '403': User_CheckAnonymous_ForbiddenResponseSchema(), + '404': User_CheckAnonymous_NotFoundResponseSchema(), + '422': UnprocessableEntityResponseSchema(), +}) +@view_config(route_name=UserAPI.name, request_method='DELETE') def delete_user(request): + """Delete a user by name.""" user = get_user_matchdict_checked(request) db = request.db evaluate_call(lambda: db.delete(user), fallback=lambda: db.rollback(), - httpError=HTTPForbidden, msgOnFail="Delete user by name refused by db") - return valid_http(httpSuccess=HTTPOk, detail="Delete user successful") + httpError=HTTPForbidden, msgOnFail=User_DELETE_ForbiddenResponseSchema.description) + return valid_http(httpSuccess=HTTPOk, detail=User_DELETE_OkResponseSchema.description) -@view_config(route_name='user_groups', request_method='GET', permission=NO_PERMISSION_REQUIRED) +@UserGroupsAPI.get(tags=[UsersTag], api_security=SecurityEveryoneAPI, response_schemas=UserGroups_GET_responses) +@LoggedUserGroupsAPI.get(tags=[LoggedUserTag], api_security=SecurityEveryoneAPI, + response_schemas=LoggedUserGroups_GET_responses) +@view_config(route_name=UserGroupsAPI.name, request_method='GET', permission=NO_PERMISSION_REQUIRED) def get_user_groups(request): + """List all groups a user belongs to.""" user = get_user_matchdict_checked(request) group_names = get_user_groups_checked(request, user) - return valid_http(httpSuccess=HTTPOk, detail="Get user groups successful", content={u'group_names': group_names}) + return valid_http(httpSuccess=HTTPOk, detail=UserGroups_GET_OkResponseSchema.description, + content={u'group_names': group_names}) -@view_config(route_name='user_group', request_method='POST') +@UserGroupsAPI.post(schema=UserGroups_POST_RequestSchema(), tags=[UsersTag], response_schemas=UserGroups_POST_responses) +@LoggedUserGroupsAPI.post(schema=UserGroups_POST_RequestSchema(), tags=[LoggedUserTag], + response_schemas=LoggedUserGroups_POST_responses) +@view_config(route_name=UserGroupsAPI.name, request_method='POST') def assign_user_group(request): + """Assign a user to a group.""" db = request.db user = get_user_matchdict_checked(request) group = get_group_matchdict_checked(request) new_user_group = models.UserGroup(group_id=group.id, user_id=user.id) evaluate_call(lambda: db.add(new_user_group), fallback=lambda: db.rollback(), - httpError=HTTPConflict, msgOnFail="User already belongs to this group", + httpError=HTTPConflict, msgOnFail=UserGroups_POST_ConflictResponseSchema.description, content={u'user_name': user.user_name, u'group_name': group.group_name}) - return valid_http(httpSuccess=HTTPCreated, detail="Create user-group assignation successful") + return valid_http(httpSuccess=HTTPCreated, detail=UserGroups_POST_OkResponseSchema.description) -@view_config(route_name='user_group', request_method='DELETE') +@UserGroupAPI.delete(schema=UserGroup_DELETE_RequestSchema(), tags=[UsersTag], + response_schemas=UserGroup_DELETE_responses) +@LoggedUserGroupAPI.delete(schema=UserGroup_DELETE_RequestSchema(), tags=[LoggedUserTag], + response_schemas=LoggedUserGroup_DELETE_responses) +@view_config(route_name=UserGroupAPI.name, request_method='DELETE') def delete_user_group(request): + """Remove a user from a group.""" db = request.db user = get_user_matchdict_checked(request) group = get_group_matchdict_checked(request) @@ -97,9 +180,9 @@ def del_usr_grp(usr, grp): .delete() evaluate_call(lambda: del_usr_grp(user, group), fallback=lambda: db.rollback(), - httpError=HTTPNotFound, msgOnFail="Invalid user-group combination for delete", + httpError=HTTPNotFound, msgOnFail=UserGroup_DELETE_NotFoundResponseSchema.description, content={u'user_name': user.user_name, u'group_name': group.group_name}) - return valid_http(httpSuccess=HTTPOk, detail="Delete user-group successful") + return valid_http(httpSuccess=HTTPOk, detail=UserGroup_DELETE_OkResponseSchema.description) def get_user_resources_runner(request, inherited_group_resources_permissions=True): @@ -127,18 +210,28 @@ def build_json_user_resource_tree(usr): usr_res_dict = evaluate_call(lambda: build_json_user_resource_tree(user), fallback=lambda: db.rollback(), httpError=HTTPNotFound, - msgOnFail="Failed to populate user resources", + msgOnFail=UserResources_GET_NotFoundResponseSchema.description, content={u'user_name': user.user_name, u'resource_types': [u'service']}) - return valid_http(httpSuccess=HTTPOk, detail="Get user resources successful", content={u'resources': usr_res_dict}) + return valid_http(httpSuccess=HTTPOk, detail=UserResources_GET_OkResponseSchema.description, + content={u'resources': usr_res_dict}) -@view_config(route_name='user_resources', request_method='GET', permission=NO_PERMISSION_REQUIRED) +@UserResourcesAPI.get(tags=[UsersTag], api_security=SecurityEveryoneAPI, response_schemas=UserResources_GET_responses) +@LoggedUserResourcesAPI.get(tags=[LoggedUserTag], api_security=SecurityEveryoneAPI, + response_schemas=LoggedUserResources_GET_responses) +@view_config(route_name=UserResourcesAPI.name, request_method='GET', permission=NO_PERMISSION_REQUIRED) def get_user_resources_view(request): + """List all resources a user has direct permission on (not including his groups permissions).""" return get_user_resources_runner(request, inherited_group_resources_permissions=False) -@view_config(route_name='user_inherited_resources', request_method='GET', permission=NO_PERMISSION_REQUIRED) +@UserInheritedResourcesAPI.get(tags=[UsersTag], api_security=SecurityEveryoneAPI, + response_schemas=UserResources_GET_responses) +@LoggedUserInheritedResourcesAPI.get(tags=[LoggedUserTag], api_security=SecurityEveryoneAPI, + response_schemas=LoggedUserResources_GET_responses) +@view_config(route_name=UserInheritedResourcesAPI.name, request_method='GET', permission=NO_PERMISSION_REQUIRED) def get_user_inherited_resources_view(request): + """List all resources a user has permission on with his inherited user and groups permissions.""" return get_user_resources_runner(request, inherited_group_resources_permissions=True) @@ -147,30 +240,51 @@ def get_user_resource_permissions_runner(request, inherited_permissions=True): resource = get_resource_matchdict_checked(request, 'resource_id') perm_names = get_user_resource_permissions(resource=resource, user=user, db_session=request.db, inherited_permissions=inherited_permissions) - return valid_http(httpSuccess=HTTPOk, detail="Get user resource permissions successful", - content={u'permission_names': perm_names}) + return valid_http(httpSuccess=HTTPOk, detail=UserResourcePermissions_GET_OkResponseSchema.description, + content={u'permission_names': sorted(perm_names)}) -@view_config(route_name='user_resource_permissions', request_method='GET', permission=NO_PERMISSION_REQUIRED) +@UserResourcePermissionsAPI.get(tags=[UsersTag], api_security=SecurityEveryoneAPI, + response_schemas=UserResourcePermissions_GET_responses) +@LoggedUserResourcePermissionsAPI.get(tags=[LoggedUserTag], api_security=SecurityEveryoneAPI, + response_schemas=LoggedUserResourcePermissions_GET_responses) +@view_config(route_name=UserResourcePermissionsAPI.name, request_method='GET', permission=NO_PERMISSION_REQUIRED) def get_user_resource_permissions_view(request): + """List all direct permissions a user has on a specific resource (not including his groups permissions).""" return get_user_resource_permissions_runner(request, inherited_permissions=False) -@view_config(route_name='user_resource_inherited_permissions', request_method='GET', permission=NO_PERMISSION_REQUIRED) +@UserResourceInheritedPermissionsAPI.get(tags=[UsersTag], api_security=SecurityEveryoneAPI, + response_schemas=UserResourcePermissions_GET_responses) +@LoggedUserResourceInheritedPermissionsAPI.get(tags=[LoggedUserTag], api_security=SecurityEveryoneAPI, + response_schemas=LoggedUserResourcePermissions_GET_responses) +@view_config(route_name=UserResourceInheritedPermissionsAPI.name, request_method='GET', + permission=NO_PERMISSION_REQUIRED) def get_user_resource_inherited_permissions_view(request): + """List all permissions a user has on a specific resource with his inherited user and groups permissions.""" return get_user_resource_permissions_runner(request, inherited_permissions=True) -@view_config(route_name='user_resource_permissions', request_method='POST') +@UserResourcePermissionsAPI.post(schema=UserResourcePermissions_POST_RequestSchema(), tags=[UsersTag], + response_schemas=UserResourcePermissions_POST_responses) +@LoggedUserResourcePermissionsAPI.post(schema=UserResourcePermissions_POST_RequestSchema(), tags=[LoggedUserTag], + response_schemas=LoggedUserResourcePermissions_POST_responses) +@view_config(route_name=UserResourcePermissionsAPI.name, request_method='POST') def create_user_resource_permission_view(request): + """Create a permission on specific resource for a user.""" user = get_user_matchdict_checked(request) resource = get_resource_matchdict_checked(request) perm_name = get_permission_multiformat_post_checked(request, resource) return create_user_resource_permission(perm_name, resource, user.id, request.db) -@view_config(route_name='user_resource_permission', request_method='DELETE') +@UserResourcePermissionAPI.delete(schema=UserResourcePermission_DELETE_RequestSchema(), tags=[UsersTag], + response_schemas=UserResourcePermission_DELETE_responses) +@LoggedUserResourcePermissionAPI.delete(schema=UserResourcePermission_DELETE_RequestSchema(), tags=[LoggedUserTag], + response_schemas=LoggedUserResourcePermission_DELETE_responses) +@view_config(route_name=UserResourcePermissionAPI.name, request_method='DELETE') def delete_user_resource_permission_view(request): + """Delete a permission on a resource for a user (not including his groups permissions).""" user = get_user_matchdict_checked(request) resource = get_resource_matchdict_checked(request) perm_name = get_permission_matchdict_checked(request, resource) @@ -189,16 +303,26 @@ def get_user_services_runner(request, inherited_group_services_permissions): svc_json[svc.type] = {} svc_json[svc.type][svc.resource_name] = format_service(svc, perms) - return valid_http(httpSuccess=HTTPOk, detail="Get user services successful", content={u'services': svc_json}) + return valid_http(httpSuccess=HTTPOk, detail=UserServices_GET_OkResponseSchema.description, + content={u'services': svc_json}) -@view_config(route_name='user_services', request_method='GET', permission=NO_PERMISSION_REQUIRED) +@UserServicesAPI.get(tags=[UsersTag], api_security=SecurityEveryoneAPI, response_schemas=UserServices_GET_responses) +@LoggedUserServicesAPI.get(tags=[LoggedUserTag], api_security=SecurityEveryoneAPI, + response_schemas=LoggedUserServices_GET_responses) +@view_config(route_name=UserServicesAPI.name, request_method='GET', permission=NO_PERMISSION_REQUIRED) def get_user_services_view(request): + """List all services a user has direct permission on (not including his groups permissions).""" return get_user_services_runner(request, inherited_group_services_permissions=False) -@view_config(route_name='user_inherited_services', request_method='GET', permission=NO_PERMISSION_REQUIRED) +@UserInheritedServicesAPI.get(tags=[UsersTag], api_security=SecurityEveryoneAPI, + response_schemas=UserServices_GET_responses) +@LoggedUserInheritedServicesAPI.get(tags=[LoggedUserTag], api_security=SecurityEveryoneAPI, + response_schemas=LoggedUserServices_GET_responses) +@view_config(route_name=UserInheritedServicesAPI.name, request_method='GET', permission=NO_PERMISSION_REQUIRED) def get_user_inherited_services_view(request): + """List all services a user has permission on with his inherited user and groups permissions.""" return get_user_services_runner(request, inherited_group_services_permissions=True) @@ -208,33 +332,53 @@ def get_user_service_permissions_runner(request, inherited_permissions): perms = evaluate_call(lambda: get_user_service_permissions(service=service, user=user, db_session=request.db, inherited_permissions=inherited_permissions), fallback=lambda: request.db.rollback(), httpError=HTTPNotFound, - msgOnFail="Could not find permissions using specified `service_name` and `user_name`", + msgOnFail=UserServicePermissions_GET_NotFoundResponseSchema.description, content={u'service_name': str(service.resource_name), u'user_name': str(user.user_name)}) - message_inherit = ' inherited ' if inherited_permissions else ' ' - return valid_http(httpSuccess=HTTPOk, detail="Get user service{}permissions successful".format(message_inherit), - content={u'permission_names': perms}) + return valid_http(httpSuccess=HTTPOk, detail=UserServicePermissions_GET_OkResponseSchema.description, + content={u'permission_names': sorted(perms)}) -@view_config(route_name='user_service_permissions', request_method='GET', permission=NO_PERMISSION_REQUIRED) +@UserServicePermissionsAPI.get(tags=[UsersTag], api_security=SecurityEveryoneAPI, + response_schemas=UserServicePermissions_GET_responses) +@LoggedUserServicePermissionsAPI.get(tags=[LoggedUserTag], api_security=SecurityEveryoneAPI, + response_schemas=LoggedUserServicePermissions_GET_responses) +@view_config(route_name=UserServicePermissionsAPI.name, request_method='GET', permission=NO_PERMISSION_REQUIRED) def get_user_service_permissions_view(request): + """List all direct permissions a user has on a service (not including his groups permissions).""" return get_user_service_permissions_runner(request, inherited_permissions=False) -@view_config(route_name='user_service_inherited_permissions', request_method='GET', permission=NO_PERMISSION_REQUIRED) +@UserServiceInheritedPermissionsAPI.get(tags=[UsersTag], api_security=SecurityEveryoneAPI, + response_schemas=UserServicePermissions_GET_responses) +@LoggedUserServiceInheritedPermissionsAPI.get(tags=[LoggedUserTag], api_security=SecurityEveryoneAPI, + response_schemas=LoggedUserServicePermissions_GET_responses) +@view_config(route_name=UserServiceInheritedPermissionsAPI.name, request_method='GET', + permission=NO_PERMISSION_REQUIRED) def get_user_service_inherited_permissions_view(request): + """List all permissions a user has on a service using all his inherited user and groups permissions.""" return get_user_service_permissions_runner(request, inherited_permissions=True) -@view_config(route_name='user_service_permissions', request_method='POST') +@UserServicePermissionsAPI.post(schema=UserServicePermissions_POST_RequestBodySchema, tags=[UsersTag], + response_schemas=UserServicePermissions_POST_responses) +@LoggedUserServicePermissionsAPI.post(schema=UserServicePermissions_POST_RequestBodySchema, tags=[LoggedUserTag], + response_schemas=LoggedUserServicePermissions_POST_responses) +@view_config(route_name=UserServicePermissionsAPI.name, request_method='POST') def create_user_service_permission(request): + """Create a permission on a service for a user.""" user = get_user_matchdict_checked(request) service = get_service_matchdict_checked(request) perm_name = get_permission_multiformat_post_checked(request, service) return create_user_resource_permission(perm_name, service, user.id, request.db) -@view_config(route_name='user_service_permission', request_method='DELETE') +@UserServicePermissionAPI.delete(schema=UserServicePermission_DELETE_RequestSchema, tags=[UsersTag], + response_schemas=UserServicePermission_DELETE_responses) +@LoggedUserServicePermissionAPI.delete(schema=UserServicePermission_DELETE_RequestSchema, tags=[LoggedUserTag], + response_schemas=LoggedUserServicePermission_DELETE_responses) +@view_config(route_name=UserServicePermissionAPI.name, request_method='DELETE') def delete_user_service_permission(request): + """Delete a permission on a service for a user (not including his groups permissions).""" user = get_user_matchdict_checked(request) service = get_service_matchdict_checked(request) perm_name = get_permission_multiformat_post_checked(request, service) @@ -246,7 +390,7 @@ def get_user_service_resource_permissions_runner(request, inherited_permissions) Resource permissions a user as on a specific service :param request: - :param inherited_permissions: only direct permissions if False, else resolve permissions with user and its groups. + :param inherited_permissions: only direct permissions if False, else resolve permissions with user and his groups. :return: """ user = get_user_matchdict_checked(request) @@ -262,15 +406,26 @@ def get_user_service_resource_permissions_runner(request, inherited_permissions) resources_perms_dict=resources_perms_dict, display_all=False ) - return valid_http(httpSuccess=HTTPOk, detail="Get user service resources successful", + return valid_http(httpSuccess=HTTPOk, detail=UserServiceResources_GET_OkResponseSchema.description, content={u'service': user_svc_res_json}) -@view_config(route_name='user_service_resources', request_method='GET', permission=NO_PERMISSION_REQUIRED) +@UserServiceResourcesAPI.get(tags=[UsersTag], api_security=SecurityEveryoneAPI, + response_schemas=UserServiceResources_GET_responses) +@LoggedUserServiceResourcesAPI.get(tags=[LoggedUserTag], api_security=SecurityEveryoneAPI, + response_schemas=LoggedUserServiceResources_GET_responses) +@view_config(route_name=UserServiceResourcesAPI.name, request_method='GET', permission=NO_PERMISSION_REQUIRED) def get_user_service_resources_view(request): + """List all resources under a service a user has direct permission on (not including his groups permissions).""" return get_user_service_resource_permissions_runner(request, inherited_permissions=False) -@view_config(route_name='user_service_inherited_resources', request_method='GET', permission=NO_PERMISSION_REQUIRED) +@UserServiceInheritedResourcesAPI.get(tags=[UsersTag], api_security=SecurityEveryoneAPI, + response_schemas=UserServiceResources_GET_responses) +@LoggedUserServiceInheritedResourcesAPI.get(tags=[LoggedUserTag], api_security=SecurityEveryoneAPI, + response_schemas=LoggedUserServiceResources_GET_responses) +@view_config(route_name=UserServiceInheritedResourcesAPI.name, request_method='GET', permission=NO_PERMISSION_REQUIRED) def get_user_service_inherited_resources_view(request): + """List all resources under a service a user has permission on using all his inherited user and groups + permissions.""" return get_user_service_resource_permissions_runner(request, inherited_permissions=True) diff --git a/magpie/common.py b/magpie/common.py index b08876ed2..e0784f5b5 100644 --- a/magpie/common.py +++ b/magpie/common.py @@ -1,3 +1,6 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import unicode_literals from distutils.dir_util import mkpath import logging import os diff --git a/magpie/db.py b/magpie/db.py index 4094b5e38..29b0ffbe2 100644 --- a/magpie/db.py +++ b/magpie/db.py @@ -1,6 +1,9 @@ -from definitions.alembic_definitions import * -from definitions.sqlalchemy_definitions import * -import ConfigParser +from magpie import MAGPIE_ROOT +from magpie.definitions.alembic_definitions import * +from magpie.definitions.sqlalchemy_definitions import * +from common import print_log +# noinspection PyCompatibility +import configparser import transaction import models import inspect @@ -9,7 +12,6 @@ import logging logger = logging.getLogger(__name__) - # import or define all models here to ensure they are attached to the # Base.metadata prior to any initialization routines from models import * @@ -78,20 +80,14 @@ def get_db_session_from_config_ini(config_ini_path, ini_main_section_name='app:m def get_settings_from_config_ini(config_ini_path, ini_main_section_name='app:magpie_app'): - parser = ConfigParser.ConfigParser() + parser = configparser.ConfigParser() parser.read([config_ini_path]) settings = dict(parser.items(ini_main_section_name)) return settings -def get_alembic_ini_path(): - curr_path = os.path.dirname(os.path.abspath(__file__)) - curr_path = os.path.dirname(curr_path) - return '{path}/alembic.ini'.format(path=curr_path) - - def run_database_migration(): - alembic_args = ['-c', get_alembic_ini_path(), 'upgrade', 'heads'] + alembic_args = ['-c', '{path}/alembic.ini'.format(path=MAGPIE_ROOT), 'upgrade', 'heads'] alembic.config.main(argv=alembic_args) diff --git a/magpie/definitions/pyramid_definitions.py b/magpie/definitions/pyramid_definitions.py index 79751e2f9..3871c1085 100644 --- a/magpie/definitions/pyramid_definitions.py +++ b/magpie/definitions/pyramid_definitions.py @@ -17,7 +17,7 @@ HTTPInternalServerError, ) from pyramid.interfaces import IAuthenticationPolicy, IAuthorizationPolicy -from pyramid.response import Response +from pyramid.response import Response, FileResponse from pyramid.view import ( view_config, notfound_view_config, diff --git a/magpie/definitions/sqlalchemy_definitions.py b/magpie/definitions/sqlalchemy_definitions.py index f2db153d8..2f603774d 100644 --- a/magpie/definitions/sqlalchemy_definitions.py +++ b/magpie/definitions/sqlalchemy_definitions.py @@ -6,4 +6,5 @@ from sqlalchemy.orm import relationship, sessionmaker, configure_mappers from sqlalchemy.sql import select from sqlalchemy import engine_from_config, pool, create_engine +from sqlalchemy import exc as sa_exc import sqlalchemy as sa diff --git a/magpie/demo.py b/magpie/demo.py index e8babdbb0..e5e81d558 100644 --- a/magpie/demo.py +++ b/magpie/demo.py @@ -11,7 +11,7 @@ def main(global_settings, **settings): ) config.include('pyramid_mako') - config.include('ui') + config.include('magpie.ui') config.scan() return config.make_wsgi_app() diff --git a/magpie/helpers/register_default_users.py b/magpie/helpers/register_default_users.py index d89d2d2a5..4dcecadf2 100644 --- a/magpie/helpers/register_default_users.py +++ b/magpie/helpers/register_default_users.py @@ -1,6 +1,6 @@ from magpie import * from common import * -from definitions.ziggurat_definitions import * +from magpie.definitions.ziggurat_definitions import * import models import db import transaction @@ -47,9 +47,9 @@ def register_user_with_group(user_name, group_name, email, password, db_session) def init_anonymous(db_session): register_user_with_group(user_name=ANONYMOUS_USER, - group_name=ANONYMOUS_USER, + group_name=ANONYMOUS_GROUP, email=ANONYMOUS_USER + '@mail.com', - password=ANONYMOUS_USER, + password=ANONYMOUS_PASSWORD, db_session=db_session) @@ -87,10 +87,7 @@ def register_default_users(): time.sleep(2) raise_log('Database not ready') - magpie_module_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) - magpie_ini_file = '{}/magpie.ini'.format(magpie_module_dir) - db_session = db.get_db_session_from_config_ini(magpie_ini_file) - + db_session = db.get_db_session_from_config_ini(MAGPIE_INI_FILE_PATH) init_admin(db_session) init_anonymous(db_session) init_user_group(db_session) diff --git a/magpie/helpers/register_providers.py b/magpie/helpers/register_providers.py index bad498a3b..431eb64d6 100644 --- a/magpie/helpers/register_providers.py +++ b/magpie/helpers/register_providers.py @@ -1,13 +1,13 @@ +from magpie import MAGPIE_PROVIDERS_CONFIG_PATH, MAGPIE_INI_FILE_PATH from register import magpie_register_services_from_config from db import get_db_session_from_config_ini -import os import argparse if __name__ == "__main__": parser = argparse.ArgumentParser(description="Register service providers into Magpie and Phoenix") parser.add_argument('-c', '--config-file', metavar='config_file', dest='config_file', - type=str, default=os.path.join(os.path.dirname(os.path.abspath(__file__)), "providers.cfg"), + type=str, default=MAGPIE_PROVIDERS_CONFIG_PATH, help="configuration file to employ for services registration (default: %(default)s)") parser.add_argument('-f', '--force-update', default=False, action='store_true', dest='force_update', help="enforce update of services URL if conflicting services are found (default: %(default)s)") @@ -23,8 +23,7 @@ db_session = None if args.use_db_session: - config_ini = '{}/magpie.ini'.format(os.path.abspath(os.path.dirname(os.path.dirname(__file__)))) - db_session = get_db_session_from_config_ini(config_ini) + db_session = get_db_session_from_config_ini(MAGPIE_INI_FILE_PATH) magpie_register_services_from_config(args.config_file, push_to_phoenix=args.phoenix_push, force_update=args.force_update, disable_getcapabilities=args.no_getcapabilities, db_session=db_session) diff --git a/magpie/magpie.ini b/magpie/magpie.ini index b7f29426a..76da9346a 100644 --- a/magpie/magpie.ini +++ b/magpie/magpie.ini @@ -6,8 +6,8 @@ [composite:main] use = egg:Paste#urlmap / = magpie_app -/api = api_app -/magpie/api = api_app +#/api = api_app +#/magpie/api = api_app [app:magpie_app] use = egg:magpie @@ -39,7 +39,7 @@ ziggurat_foundations.session_provider_callable = models:get_session_callable [app:api_app] use = egg:Paste#static -document_root = %(here)s/ui/swagger-ui +document_root = %(here)s/ui/swagger [filter:urlprefix] use = egg:PasteDeploy#prefix diff --git a/magpie/magpie.py b/magpie/magpie.py deleted file mode 100644 index a0deada9d..000000000 --- a/magpie/magpie.py +++ /dev/null @@ -1,166 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -""" -Magpie is a service for AuthN and AuthZ based on Ziggurat-Foundations -""" - -# -- Standard library -------------------------------------------------------- -import logging.config -import argparse -import os -import time -import logging -LOGGER = logging.getLogger(__name__) - -# -- Definitions -from definitions.alembic_definitions import * -from definitions.pyramid_definitions import * -from definitions.sqlalchemy_definitions import * -from definitions.ziggurat_definitions import * - -# -- Project specific -------------------------------------------------------- -from __init__ import * -from api.api_except import * -from api.api_rest_schemas import * -from common import * -from helpers.register_default_users import register_default_users -from helpers.register_providers import magpie_register_services_from_config -import models -import db -import __meta__ -MAGPIE_MODULE_DIR = os.path.abspath(os.path.dirname(__file__)) -MAGPIE_ROOT = os.path.dirname(MAGPIE_MODULE_DIR) -sys.path.insert(0, MAGPIE_MODULE_DIR) - - -@VersionAPI.get(schema=Version_GET_Schema(), tags=[APITag], response_schemas={ - '200': Version_GET_OkResponseSchema(description="Get version successful.")}) -@view_config(route_name='version', request_method='GET', permission=NO_PERMISSION_REQUIRED) -def get_version(request): - return valid_http(httpSuccess=HTTPOk, - content={u'version': __meta__.__version__, u'db_version': db.get_database_revision(request.db)}, - detail="Get version successful.", contentType='application/json') - - -#@NotFoundAPI.get(schema=NotFoundResponseSchema(), response_schemas={ -# '404': NotFoundResponseSchema(description="Route not found")}) -@notfound_view_config() -def not_found(request): - content = get_request_info(request, default_msg="The route resource could not be found.") - return raise_http(nothrow=True, httpError=HTTPNotFound, contentType='application/json', - detail=content['detail'], content=content) - - -@exception_view_config() -def internal_server_error(request): - content = get_request_info(request, default_msg="Internal Server Error. Unhandled exception occurred.") - return raise_http(nothrow=True, httpError=HTTPInternalServerError, contentType='application/json', - detail=content['detail'], content=content) - - -@forbidden_view_config() -def unauthorized_access(request): - # if not overridden, default is HTTPForbidden [403], which is for a slightly different situation - # this better reflects the HTTPUnauthorized [401] user access with specified AuthZ headers - # [http://www.restapitutorial.com/httpstatuscodes.html] - msg = "Unauthorized. Insufficient user privileges or missing authentication headers." - content = get_request_info(request, default_msg=msg) - return raise_http(nothrow=True, httpError=HTTPUnauthorized, contentType='application/json', - detail=content['detail'], content=content) - - -def get_request_info(request, default_msg="undefined"): - content = {u'route_name': str(request.upath_info), u'request_url': str(request.url), u'detail': default_msg} - if hasattr(request, 'exception'): - if hasattr(request.exception, 'json'): - if type(request.exception.json) is dict: - content.update(request.exception.json) - elif hasattr(request, 'matchdict'): - if request.matchdict is not None and request.matchdict != '': - content.update(request.matchdict) - return content - - -def main(global_config=None, **settings): - """ - This function returns a Pyramid WSGI application. - """ - - settings['magpie.root'] = MAGPIE_ROOT - settings['magpie.module'] = MAGPIE_MODULE_DIR - - # migrate db as required and check if database is ready - print_log('Running database migration (as required) ...') - try: - db.run_database_migration() - except ImportError: - pass - except Exception as e: - raise_log('Database migration failed [{}]'.format(str(e))) - if not db.is_database_ready(): - time.sleep(2) - raise_log('Database not ready') - - settings['magpie.phoenix_push'] = str2bool(os.getenv('PHOENIX_PUSH', False)) - - print_log('Register default providers...', LOGGER) - providers_config_path = '{}/providers.cfg'.format(MAGPIE_ROOT) - magpie_ini_path = '{}/magpie.ini'.format(MAGPIE_MODULE_DIR) - svc_db_session = db.get_db_session_from_config_ini(magpie_ini_path) - magpie_register_services_from_config(providers_config_path, push_to_phoenix=settings['magpie.phoenix_push'], - force_update=True, disable_getcapabilities=False, db_session=svc_db_session) - - print_log('Register default users...') - register_default_users() - - print_log('Running configurations setup...') - magpie_url_template = 'http://{hostname}:{port}/magpie' - port = os.getenv('MAGPIE_PORT') - if port: - settings['magpie.port'] = port - hostname = os.getenv('HOSTNAME') - if hostname: - settings['magpie.url'] = magpie_url_template.format(hostname=hostname, port=settings['magpie.port']) - - magpie_secret = os.getenv('MAGPIE_SECRET') - if magpie_secret is None: - print_log('Use default secret from magpie.ini', level=logging.DEBUG) - magpie_secret = settings['magpie.secret'] - - authn_policy = AuthTktAuthenticationPolicy( - magpie_secret, - callback=groupfinder, - ) - authz_policy = ACLAuthorizationPolicy() - - config = Configurator( - settings=settings, - root_factory=models.RootFactory, - authentication_policy=authn_policy, - authorization_policy=authz_policy - ) - - config.include('magpie') - - # include api views - magpie_api_path = '{}/__api__'.format(settings['magpie.url']) - magpie_api_view = '{}/api-explorer'.format(settings['magpie.url']) - config.cornice_enable_openapi_view( - api_path=magpie_api_path, - title='Magpie REST API', - description="OpenAPI documentation", - version=__meta__.__version__ - ) - config.cornice_enable_openapi_explorer(api_explorer_path=magpie_api_view) - #config.register_swagger_ui(swagger_ui_path=magpie_api_path) - - config.scan('magpie') - config.set_default_permission(ADMIN_PERM) - - wsgi_app = config.make_wsgi_app() - return wsgi_app - - -if __name__ == '__main__': - main() diff --git a/magpie/magpiectl.py b/magpie/magpiectl.py new file mode 100644 index 000000000..bd9c2b466 --- /dev/null +++ b/magpie/magpiectl.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +Magpie is a service for AuthN and AuthZ based on Ziggurat-Foundations +""" + +# -- Standard library --403------------------------------------------------------ +import logging.config +import argparse +import time +import warnings +import logging +LOGGER = logging.getLogger(__name__) + +# -- Definitions +from magpie.definitions.alembic_definitions import * +from magpie.definitions.pyramid_definitions import * +from magpie.definitions.sqlalchemy_definitions import * +from magpie.definitions.ziggurat_definitions import * + +# -- Project specific -------------------------------------------------------- +from __init__ import * +from magpie.api.api_except import * +from magpie.api.api_rest_schemas import * +from magpie.api.api_generic import * +from magpie.common import * +from magpie.helpers.register_default_users import register_default_users +from magpie.helpers.register_providers import magpie_register_services_from_config +from magpie.security import auth_config_from_settings +from magpie import models, db, __meta__ + + +def main(global_config=None, **settings): + """ + This function returns a Pyramid WSGI application. + """ + + settings['magpie.root'] = MAGPIE_ROOT + settings['magpie.module'] = MAGPIE_MODULE_DIR + + # migrate db as required and check if database is ready + if not settings.get('magpie.db_migration_disabled', False): + print_log('Running database migration (as required) ...') + try: + with warnings.catch_warnings(): + warnings.simplefilter("ignore", category=sa_exc.SAWarning) + db.run_database_migration() + except ImportError: + pass + except Exception as e: + raise_log('Database migration failed [{}]'.format(str(e))) + if not db.is_database_ready(): + time.sleep(2) + raise_log('Database not ready') + + settings['magpie.phoenix_push'] = str2bool(os.getenv('PHOENIX_PUSH', False)) + + print_log('Register default providers...', LOGGER) + svc_db_session = db.get_db_session_from_config_ini(MAGPIE_INI_FILE_PATH) + magpie_register_services_from_config(MAGPIE_PROVIDERS_CONFIG_PATH, push_to_phoenix=settings['magpie.phoenix_push'], + force_update=True, disable_getcapabilities=False, db_session=svc_db_session) + + print_log('Register default users...') + register_default_users() + + print_log('Running configurations setup...') + magpie_url_template = 'http://{hostname}:{port}/magpie' + port = os.getenv('MAGPIE_PORT') + if port: + settings['magpie.port'] = port + hostname = os.getenv('HOSTNAME') + if hostname: + settings['magpie.url'] = magpie_url_template.format(hostname=hostname, port=settings['magpie.port']) + + # avoid cornice conflicting with magpie exception views + settings['handle_exceptions'] = False + + config = auth_config_from_settings(settings) + config.include('magpie') + # Don't use scan otherwise modules like 'magpie.adapter' are + # automatically found and cause import errors on missing packages + #config.scan('magpie') + config.set_default_permission(ADMIN_PERM) + + # include api views + print_log('Running api documentation setup...') + magpie_api_gen_disabled = os.getenv('MAGPIE_API_GENERATION_DISABLED') + if magpie_api_gen_disabled: + settings['magpie.api_generation_disabled'] = magpie_api_gen_disabled + if 'magpie.api_generation_disabled' not in settings: + settings['magpie.api_generation_disabled'] = False + + if not settings['magpie.api_generation_disabled']: + magpie_api_path = '{base}{path}'.format(base=settings['magpie.url'], path=SwaggerGenerator.path) + config.cornice_enable_openapi_view( + api_path=magpie_api_path, + title=TitleAPI, + description=__meta__.__description__, + version=__meta__.__version__ + ) + config.add_route(**service_api_route_info(SwaggerGenerator)) + config.add_view(api_schema, route_name=SwaggerGenerator.name, request_method='GET', + renderer='json', permission=NO_PERMISSION_REQUIRED) + config.add_route(**service_api_route_info(SwaggerAPI)) + + print_log('Starting Magpie app...') + wsgi_app = config.make_wsgi_app() + return wsgi_app + + +if __name__ == '__main__': + main() diff --git a/magpie/models.py b/magpie/models.py index e522416b0..822858056 100644 --- a/magpie/models.py +++ b/magpie/models.py @@ -1,7 +1,7 @@ -from definitions.pyramid_definitions import * -from definitions.ziggurat_definitions import * -from definitions.sqlalchemy_definitions import * -from api.api_except import * +from magpie.definitions.pyramid_definitions import * +from magpie.definitions.ziggurat_definitions import * +from magpie.definitions.sqlalchemy_definitions import * +from magpie.api.api_except import * Base = declarative_base() diff --git a/magpie/register.py b/magpie/register.py index c9d899091..d204b0526 100644 --- a/magpie/register.py +++ b/magpie/register.py @@ -6,7 +6,6 @@ import transaction import models from services import service_type_dict -from api.management.user.user_utils import create_user_resource_permission from common import * LOGGER = logging.getLogger(__name__) @@ -184,9 +183,9 @@ def get_magpie_url(): return 'http://{0}{1}'.format(hostname, magpie_port) -def get_twitcher_protected_service_url(magpie_service_name): +def get_twitcher_protected_service_url(magpie_service_name, hostname=None): try: - hostname = os.getenv('HOSTNAME') + hostname = hostname or os.getenv('HOSTNAME') twitcher_proxy = os.getenv('TWITCHER_PROTECTED_PATH') if hostname is None: raise ValueError("Environment variable was None", 'HOSTNAME') diff --git a/magpie/security.py b/magpie/security.py index 8f9a661c8..cd29401b4 100644 --- a/magpie/security.py +++ b/magpie/security.py @@ -1,6 +1,6 @@ -from definitions.pyramid_definitions import * -from definitions.ziggurat_definitions import * -from api.esgf import esgfopenid +from magpie.definitions.pyramid_definitions import * +from magpie.definitions.ziggurat_definitions import * +from magpie.api.esgf import esgfopenid from common import print_log from authomatic import Authomatic, provider_id from authomatic.providers import oauth2, openid diff --git a/magpie/services.py b/magpie/services.py index 1f09c731e..72d56fc22 100644 --- a/magpie/services.py +++ b/magpie/services.py @@ -1,9 +1,8 @@ from magpie import * from owsrequest import * -from definitions.ziggurat_definitions import * -from pyramid.security import Everyone as EVERYONE -from pyramid.security import Allow -from api.api_except import * +from magpie.definitions.ziggurat_definitions import * +from magpie.definitions.pyramid_definitions import EVERYONE, ALLOW +from magpie.api.api_except import * import models @@ -170,7 +169,7 @@ def __acl__(self): netcdf_file = netcdf_file.rsplit('/', 1)[0] else: - return [(Allow, EVERYONE, permission_requested,)] + return [(ALLOW, EVERYONE, permission_requested,)] if netcdf_file: verify_param('outputs/', paramCompare=netcdf_file, httpError=HTTPNotFound, diff --git a/magpie/ui/__init__.py b/magpie/ui/__init__.py index a9f490de1..4872d4120 100644 --- a/magpie/ui/__init__.py +++ b/magpie/ui/__init__.py @@ -6,6 +6,8 @@ def includeme(config): logger.info('Adding ui routes ...') # Add all the admin ui routes - config.include('ui.login') - config.include('ui.home') - config.include('ui.management') + config.include('magpie.ui.login') + config.include('magpie.ui.home') + config.include('magpie.ui.management') + config.include('magpie.ui.swagger') + config.scan() diff --git a/magpie/ui/home/__init__.py b/magpie/ui/home/__init__.py index 2878d2a9c..129d52a09 100644 --- a/magpie/ui/home/__init__.py +++ b/magpie/ui/home/__init__.py @@ -1,6 +1,6 @@ import logging import requests -from definitions.pyramid_definitions import * +from magpie.definitions.pyramid_definitions import * logger = logging.getLogger(__name__) diff --git a/magpie/ui/home/templates/home.mako b/magpie/ui/home/templates/home.mako index 10ad49386..598156374 100644 --- a/magpie/ui/home/templates/home.mako +++ b/magpie/ui/home/templates/home.mako @@ -6,15 +6,15 @@
- +
Edit Users
- +
Edit Groups
- +
Edit Services
diff --git a/magpie/ui/home/templates/template.mako b/magpie/ui/home/templates/template.mako index 3f59daad1..1c75e5502 100644 --- a/magpie/ui/home/templates/template.mako +++ b/magpie/ui/home/templates/template.mako @@ -4,8 +4,8 @@ Magpie admin area - - + + - - - -
- - - - - - diff --git a/magpie/ui/swagger-ui/magpie-rest-api-tmp.json b/magpie/ui/swagger-ui/magpie-rest-api-tmp.json deleted file mode 100644 index 9f6b2d953..000000000 --- a/magpie/ui/swagger-ui/magpie-rest-api-tmp.json +++ /dev/null @@ -1,6654 +0,0 @@ -{ - "openapi" : "3.0.0", - "info" : { - "description" : "Magpie Rest API Documentation\n", - "version" : "0.5.x", - "title" : "Magpie Rest API", - "contact" : { - "name" : "David Byrns", - "email" : "david.byrns@crim.ca" - } - }, - "servers" : [ { - "url" : "https://{bird_hostname}.crim.ca/magpie", - "description" : "Magpie Admin", - "variables" : { - "bird_hostname" : { - "enum" : [ "pluvier", "outarde", "hirondelle", "colibri" ], - "default" : "pluvier" - } - } - }, { - "url" : "http://{bird_hostname}.crim.ca:2001", - "description" : "Magpie Admin", - "variables" : { - "bird_hostname" : { - "enum" : [ "pluvier", "outarde", "hirondelle", "colibri" ], - "default" : "pluvier" - } - } - }, { - "url" : "https://{bird_hostname}.crim.ca:28001", - "description" : "Magpie Admin", - "variables" : { - "bird_hostname" : { - "enum" : [ "pluvier", "outarde", "hirondelle", "colibri" ], - "default" : "pluvier" - } - } - } ], - "paths" : { - "/version" : { - "get" : { - "summary" : "Get API version", - "responses" : { - "200" : { - "description" : "version successfully returned", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Version_GET_200" - } - } - } - } - } - } - }, - "/signin" : { - "post" : { - "tags" : [ "Login" ], - "summary" : "Logs user into the system", - "requestBody" : { - "required" : true, - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/SigninCredentials" - } - } - } - }, - "responses" : { - "200" : { - "description" : "Internal/External login successful", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Signin_POST_200" - } - } - } - }, - "400" : { - "description" : "Missing required information (login failed)", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Signin_POST_400" - } - } - } - }, - "401" : { - "description" : "Unauthorized access (wrong credentials, login failed)", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Signin_POST_401" - } - } - } - }, - "403" : { - "description" : "Failed user name vefication (login failed)", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Signin_POST_403" - } - } - } - }, - "404" : { - "description" : "Invalid login credential values (login failed)", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Signin_POST_404" - } - } - } - } - } - } - }, - "/signout" : { - "get" : { - "tags" : [ "Login" ], - "summary" : "Logs out currently logged in user sesion", - "description" : "Remove auth_tkt from header", - "operationId" : "signout", - "responses" : { - "200" : { - "description" : "Sign out successful", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Signout_POST_200" - } - } - } - } - } - } - }, - "/session" : { - "get" : { - "tags" : [ "Login" ], - "summary" : "get info about current session", - "description" : "Get info about current session", - "operationId" : "session", - "responses" : { - "200" : { - "description" : "user_name, user_email, group_name", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Session_GET_200" - } - } - } - } - } - } - }, - "/providers" : { - "get" : { - "tags" : [ "Login" ], - "summary" : "Get list of providers", - "responses" : { - "200" : { - "description" : "providers successfully returned", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Providers_GET_200" - } - } - } - } - } - } - }, - "/groups" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Group" ], - "summary" : "Get list of group names", - "responses" : { - "200" : { - "description" : "list of group names", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Groups_GET_200" - } - } - } - }, - "403" : { - "description" : "access to group names forbidden", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Groups_GET_403" - } - } - } - } - } - }, - "post" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Group" ], - "summary" : "Create a group", - "requestBody" : { - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/GroupBody" - } - } - } - }, - "responses" : { - "201" : { - "description" : "group created", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Group_POST_201" - } - } - } - }, - "403" : { - "description" : "[Forbidden] Group creation refused", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Group_POST_409" - } - } - } - }, - "409" : { - "description" : "[Conflict] Group already exists", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Group_POST_409" - } - } - } - } - } - } - }, - "/groups/{group_name}" : { - "put" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Group" ], - "summary" : "Update a group by name", - "parameters" : [ { - "name" : "group_name", - "in" : "path", - "required" : true, - "schema" : { - "type" : "string" - }, - "description" : "name of group to update" - } ], - "responses" : { - "200" : { - "description" : "user updated", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Group_PUT_200" - } - } - } - }, - "403" : { - "description" : "Forbidden access for group edition", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Group_PUT_403" - } - } - } - }, - "404" : { - "description" : "Group not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Group_PUT_404" - } - } - } - }, - "406" : { - "description" : "Invalid required parameter format received", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Group_PUT_406" - } - } - } - }, - "409" : { - "description" : "Conflict, this group already exists (or any sub-dependency)", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Group_PUT_409" - } - } - } - } - } - }, - "delete" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Group" ], - "summary" : "Delete a group by name", - "parameters" : [ { - "name" : "group_name", - "in" : "path", - "required" : true, - "schema" : { - "type" : "string" - }, - "description" : "name of group to delete" - } ], - "responses" : { - "200" : { - "description" : "group deleted", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Group_DELETE_200" - } - } - } - }, - "400" : { - "description" : "invalid group name", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Group_MatchDictCheck_400" - } - } - } - }, - "403" : { - "description" : "group deletion forbidden", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Group_DELETE_403" - } - } - } - }, - "404" : { - "description" : "group not found", - "content" : { - "application/json" : { - "schema" : { - "oneOf" : [ { - "$ref" : "#/components/responses/Group_MatchDictCheck_404" - }, { - "$ref" : "#/components/responses/NotFound_404" - } ] - } - } - } - } - } - } - }, - "/groups/{group_name}/users" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Group" ], - "summary" : "List all user from group", - "parameters" : [ { - "name" : "group_name", - "in" : "path", - "required" : true, - "schema" : { - "type" : "integer" - }, - "description" : "group name" - } ], - "responses" : { - "200" : { - "description" : "group users found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/GroupUsers_GET_200" - } - } - } - } - } - } - }, - "/groups/{group_name}/resources" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Group" ], - "summary" : "List all resources a group has permission on", - "parameters" : [ { - "name" : "group_name", - "in" : "path", - "description" : "name of the group", - "required" : true, - "schema" : { - "type" : "string" - } - } ], - "responses" : { - "200" : { - "description" : "group resources permissions found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/GroupResources_GET_200" - } - } - } - } - } - } - }, - "/groups/{group_name}/resources/{resource_id}/permissions" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Group" ], - "summary" : "List all permissions a group has on a specific resource", - "parameters" : [ { - "name" : "group_name", - "description" : "Name of the group", - "in" : "path", - "required" : true, - "schema" : { - "type" : "string" - } - }, { - "name" : "resource_id", - "description" : "Id of the resource", - "in" : "path", - "required" : true, - "schema" : { - "type" : "integer" - } - } ], - "responses" : { - "200" : { - "description" : "list of group permissions on a specific resource", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/GroupResourceOrServicePermissions_GET_200" - } - } - } - }, - "403" : { - "description" : "resource id query forbidden by db", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resource_MatchDictCheck_403" - } - } - } - }, - "404" : { - "description" : "resource id not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resource_MatchDictCheck_404" - } - } - } - }, - "406" : { - "description" : "invalid resource id (format/type)", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resource_MatchDictCheck_406" - } - } - } - } - } - }, - "post" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Group" ], - "summary" : "Create a permission on a specific resource for that group", - "parameters" : [ { - "name" : "group_name", - "description" : "Name of the group", - "in" : "path", - "required" : true, - "schema" : { - "type" : "string" - } - }, { - "name" : "resource_id", - "description" : "Id of the resource", - "in" : "path", - "required" : true, - "schema" : { - "type" : "integer" - } - } ], - "requestBody" : { - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/PermissionBodyCreate" - } - } - } - }, - "responses" : { - "201" : { - "description" : "permission created", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/GroupResourceOrServicePermission_POST_201" - } - } - } - }, - "403" : { - "description" : "permission creation forbidden by db", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/GroupResourceOrServicePermission_POST_403" - } - } - } - }, - "404" : { - "description" : "resource id not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resource_MatchDictCheck_404" - } - } - } - }, - "406" : { - "description" : "invalid resource id (format/type)", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resource_MatchDictCheck_406" - } - } - } - }, - "409" : { - "description" : "conflict with existing permission for corresponding group and resource", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/GroupResourceOrServicePermission_POST_409" - } - } - } - } - } - } - }, - "/groups/{group_name}/resources/{resource_id}/permissions/{permission_name}" : { - "delete" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Group" ], - "summary" : "Delete a permission on a resource for a group", - "parameters" : [ { - "name" : "group_name", - "description" : "name of the group", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true, - "example" : "admin" - }, { - "name" : "permission_name", - "description" : "name of the permission", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true, - "example" : "getcapabilities" - }, { - "name" : "resource_id", - "description" : "id of the resource", - "in" : "path", - "schema" : { - "type" : "integer" - }, - "required" : true, - "example" : 44 - } ], - "responses" : { - "200" : { - "description" : "permission deleted", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/GroupResourceOrServicePermission_DELETE_200" - } - } - } - }, - "404" : { - "description" : "group, resource or permission not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/NotFound_404" - } - } - } - } - } - } - }, - "/groups/{group_name}/services" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Group" ], - "summary" : "List all services a group has permission on", - "parameters" : [ { - "name" : "group_name", - "description" : "name of the group", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - } ], - "responses" : { - "200" : { - "description" : "list of all services a group has permissions on", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/GroupServices_GET_200" - } - } - } - } - } - } - }, - "/groups/{group_name}/services/{service_name}/permissions" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Group" ], - "summary" : "List all permissions a group has on a specific service", - "parameters" : [ { - "name" : "group_name", - "in" : "path", - "description" : "Name of the group", - "required" : true, - "schema" : { - "type" : "string" - } - }, { - "name" : "service_name", - "in" : "path", - "description" : "Name of the service", - "required" : true, - "schema" : { - "type" : "string" - } - } ], - "responses" : { - "200" : { - "description" : "list of group permissions on a specific service", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/GroupResourceOrServicePermissions_GET_200" - } - } - } - }, - "404" : { - "description" : "service or group not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/NotFound_404" - } - } - } - } - } - }, - "post" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Group" ], - "summary" : "Create a permission on a service for a group", - "parameters" : [ { - "name" : "group_name", - "in" : "path", - "description" : "Name of the group", - "required" : true, - "schema" : { - "type" : "string" - } - }, { - "name" : "service_name", - "in" : "path", - "description" : "Name of the service", - "required" : true, - "schema" : { - "type" : "string" - } - } ], - "requestBody" : { - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/GroupServicePermissionBodyCreate" - } - } - } - }, - "responses" : { - "201" : { - "description" : "group service permission created", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/GroupResourceOrServicePermission_POST_201" - } - } - } - }, - "404" : { - "description" : "service or group not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/NotFound_404" - } - } - } - } - } - } - }, - "/groups/{group_name}/services/{service_name}/permissions/{permission_name}" : { - "delete" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Group" ], - "summary" : "Delete a permission on a service for a group", - "parameters" : [ { - "name" : "group_name", - "description" : "name of the group", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - }, { - "name" : "permission_name", - "description" : "name of the permission", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - }, { - "name" : "service_name", - "description" : "name of the service", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - } ], - "responses" : { - "200" : { - "description" : "user not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/GroupResourceOrServicePermission_DELETE_200" - } - } - } - }, - "404" : { - "description" : "service or permission not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/NotFound_404" - } - } - } - } - } - } - }, - "/groups/{group_name}/services/{service_name}/resources" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Group" ], - "summary" : "List all resources under a service a group has permission on", - "parameters" : [ { - "name" : "group_name", - "description" : "name of the group", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - }, { - "name" : "service_name", - "description" : "name of the service", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - } ], - "responses" : { - "200" : { - "description" : "list of resources under the service the group has permission on", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/GroupServiceResources_GET_200" - } - } - } - }, - "400" : { - "description" : "group or service not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/NotFound_404" - } - } - } - } - } - } - }, - "/users" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "User" ], - "summary" : "List all users registered", - "responses" : { - "200" : { - "description" : "list of registered user", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Users_GET_200" - } - } - } - }, - "403" : { - "description" : "get list forbidden", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Users_GET_403" - } - } - } - } - } - }, - "post" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "User" ], - "summary" : "Create a new user", - "requestBody" : { - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/UserBodyCreate" - } - } - } - }, - "responses" : { - "201" : { - "description" : "user created", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Users_POST_201" - } - } - } - }, - "400" : { - "description" : "Invalid required parameter received", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/User_MatchDictCheck_400" - } - } - } - }, - "401" : { - "description" : "Unauthorized access for user creation", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Unauthorized_401" - } - } - } - }, - "409" : { - "description" : "Conflict, this user already exists (or any sub-dependency)", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/User_MatchDictCheck_409" - } - } - } - } - } - } - }, - "/users/{user_name}" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "User" ], - "summary" : "Get user information by name", - "parameters" : [ { - "name" : "user_name", - "description" : "name of user to get information from", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - } ], - "responses" : { - "200" : { - "description" : "found user information", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/User_GET_200" - } - } - } - }, - "403" : { - "description" : "get user information forbidden", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/User_MatchDictCheck_403" - } - } - } - }, - "404" : { - "description" : "user not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/NotFound_404" - } - } - } - } - } - }, - "put" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "User" ], - "summary" : "Update user informations by user name", - "parameters" : [ { - "name" : "user_name", - "description" : "Name of the user", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - } ], - "requestBody" : { - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/UserBodyEdit" - } - } - } - }, - "responses" : { - "200" : { - "description" : "user updated", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/User_PUT_200" - } - } - } - }, - "403" : { - "description" : "Forbidden access for user edition", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/User_PUT_403" - } - } - } - }, - "404" : { - "description" : "User or user-group not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/User_PUT_404" - } - } - } - }, - "409" : { - "description" : "Conflict, this user already exists (or any sub-dependency)", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/User_PUT_409" - } - } - } - } - } - }, - "delete" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "User" ], - "summary" : "Delete a user by name", - "parameters" : [ { - "name" : "user_name", - "description" : "Name of the user", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - } ], - "responses" : { - "200" : { - "description" : "user deleted", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/User_DELETE_200" - } - } - } - }, - "404" : { - "description" : "user not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/NotFound_404" - } - } - } - } - } - } - }, - "/users/{user_name}/groups" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "User" ], - "summary" : "List all groups a user belongs to", - "parameters" : [ { - "name" : "user_name", - "description" : "Name of the user", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true, - "example" : "simpleusername" - } ], - "responses" : { - "200" : { - "description" : "list of groups the user belongs to", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserGroups_GET_200" - } - } - } - }, - "404" : { - "description" : "user not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/NotFound_404" - } - } - } - } - } - }, - "post" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "User" ], - "summary" : "Assign a user to a group", - "parameters" : [ { - "name" : "user_name", - "description" : "Name of the user", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - } ], - "requestBody" : { - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/GroupBody" - } - } - } - }, - "responses" : { - "201" : { - "description" : "User Group relation created", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserGroup_POST_201" - } - } - } - }, - "404" : { - "description" : "user name does not exist", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/User_MatchDictCheck_404" - } - } - } - }, - "409" : { - "description" : "conflict, user already belongs to that group", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserGroup_POST_409" - } - } - } - } - } - } - }, - "/users/{user_name}/groups/{group_name}" : { - "delete" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "User" ], - "summary" : "Remove user from a group", - "parameters" : [ { - "name" : "user_name", - "description" : "Name of the user", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - }, { - "name" : "group_name", - "description" : "Name of the group", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - } ], - "responses" : { - "200" : { - "description" : "user removed from group", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserGroup_DELETE_201" - } - } - } - }, - "400" : { - "description" : "user does not belong to group", - "content" : { - "application/json" : { - "schema" : { - "oneOf" : [ { - "$ref" : "#/components/responses/User_MatchDictCheck_400" - }, { - "$ref" : "#/components/responses/Group_MatchDictCheck_400" - } ] - } - } - } - }, - "404" : { - "description" : "user does not belong to group", - "content" : { - "application/json" : { - "schema" : { - "oneOf" : [ { - "$ref" : "#/components/responses/UserGroup_DELETE_404" - }, { - "$ref" : "#/components/responses/User_MatchDictCheck_404" - }, { - "$ref" : "#/components/responses/Group_MatchDictCheck_404" - } ] - } - } - } - } - } - } - }, - "/users/{user_name}/inherited_resources" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "User" ], - "summary" : "List all resources a user has permission on with his inherited user and groups permissions", - "parameters" : [ { - "name" : "user_name", - "description" : "Name of the user", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - } ], - "responses" : { - "200" : { - "description" : "list of inherited resources permissions of the user", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserResources_GET_200" - } - } - } - }, - "404" : { - "description" : "could not find user name", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/NotFound_404" - } - } - } - } - } - } - }, - "/users/{user_name}/resources" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "User" ], - "summary" : "List all resources a user has direct permission on (not including his groups permissions)", - "parameters" : [ { - "name" : "user_name", - "description" : "Name of the user", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - } ], - "responses" : { - "200" : { - "description" : "list of direct resources permissions of the user", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserResources_GET_200" - } - } - } - }, - "404" : { - "description" : "could not find user name", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/NotFound_404" - } - } - } - } - } - } - }, - "/users/{user_name}/resources/{resource_id}/inherited_permissions" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "User" ], - "summary" : "List all permissions a user has on a specific resource with his inherited user and groups permissions", - "parameters" : [ { - "name" : "user_name", - "description" : "Name of the user", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - }, { - "name" : "resource_id", - "schema" : { - "type" : "integer" - }, - "description" : "Id of the resource", - "in" : "path", - "required" : true - } ], - "responses" : { - "200" : { - "description" : "list of all permissions the user has for a specific resource", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserResourcePermissions_GET_200" - } - } - } - } - } - } - }, - "/users/{user_name}/resources/{resource_id}/permissions" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "User" ], - "summary" : "List all direct permissions a user has on a specific resource (not including his groups permissions)", - "parameters" : [ { - "name" : "user_name", - "description" : "Name of the user", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - }, { - "name" : "resource_id", - "schema" : { - "type" : "integer" - }, - "description" : "Id of the resource", - "in" : "path", - "required" : true - } ], - "responses" : { - "200" : { - "description" : "list of all permissions the user has for a specific resource", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserResourcePermissions_GET_200" - } - } - } - } - } - }, - "post" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "User" ], - "summary" : "Create a permission on specific resource for a user", - "parameters" : [ { - "name" : "user_name", - "description" : "Name of the user", - "schema" : { - "type" : "string" - }, - "in" : "path", - "required" : true - }, { - "name" : "resource_id", - "schema" : { - "type" : "integer" - }, - "description" : "Id of the resource", - "in" : "path", - "required" : true - } ], - "requestBody" : { - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/PermissionBodyCreate" - } - } - } - }, - "responses" : { - "201" : { - "description" : "permission created" - }, - "409" : { - "description" : "Conflict, this permission on that resource already exist for that user" - } - } - } - }, - "/users/{user_name}/resources/{resource_id}/permissions/{permission_name}" : { - "delete" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "User" ], - "summary" : "Delete a permission on a resource for a user", - "parameters" : [ { - "name" : "user_name", - "description" : "name of the group", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - }, { - "name" : "permission_name", - "description" : "name of the permission", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - }, { - "name" : "resource_id", - "description" : "id of the resource", - "in" : "path", - "schema" : { - "type" : "integer" - }, - "required" : true - } ], - "responses" : { - "200" : { - "description" : "permission deleted" - }, - "400" : { - "description" : "permission not found" - } - } - } - }, - "/users/{user_name}/inherited_services" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "User" ], - "summary" : "List all services a user has permission on with his inherited user and groups permissions", - "parameters" : [ { - "name" : "user_name", - "description" : "Name of the user", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - } ], - "responses" : { - "200" : { - "description" : "list of services the user has permission on", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserServices_GET_200" - } - } - } - } - } - } - }, - "/users/{user_name}/services" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "User" ], - "summary" : "List all services a user has direct permission on (not including his groups permissions)", - "parameters" : [ { - "name" : "user_name", - "description" : "Name of the user", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - } ], - "responses" : { - "200" : { - "description" : "list of services the user has permission on", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserServices_GET_200" - } - } - } - } - } - } - }, - "/users/{user_name}/services/{service_name}/inherited_permissions" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "User" ], - "summary" : "List all permissions a user has on a service using all his inherited user and groups permissions", - "parameters" : [ { - "name" : "user_name", - "description" : "Name of the user", - "schema" : { - "type" : "string" - }, - "in" : "path", - "required" : true - }, { - "name" : "service_name", - "schema" : { - "type" : "string" - }, - "description" : "Name of the service", - "in" : "path", - "required" : true - } ], - "responses" : { - "200" : { - "description" : "list of permissions the user has on a specific service", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserServicePermissions_GET_200" - } - } - } - } - } - } - }, - "/users/{user_name}/services/{service_name}/permissions" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "User" ], - "summary" : "List all direct permissions a user has on a service (not including his groups permissions)", - "parameters" : [ { - "name" : "user_name", - "description" : "Name of the user", - "schema" : { - "type" : "string" - }, - "in" : "path", - "required" : true - }, { - "name" : "service_name", - "schema" : { - "type" : "string" - }, - "description" : "Name of the service", - "in" : "path", - "required" : true - } ], - "responses" : { - "200" : { - "description" : "list of permissions the user has on a specific service", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserServicePermissions_GET_200" - } - } - } - } - } - }, - "post" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "User" ], - "summary" : "Create a permission on a service for a user", - "requestBody" : { - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/PermissionBodyCreate" - } - } - } - }, - "parameters" : [ { - "name" : "user_name", - "description" : "Name of the group", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - }, { - "name" : "service_name", - "description" : "Name of the service", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - } ], - "responses" : { - "201" : { - "description" : "permission created for user on a specific service", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserServicePermissions_POST_201" - } - } - } - } - } - } - }, - "/users/{user_name}/services/{service_name}/permissions/{permission_name}" : { - "delete" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "User" ], - "summary" : "Delete a permission on a service for a user", - "parameters" : [ { - "name" : "user_name", - "description" : "name of the group", - "in" : "path", - "required" : true, - "schema" : { - "type" : "string" - } - }, { - "name" : "permission_name", - "description" : "name of the permission", - "in" : "path", - "required" : true, - "schema" : { - "type" : "string" - } - }, { - "name" : "service_name", - "description" : "name of the service", - "in" : "path", - "required" : true, - "schema" : { - "type" : "string" - } - } ], - "responses" : { - "200" : { - "description" : "permission deleted" - }, - "400" : { - "description" : "permission not found" - } - } - } - }, - "/users/{user_name}/services/{service_name}/inherited_resources" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "User" ], - "summary" : "List all resources under a service a user has permission on using all his inherited user and groups permissions", - "parameters" : [ { - "name" : "user_name", - "description" : "name of the group", - "in" : "path", - "required" : true, - "schema" : { - "type" : "string" - } - }, { - "name" : "service_name", - "description" : "name of the service", - "in" : "path", - "required" : true, - "schema" : { - "type" : "string" - } - } ], - "responses" : { - "200" : { - "description" : "list of service resources the user has inherited permission on", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserServiceResources_GET_200" - } - } - } - }, - "400" : { - "description" : "invalid inputs", - "content" : { - "application/json" : { - "schema" : { - "oneOf" : [ { - "$ref" : "#/components/responses/User_MatchDictCheck_400" - } ] - } - } - } - }, - "403" : { - "description" : "forbidden service obtention", - "content" : { - "application/json" : { - "schema" : { - "oneOf" : [ { - "$ref" : "#/components/responses/Service_MatchDictCheck_403" - } ] - } - } - } - }, - "404" : { - "description" : "service name not found", - "content" : { - "application/json" : { - "schema" : { - "oneOf" : [ { - "$ref" : "#/components/responses/Service_MatchDictCheck_404" - } ] - } - } - } - } - } - } - }, - "/users/{user_name}/services/{service_name}/resources" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "User" ], - "summary" : "List all resources under a service a user has direct permission on (not including his groups permissions)", - "parameters" : [ { - "name" : "user_name", - "description" : "name of the group", - "in" : "path", - "required" : true, - "schema" : { - "type" : "string" - } - }, { - "name" : "service_name", - "description" : "name of the service", - "in" : "path", - "required" : true, - "schema" : { - "type" : "string" - } - } ], - "responses" : { - "200" : { - "description" : "list of service resources the user has permission on", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserServiceResources_GET_200" - } - } - } - }, - "400" : { - "description" : "invalid inputs", - "content" : { - "application/json" : { - "schema" : { - "oneOf" : [ { - "$ref" : "#/components/responses/User_MatchDictCheck_400" - } ] - } - } - } - }, - "403" : { - "description" : "forbidden service obtention", - "content" : { - "application/json" : { - "schema" : { - "oneOf" : [ { - "$ref" : "#/components/responses/Service_MatchDictCheck_403" - } ] - } - } - } - }, - "404" : { - "description" : "service name not found", - "content" : { - "application/json" : { - "schema" : { - "oneOf" : [ { - "$ref" : "#/components/responses/Service_MatchDictCheck_404" - } ] - } - } - } - } - } - } - }, - "/users/current" : { - "get" : { - "tags" : [ "Current User" ], - "summary" : "Get currently logged in user information (anonymous if none logged in)", - "responses" : { - "200" : { - "description" : "found user information", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/User_GET_200" - } - } - } - }, - "403" : { - "description" : "get user information forbidden", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/User_MatchDictCheck_403" - } - } - } - } - } - }, - "put" : { - "tags" : [ "Current User" ], - "summary" : "Update currently logged in user information", - "requestBody" : { - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/UserBodyEdit" - } - } - } - }, - "responses" : { - "200" : { - "description" : "user updated", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/User_PUT_200" - } - } - } - }, - "403" : { - "description" : "Forbidden access for user edition", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/User_PUT_403" - } - } - } - }, - "404" : { - "description" : "User not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/User_PUT_404" - } - } - } - }, - "409" : { - "description" : "Conflict, this user already exists (or any sub-dependency)", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/User_PUT_409" - } - } - } - } - } - }, - "delete" : { - "tags" : [ "Current User" ], - "summary" : "Delete currently logged in user", - "responses" : { - "200" : { - "description" : "user deleted", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/User_DELETE_200" - } - } - } - }, - "404" : { - "description" : "user not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/NotFound_404" - } - } - } - } - } - } - }, - "/users/current/groups" : { - "get" : { - "tags" : [ "Current User" ], - "summary" : "List all groups the currently logged in user belongs to", - "responses" : { - "200" : { - "description" : "list of groups the user belongs to", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserGroups_GET_200" - } - } - } - }, - "404" : { - "description" : "user not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/NotFound_404" - } - } - } - } - } - }, - "post" : { - "tags" : [ "Current User" ], - "summary" : "Assign the currently logged in user to a group", - "requestBody" : { - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/GroupBody" - } - } - } - }, - "responses" : { - "201" : { - "description" : "User Group relation created", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserGroup_POST_201" - } - } - } - }, - "404" : { - "description" : "user name does not exist", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/User_MatchDictCheck_404" - } - } - } - }, - "409" : { - "description" : "conflict, user already belongs to that group", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserGroup_POST_409" - } - } - } - } - } - } - }, - "/users/current/groups/{group_name}" : { - "delete" : { - "tags" : [ "Current User" ], - "summary" : "Remove the currently logged in user from a group", - "parameters" : [ { - "name" : "group_name", - "description" : "Name of the group", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - } ], - "responses" : { - "200" : { - "description" : "user removed from group", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserGroup_DELETE_201" - } - } - } - }, - "400" : { - "description" : "user does not belong to group", - "content" : { - "application/json" : { - "schema" : { - "oneOf" : [ { - "$ref" : "#/components/responses/User_MatchDictCheck_400" - }, { - "$ref" : "#/components/responses/Group_MatchDictCheck_400" - } ] - } - } - } - }, - "404" : { - "description" : "user does not belong to group", - "content" : { - "application/json" : { - "schema" : { - "oneOf" : [ { - "$ref" : "#/components/responses/UserGroup_DELETE_404" - }, { - "$ref" : "#/components/responses/User_MatchDictCheck_404" - }, { - "$ref" : "#/components/responses/Group_MatchDictCheck_404" - } ] - } - } - } - } - } - } - }, - "/users/current/inherited_resources" : { - "get" : { - "tags" : [ "Current User" ], - "summary" : "List all resources the currently logged in user has permission on with his inherited user and groups permissions", - "responses" : { - "200" : { - "description" : "list of inherited resources permissions of the user", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserResources_GET_200" - } - } - } - }, - "404" : { - "description" : "could not find user name", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/NotFound_404" - } - } - } - } - } - } - }, - "/users/current/resources" : { - "get" : { - "tags" : [ "Current User" ], - "summary" : "List all resources the currently logged in user has direct permission on (not including his groups permissions)", - "responses" : { - "200" : { - "description" : "list of direct resources permissions of the user", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserResources_GET_200" - } - } - } - }, - "404" : { - "description" : "could not find user name", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/NotFound_404" - } - } - } - } - } - } - }, - "/users/current/resources/{resource_id}/inherited_permissions" : { - "get" : { - "tags" : [ "Current User" ], - "summary" : "List all permissions the currently logged in user has on a specific resource with his inherited user and groups permissions", - "parameters" : [ { - "name" : "resource_id", - "schema" : { - "type" : "integer" - }, - "description" : "Id of the resource", - "in" : "path", - "required" : true - } ], - "responses" : { - "200" : { - "description" : "list of all permissions the user has for a specific resource", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserResourcePermissions_GET_200" - } - } - } - } - } - } - }, - "/users/current/resources/{resource_id}/permissions" : { - "get" : { - "tags" : [ "Current User" ], - "summary" : "List all direct permissions the currently logged in user has on a specific resource (not including his groups permissions)", - "parameters" : [ { - "name" : "resource_id", - "schema" : { - "type" : "integer" - }, - "description" : "Id of the resource", - "in" : "path", - "required" : true - } ], - "responses" : { - "200" : { - "description" : "list of all permissions the user has for a specific resource", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserResourcePermissions_GET_200" - } - } - } - } - } - }, - "post" : { - "tags" : [ "Current User" ], - "summary" : "Create a permission on specific resource for the currently logged in user", - "parameters" : [ { - "name" : "resource_id", - "schema" : { - "type" : "integer" - }, - "description" : "Id of the resource", - "in" : "path", - "required" : true - } ], - "requestBody" : { - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/PermissionBodyCreate" - } - } - } - }, - "responses" : { - "201" : { - "description" : "permission created" - }, - "409" : { - "description" : "Conflict, this permission on that resource already exist for that user" - } - } - } - }, - "/users/current/resources/{resource_id}/permissions/{permission_name}" : { - "delete" : { - "tags" : [ "Current User" ], - "summary" : "Delete a permission on a resource for the currently logged in user", - "parameters" : [ { - "name" : "permission_name", - "description" : "name of the permission", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - }, { - "name" : "resource_id", - "description" : "id of the resource", - "in" : "path", - "schema" : { - "type" : "integer" - }, - "required" : true - } ], - "responses" : { - "200" : { - "description" : "permission deleted" - }, - "400" : { - "description" : "permission not found" - } - } - } - }, - "/users/current/inherited_services" : { - "get" : { - "tags" : [ "Current User" ], - "summary" : "List all services the currently logged in user has permission on with his inherited user and groups permissions", - "responses" : { - "200" : { - "description" : "list of services the user has permission on", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserServices_GET_200" - } - } - } - } - } - } - }, - "/users/current/services" : { - "get" : { - "tags" : [ "Current User" ], - "summary" : "List all services the currently logged in user has direct permission on (not including his groups permissions)", - "responses" : { - "200" : { - "description" : "list of services the user has permission on", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserServices_GET_200" - } - } - } - } - } - } - }, - "/users/current/services/{service_name}/inherited_permissions" : { - "get" : { - "tags" : [ "Current User" ], - "summary" : "List all permissions the currently logged in user has on a service using all his inherited user and groups permissions", - "parameters" : [ { - "name" : "service_name", - "schema" : { - "type" : "string" - }, - "description" : "Name of the service", - "in" : "path", - "required" : true - } ], - "responses" : { - "200" : { - "description" : "list of permissions the user has on a specific service", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserServicePermissions_GET_200" - } - } - } - } - } - } - }, - "/users/current/services/{service_name}/permissions" : { - "get" : { - "tags" : [ "Current User" ], - "summary" : "List all direct permissions the currently logged in user has on a service (not including his groups permissions)", - "parameters" : [ { - "name" : "service_name", - "schema" : { - "type" : "string" - }, - "description" : "Name of the service", - "in" : "path", - "required" : true - } ], - "responses" : { - "200" : { - "description" : "list of permissions the user has on a specific service", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserServicePermissions_GET_200" - } - } - } - } - } - }, - "post" : { - "tags" : [ "Current User" ], - "summary" : "Create a permission on a service for the currently logged in user", - "requestBody" : { - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/PermissionBodyCreate" - } - } - } - }, - "parameters" : [ { - "name" : "service_name", - "description" : "Name of the service", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - } ], - "responses" : { - "201" : { - "description" : "permission created for user on a specific service", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserServicePermissions_POST_201" - } - } - } - } - } - } - }, - "/users/current/services/{service_name}/permissions/{permission_name}" : { - "delete" : { - "tags" : [ "Current User" ], - "summary" : "Delete a permission on a service for the currently logged in user", - "parameters" : [ { - "name" : "permission_name", - "description" : "name of the permission", - "in" : "path", - "required" : true, - "schema" : { - "type" : "string" - } - }, { - "name" : "service_name", - "description" : "name of the service", - "in" : "path", - "required" : true, - "schema" : { - "type" : "string" - } - } ], - "responses" : { - "200" : { - "description" : "permission deleted" - }, - "400" : { - "description" : "permission not found" - } - } - } - }, - "/users/current/services/{service_name}/inherited_resources" : { - "get" : { - "tags" : [ "Current User" ], - "summary" : "List all resources under a service the currently logged in user has permission on using all his inherited user and groups permissions", - "parameters" : [ { - "name" : "service_name", - "description" : "name of the service", - "in" : "path", - "required" : true, - "schema" : { - "type" : "string" - } - } ], - "responses" : { - "200" : { - "description" : "list of service resources the user has inherited permission on", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserServiceResources_GET_200" - } - } - } - }, - "400" : { - "description" : "invalid inputs", - "content" : { - "application/json" : { - "schema" : { - "oneOf" : [ { - "$ref" : "#/components/responses/User_MatchDictCheck_400" - } ] - } - } - } - }, - "403" : { - "description" : "forbidden service obtention", - "content" : { - "application/json" : { - "schema" : { - "oneOf" : [ { - "$ref" : "#/components/responses/Service_MatchDictCheck_403" - } ] - } - } - } - }, - "404" : { - "description" : "service name not found", - "content" : { - "application/json" : { - "schema" : { - "oneOf" : [ { - "$ref" : "#/components/responses/Service_MatchDictCheck_404" - } ] - } - } - } - } - } - } - }, - "/users/current/services/{service_name}/resources" : { - "get" : { - "tags" : [ "Current User" ], - "summary" : "List all resources under a service a user has direct permission on (not including his groups permissions)", - "parameters" : [ { - "name" : "service_name", - "description" : "name of the service", - "in" : "path", - "required" : true, - "schema" : { - "type" : "string" - } - } ], - "responses" : { - "200" : { - "description" : "list of service resources the user has permission on", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/UserServiceResources_GET_200" - } - } - } - }, - "400" : { - "description" : "invalid inputs", - "content" : { - "application/json" : { - "schema" : { - "oneOf" : [ { - "$ref" : "#/components/responses/User_MatchDictCheck_400" - } ] - } - } - } - }, - "403" : { - "description" : "forbidden service obtention", - "content" : { - "application/json" : { - "schema" : { - "oneOf" : [ { - "$ref" : "#/components/responses/Service_MatchDictCheck_403" - } ] - } - } - } - }, - "404" : { - "description" : "service name not found", - "content" : { - "application/json" : { - "schema" : { - "oneOf" : [ { - "$ref" : "#/components/responses/Service_MatchDictCheck_404" - } ] - } - } - } - } - } - } - }, - "/resources" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Resource" ], - "summary" : "List all resources registered in the database", - "responses" : { - "200" : { - "description" : "list of all resources with corresponding information", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resources_GET_200" - } - } - } - } - } - }, - "post" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Resource" ], - "summary" : "Register a new resource", - "requestBody" : { - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ResourceBodyCreate" - } - } - } - }, - "responses" : { - "201" : { - "description" : "resource created", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resources_POST_201" - } - } - } - }, - "400" : { - "description" : "invalid format for required resource inputs", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resources_POST_403" - } - } - } - }, - "403" : { - "description" : "resource creation refused by db", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resources_POST_403" - } - } - } - }, - "404" : { - "description" : "parent resource not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resources_POST_404" - } - } - } - }, - "406" : { - "description" : "not acceptable service/resource types for creation", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resources_POST_406" - } - } - } - }, - "409" : { - "description" : "resource name conflict at corresponding tree level", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resources_POST_409" - } - } - } - }, - "500" : { - "description" : "error obtaining root service", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resources_POST_500" - } - } - } - } - } - } - }, - "/resources/{resource_id}" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Resource" ], - "summary" : "Get resource info", - "parameters" : [ { - "name" : "resource_id", - "description" : "Id of the resource", - "in" : "path", - "schema" : { - "type" : "integer" - }, - "required" : true - } ], - "responses" : { - "200" : { - "description" : "list of resource information and its children tree (recursive information)", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resource_GET_200" - } - } - } - }, - "403" : { - "description" : "resource id query forbidden by db", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resource_MatchDictCheck_403" - } - } - } - }, - "404" : { - "description" : "resource id not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resource_MatchDictCheck_404" - } - } - } - }, - "406" : { - "description" : "invalid resource id (format/type)", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resource_MatchDictCheck_406" - } - } - } - } - } - }, - "put" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Resource" ], - "summary" : "Rename resource", - "parameters" : [ { - "name" : "resource_id", - "description" : "id of resource to edit", - "in" : "path", - "schema" : { - "type" : "integer" - }, - "required" : true - } ], - "requestBody" : { - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ResourceBodyEdit" - } - } - } - }, - "responses" : { - "403" : { - "description" : "resource id query forbidden by db", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resource_MatchDictCheck_403" - } - } - } - }, - "404" : { - "description" : "resource id not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resource_MatchDictCheck_404" - } - } - } - }, - "406" : { - "description" : "invalid resource id (format/type)", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resource_MatchDictCheck_406" - } - } - } - } - } - }, - "delete" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Resource" ], - "summary" : "Unregister resource from the database", - "parameters" : [ { - "name" : "resource_id", - "description" : "id of the resource", - "in" : "path", - "schema" : { - "type" : "integer" - }, - "required" : true - } ], - "responses" : { - "200" : { - "description" : "resource deleted", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resource_DELETE_200" - } - } - } - }, - "403" : { - "description" : "resource deletion forbidden by db", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resource_DELETE_403" - } - } - } - }, - "404" : { - "description" : "resource id not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resource_MatchDictCheck_404" - } - } - } - }, - "406" : { - "description" : "invalid resource id (format/type)", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resource_MatchDictCheck_406" - } - } - } - } - } - } - }, - "/resources/{resource_id}/permissions" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Resource" ], - "summary" : "List all possible permissions for a resource", - "parameters" : [ { - "name" : "resource_id", - "description" : "Id of the resource", - "in" : "path", - "schema" : { - "type" : "integer" - }, - "required" : true - } ], - "responses" : { - "200" : { - "description" : "get available permissions of resource", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/ResourcePermissions_GET_200" - } - } - } - }, - "403" : { - "description" : "resource id query forbidden by db", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resource_MatchDictCheck_403" - } - } - } - }, - "404" : { - "description" : "resource id not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resource_MatchDictCheck_404" - } - } - } - }, - "406" : { - "description" : "invalid resource id (format/type)", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resource_MatchDictCheck_406" - } - } - } - } - } - } - }, - "/services" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Service" ], - "summary" : "List all services registered", - "parameters" : [ { - "name" : "service_name", - "description" : "name of service", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - } ], - "responses" : { - "200" : { - "description" : "get services information", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resources_GET_200" - } - } - } - } - } - }, - "post" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Service" ], - "summary" : "Register a new service", - "requestBody" : { - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ServiceBodyCreate" - } - } - } - }, - "responses" : { - "201" : { - "description" : "Service registered", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Services_POST_201" - } - } - } - }, - "400" : { - "description" : "service type does not exist", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Services_POST_400" - } - } - } - }, - "409" : { - "description" : "service with this name already exists", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Services_POST_409" - } - } - } - }, - "422" : { - "description" : "cannot process input value", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Matchdict_422" - } - } - } - } - } - } - }, - "/services/types/{service_type}" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Service" ], - "summary" : "List all a service filtered by type", - "parameters" : [ { - "name" : "service_type", - "description" : "type of service", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - } ], - "responses" : { - "200" : { - "description" : "get service filtered by a specific service type", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/ServicesFilteredType_GET_200" - } - } - } - } - } - } - }, - "/services/{service_name}" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Service" ], - "summary" : "Get service info", - "parameters" : [ { - "name" : "service_name", - "description" : "Name of the service", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - } ], - "responses" : { - "200" : { - "description" : "get service information", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Service_GET_200" - } - } - } - } - } - }, - "put" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Service" ], - "summary" : "Update service information (name or URL)", - "parameters" : [ { - "name" : "service_name", - "description" : "Name of the service", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - } ], - "requestBody" : { - "content" : { - "application/json" : { - "schema" : { - "oneOf" : [ { - "$ref" : "#/components/schemas/ServiceBodyEdit_NameAndURL" - }, { - "$ref" : "#/components/schemas/ServiceBodyEdit_Name" - }, { - "$ref" : "#/components/schemas/ServiceBodyEdit_URL" - } ] - } - } - } - }, - "responses" : { - "200" : { - "description" : "service updated", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Service_PUT_200" - } - } - } - }, - "400" : { - "description" : "service not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Service_PUT_400" - } - } - } - }, - "403" : { - "description" : "service not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Service_PUT_403" - } - } - } - }, - "404" : { - "description" : "service not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Service_MatchDictCheck_404" - } - } - } - }, - "409" : { - "description" : "service not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Service_PUT_409" - } - } - } - } - } - }, - "delete" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Service" ], - "summary" : "Unregister a service from the database", - "parameters" : [ { - "name" : "service_name", - "description" : "Name of the service", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - } ], - "responses" : { - "200" : { - "description" : "service unregistered success" - }, - "400" : { - "description" : "service not found" - } - } - } - }, - "/services/{service_name}/resources" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Service" ], - "summary" : "List all resources registered under a service", - "parameters" : [ { - "name" : "service_name", - "description" : "Name of the service", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - } ], - "responses" : { - "200" : { - "description" : "list of registered resources under the service", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/ServiceResources_GET_200" - } - } - } - }, - "404" : { - "description" : "Service not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/ServiceResources_GET_404" - } - } - } - } - } - }, - "post" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Resource", "Service" ], - "summary" : "Register a new resource directly under a service", - "requestBody" : { - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ResourceBodyCreate" - } - } - } - }, - "parameters" : [ { - "name" : "service_name", - "description" : "Name of the service", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - } ], - "responses" : { - "201" : { - "description" : "Resource registered success", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Resources_POST_201" - } - } - } - }, - "404" : { - "description" : "Service not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Service_MatchDictCheck_404" - } - } - } - }, - "409" : { - "description" : "Conflict, this resource already exists under that service", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Services_POST_409" - } - } - } - } - } - } - }, - "/services/{service_name}/permissions" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Service" ], - "summary" : "List all possible permissions for a service", - "parameters" : [ { - "name" : "service_name", - "description" : "Name of the service", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - } ], - "responses" : { - "200" : { - "description" : "get service permissions", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/ServicePermissions_GET_200" - } - } - } - }, - "404" : { - "description" : "service does not exist", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/Service_MatchDictCheck_404" - } - } - } - }, - "406" : { - "description" : "failed to retrieve permissions with corresponding service type", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/ServicePermissions_GET_406" - } - } - } - } - } - } - }, - "/services/types/{service_type}/resources/types" : { - "get" : { - "security" : [ { - "cookieAuth" : [ ] - } ], - "tags" : [ "Service" ], - "summary" : "List all resource type for a specific service type", - "parameters" : [ { - "name" : "service_type", - "description" : "Name of the service type", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true - } ], - "responses" : { - "200" : { - "description" : "list of all resources the user has permission on", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/ServiceTypeResourceTypes_GET_200" - } - } - } - }, - "404" : { - "description" : "could not find service type", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/responses/ServiceTypeResourceTypes_GET_404" - } - } - } - } - } - } - } - }, - "components" : { - "securitySchemes" : { - "cookieAuth" : { - "type" : "apiKey", - "in" : "cookie", - "name" : "auth_tkt" - } - }, - "schemas" : { - "Base" : { - "type" : "object", - "required" : [ "code", "detail", "type" ], - "properties" : { - "code" : { - "type" : "integer" - }, - "detail" : { - "type" : "string" - }, - "type" : { - "type" : "string" - } - } - }, - "AuthHeaders" : { - "type" : "string", - "example" : "auth_tkt=abcde12345; Path=/; HttpOnly" - }, - "ErrorDetail" : { - "type" : "object", - "required" : [ "detail" ] - }, - "NotFound" : { - "required" : [ "route_name", "request_url" ], - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "type" : "object", - "properties" : { - "route_name" : { - "type" : "string" - }, - "request_url" : { - "type" : "string" - }, - "paramCompare" : { - "type" : "string" - }, - "param" : { - "type" : "string" - } - } - } ] - }, - "SigninCredentials" : { - "type" : "object", - "required" : [ "user_name", "password" ], - "properties" : { - "user_name" : { - "type" : "string" - }, - "password" : { - "type" : "string" - }, - "provider_name" : { - "type" : "string", - "enum" : [ "ziggurat", "openid", "dkrz", "ipsl", "badc", "pcmdi", "smhi", "github" ] - } - } - }, - "Group" : { - "type" : "object", - "properties" : { - "users" : { - "type" : "array", - "items" : { - "type" : "string" - } - }, - "group_id" : { - "type" : "integer" - }, - "description" : { - "type" : "string" - }, - "member_count" : { - "type" : "integer" - }, - "group_name" : { - "type" : "string" - } - } - }, - "GroupBody" : { - "type" : "object", - "required" : [ "group_name" ], - "properties" : { - "group_name" : { - "description" : "The name of the group should be unique", - "type" : "string" - } - } - }, - "GroupServicePermissionBodyCreate" : { - "allOf" : [ { - "$ref" : "#/components/schemas/PermissionBodyCreate" - }, { - "type" : "object", - "properties" : { - "group_name" : { - "type" : "string" - }, - "service_name" : { - "type" : "string" - } - } - } ] - }, - "GroupResourcePermission" : { - "type" : "object", - "properties" : { - "permission_name" : { - "type" : "string" - }, - "resource_id" : { - "type" : "integer" - }, - "group_id" : { - "type" : "integer" - } - } - }, - "GroupServicePermission" : { - "type" : "object", - "properties" : { - "permission_name" : { - "type" : "string" - }, - "service_name" : { - "type" : "string" - }, - "group_id" : { - "type" : "integer" - } - } - }, - "User" : { - "type" : "object", - "required" : [ "user_name", "email", "group_names" ], - "properties" : { - "user_name" : { - "type" : "string" - }, - "email" : { - "type" : "string" - }, - "group_names" : { - "type" : "array", - "uniqueItems" : true, - "items" : { - "type" : "string" - } - } - } - }, - "UserBodyCreate" : { - "type" : "object", - "required" : [ "user_name", "email", "password", "group_name" ], - "properties" : { - "user_name" : { - "type" : "string" - }, - "email" : { - "type" : "string" - }, - "password" : { - "type" : "string" - }, - "group_name" : { - "type" : "string" - } - } - }, - "UserBodyEdit" : { - "type" : "object", - "properties" : { - "user_name" : { - "description" : "new user name to apply if specified", - "type" : "string" - }, - "email" : { - "description" : "new email to apply if specified", - "type" : "string" - }, - "password" : { - "description" : "new password to apply if specified", - "type" : "string" - } - } - }, - "UserGroup" : { - "type" : "object", - "properties" : { - "user_name" : { - "type" : "string" - }, - "group_name" : { - "type" : "string" - } - } - }, - "Resources" : { - "type" : "object", - "required" : [ "resources" ], - "properties" : { - "resources" : { - "type" : "object", - "properties" : { - "thredds" : { - "$ref" : "#/components/schemas/Service" - }, - "wfs" : { - "$ref" : "#/components/schemas/Service" - }, - "wps" : { - "$ref" : "#/components/schemas/Service" - }, - "ncwms" : { - "$ref" : "#/components/schemas/Service" - }, - "geoserverwms" : { - "$ref" : "#/components/schemas/Service" - } - } - } - } - }, - "ResourceBasic" : { - "type" : "object", - "required" : [ "resource_name", "resource_type", "resource_id" ], - "properties" : { - "resource_name" : { - "type" : "string" - }, - "resource_type" : { - "type" : "string" - }, - "resource_id" : { - "type" : "integer" - } - } - }, - "Resource" : { - "type" : "object", - "required" : [ "resource_name", "resource_type", "resource_id", "parent_id", "permission_names", "children" ], - "properties" : { - "resource_name" : { - "type" : "string" - }, - "resource_type" : { - "type" : "string" - }, - "resource_id" : { - "type" : "integer" - }, - "parent_id" : { - "type" : "integer", - "nullable" : true - }, - "permission_names" : { - "uniqueItems" : true, - "type" : "array", - "items" : { - "type" : "string" - } - }, - "children" : { - "oneOf" : [ { - "$ref" : "#/components/schemas/Resource" - }, { - "type" : "object", - "maxProperties" : 0 - } ] - } - } - }, - "ResourceTypes" : { - "type" : "object", - "required" : [ "resource_types" ], - "properties" : { - "resource_types" : { - "type" : "array", - "uniqueItems" : true, - "items" : { - "type" : "string" - } - } - } - }, - "ResourceBodyCreate" : { - "type" : "object", - "required" : [ "resource_name", "resource_type", "parent_id" ], - "properties" : { - "resource_name" : { - "type" : "string" - }, - "resource_type" : { - "type" : "string" - }, - "service_id" : { - "type" : "integer" - }, - "parent_id" : { - "type" : "integer" - } - } - }, - "ResourceBodyEdit" : { - "type" : "object", - "required" : [ "resource_name" ], - "properties" : { - "resource_name" : { - "type" : "string", - "description" : "new name to apply to the resource" - }, - "service_push" : { - "type" : "string", - "description" : "push renamed resource to Phoenix" - } - } - }, - "ServiceThredds" : { - "type" : "object", - "properties" : { - "thredds" : { - "$ref" : "#/components/schemas/Service" - } - } - }, - "ServiceGeoserverWMS" : { - "type" : "object", - "properties" : { - "geoserverwms" : { - "$ref" : "#/components/schemas/Service" - } - } - }, - "ServiceNCWMS" : { - "type" : "object", - "properties" : { - "ncwms" : { - "$ref" : "#/components/schemas/Service" - } - } - }, - "ServiceWFS" : { - "type" : "object", - "properties" : { - "wfs" : { - "$ref" : "#/components/schemas/Service" - } - } - }, - "ServiceWPS" : { - "type" : "object", - "properties" : { - "wps" : { - "$ref" : "#/components/schemas/Service" - } - } - }, - "Service" : { - "type" : "object", - "required" : [ "service_name", "service_type", "service_url", "public_url", "permission_names", "resource_id" ], - "properties" : { - "service_name" : { - "type" : "string" - }, - "service_type" : { - "type" : "string" - }, - "service_url" : { - "type" : "string" - }, - "public_url" : { - "type" : "string" - }, - "permission_names" : { - "$ref" : "#/components/schemas/Permissions/properties/permission_names" - }, - "resource_id" : { - "type" : "integer" - }, - "resources" : { - "$ref" : "#/components/schemas/Resource" - } - } - }, - "Services" : { - "type" : "object", - "required" : [ "services" ], - "properties" : { - "services" : { - "type" : "object", - "properties" : { - "thredds" : { - "$ref" : "#/components/schemas/Service" - }, - "wfs" : { - "$ref" : "#/components/schemas/Service" - }, - "wps" : { - "$ref" : "#/components/schemas/Service" - }, - "ncwms" : { - "$ref" : "#/components/schemas/Service" - }, - "geoserverwms" : { - "$ref" : "#/components/schemas/Service" - } - } - } - } - }, - "ServiceBodyCreate" : { - "type" : "object", - "required" : [ "service_name", "service_type", "service_url" ], - "properties" : { - "service_name" : { - "type" : "string" - }, - "service_type" : { - "type" : "string" - }, - "service_url" : { - "type" : "string" - } - } - }, - "ServiceBodyEdit_Name" : { - "type" : "object", - "required" : [ "service_name" ], - "properties" : { - "service_name" : { - "description" : "New name to apply to the service", - "type" : "string" - } - } - }, - "ServiceBodyEdit_URL" : { - "type" : "object", - "required" : [ "service_url" ], - "properties" : { - "service_url" : { - "description" : "New URL to apply to the service", - "type" : "string" - } - } - }, - "ServiceBodyEdit_NameAndURL" : { - "type" : "object", - "required" : [ "service_name", "service_url" ], - "properties" : { - "service_name" : { - "description" : "New name to apply to the service", - "type" : "string" - }, - "service_url" : { - "description" : "New URL to apply to the service", - "type" : "string" - } - } - }, - "PermissionBodyCreate" : { - "type" : "object", - "required" : [ "permission_name" ], - "properties" : { - "permission_name" : { - "type" : "string" - } - } - }, - "Permissions" : { - "type" : "object", - "required" : [ "permission_names" ], - "properties" : { - "permission_names" : { - "type" : "array", - "uniqueItems" : true, - "items" : { - "type" : "string" - } - } - } - }, - "Providers" : { - "type" : "object", - "properties" : { - "internal_providers" : { - "type" : "array", - "items" : { - "type" : "string" - } - }, - "external_providers" : { - "type" : "array", - "items" : { - "type" : "string" - } - }, - "provider_names" : { - "type" : "array", - "items" : { - "type" : "string" - } - } - } - } - }, - "responses" : { - "OK_200" : { - "description" : "HTTP 200 response (OK)", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "OK" - } - } - } - }, - "Unauthorized_401" : { - "description" : "HTTP 401 response (Unauthorized)", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "type" : "object", - "properties" : { - "request_url" : { - "type" : "string" - }, - "route_name" : { - "type" : "string" - } - } - } ] - }, - "example" : { - "code" : 401, - "type" : "application/json", - "detail" : "Unauthorized. Insufficient user privileges or missing authentication headers.", - "request_url" : "{bird_hostname}/magpie/{resquest_path}", - "route_name" : "{resquest_path}" - } - } - } - }, - "NotFound_404" : { - "description" : "HTTP 404 response (NotFound)", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "type" : "object", - "properties" : { - "request_url" : { - "type" : "string" - }, - "route_name" : { - "type" : "string" - } - } - } ] - }, - "example" : { - "code" : 404, - "type" : "application/json", - "detail" : "The route resource could not be found", - "request_url" : "{bird_hostname}/magpie/{resquest_path}", - "route_name" : "{resquest_path}" - } - } - } - }, - "Error_500" : { - "description" : "HTTP 500 response (InternalServerError)", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "type" : "object", - "properties" : { - "request_url" : { - "type" : "string" - }, - "route_name" : { - "type" : "string" - } - } - } ] - }, - "example" : { - "code" : 500, - "type" : "application/json", - "detail" : "Internal Server Error. Unhandled exception occurred", - "request_url" : "{bird_hostname}/magpie/{resquest_path}", - "route_name" : "{resquest_path}" - } - } - } - }, - "Matchdict_400" : { - "description" : "HTTP 400 response (BadRequest)", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 400, - "type" : "application/json", - "detail" : "Invalid value specified." - } - } - } - }, - "Matchdict_422" : { - "description" : "HTTP 422 response (UnprocessableEntry)", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 422, - "type" : "application/json", - "detail" : "Invalid value specified." - } - } - } - }, - "Providers_GET_200" : { - "description" : "login providers", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "$ref" : "#/components/schemas/Providers" - } ] - }, - "example" : { - "code" : 200, - "detail" : "Get providers successful", - "type" : "application/json", - "internal_providers" : [ "ziggurat" ], - "external_providers" : [ "openid", "dkrz", "ipsl", "badc", "pcmdi", "smhi", "github" ], - "provider_names" : [ "ziggurat", "openid", "dkrz", "ipsl", "badc", "pcmdi", "smhi", "github" ] - } - } - } - }, - "Signin_POST_200" : { - "description" : "sign in success", - "headers" : { - "Set-Cookie" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/AuthHeaders" - } ] - } - } - }, - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - } ] - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Login successful" - } - } - } - }, - "Signin_POST_400" : { - "description" : "sign in invalid input", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "type" : "object", - "properties" : { - "reason" : { - "type" : "string" - } - } - } ] - }, - "example" : { - "code" : 400, - "type" : "application/json", - "detail" : "Login failure", - "reason" : "could not retrieve `user_name`" - } - } - } - }, - "Signin_POST_401" : { - "description" : "sign in invalid credentials", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "type" : "object", - "properties" : { - "reason" : { - "type" : "string" - } - } - } ] - }, - "example" : { - "code" : 401, - "type" : "application/json", - "detail" : "Login failure", - "reason" : "incorrect password" - } - } - } - }, - "Signin_POST_403" : { - "description" : "sign in forbidden operation", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "type" : "object", - "properties" : { - "reason" : { - "type" : "string" - } - } - } ] - }, - "example" : { - "code" : 403, - "type" : "application/json", - "detail" : "Login failure", - "reason" : "could not verify `user_name`" - } - } - } - }, - "Signin_POST_404" : { - "description" : "sign in not found user", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "type" : "object", - "properties" : { - "reason" : { - "type" : "string" - } - } - } ] - }, - "example" : { - "code" : 404, - "type" : "application/json", - "detail" : "Login failure", - "reason" : "undefined `user_name`" - } - } - } - }, - "Signout_POST_200" : { - "description" : "sign out success, cookie headers cleared", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Sign out successful" - } - } - } - }, - "Services_GET_200" : { - "description" : "OK", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "$ref" : "#/components/schemas/Services" - } ] - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Get services successful", - "services" : { - "wps" : { - "malleefowl" : { - "service_type" : "wps", - "service_name" : "malleefowl", - "service_url" : "http://outarde.crim.ca:8091", - "public_url" : "https://outarde.crim.ca/twitcher/ows/proxy/malleefowl", - "permission_names" : [ ], - "resource_id" : 1 - }, - "lb_flyingpigeon" : { - "service_type" : "wps", - "service_name" : "lb_flyingpigeon", - "service_url" : "http://outarde.crim.ca:58093/wps", - "public_url" : "https://outarde.crim.ca/twitcher/ows/proxy/lb_flyingpigeon", - "permission_names" : [ ], - "resource_id" : 2 - }, - "catalog" : { - "service_type" : "wps", - "service_name" : "catalog", - "service_url" : "http://outarde.crim.ca:8086/pywps", - "public_url" : "https://outarde.crim.ca/twitcher/ows/proxy/catalog", - "permission_names" : [ ], - "resource_id" : 3 - } - }, - "thredds" : { - "thredds" : { - "service_type" : "thredds", - "service_name" : "thredds", - "service_url" : "http://outarde.crim.ca:8083/thredds", - "public_url" : "https://outarde.crim.ca/twitcher/ows/proxy/thredds", - "permission_names" : [ ], - "resource_id" : 5 - } - }, - "wfs" : { - "geoserver" : { - "service_type" : "wfs", - "service_name" : "geoserver", - "service_url" : "http://outarde.crim.ca:8087/geoserver", - "public_url" : "https://outarde.crim.ca/twitcher/ows/proxy/thredds", - "permission_names" : [ ], - "resource_id" : 6 - } - }, - "ncwms" : { - "ncWMS2" : { - "service_type" : "ncwms", - "service_name" : "ncWMS2", - "service_url" : "http://outarde.crim.ca:8080/ncWMS2", - "public_url" : "https://outarde.crim.ca/twitcher/ows/proxy/ncWMS2", - "permission_names" : [ ], - "resource_id" : 7 - } - }, - "geoserverwms" : { - "geoserverwms" : { - "service_type" : "geoserverwms", - "service_name" : "geoserverwms", - "service_url" : "http://outarde.crim.ca:8087/geoserverwms", - "public_url" : "https://outarde.crim.ca/twitcher/ows/proxy/geoserverwms", - "permission_names" : [ ], - "resource_id" : 8 - } - } - } - } - } - } - }, - "Services_POST_201" : { - "description" : "service created", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 201, - "type" : "application/json", - "detail" : "Create resource successful" - } - } - } - }, - "Services_POST_400" : { - "description" : "not found service type", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 400, - "type" : "application/json", - "detail" : "Specified `service_type` value does not correspond to any of the available types." - } - } - } - }, - "Services_POST_409" : { - "description" : "conflict service for creation", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 409, - "type" : "application/json", - "detail" : "Specified `service_name` value '' already exists." - } - } - } - }, - "ServicesFilteredType_GET_200" : { - "description" : "filtered services by specific type", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "$ref" : "#/components/schemas/Services" - } ] - }, - "example" : { - "services" : { - "wps" : { - "lb_malleefowl" : { - "service_type" : "wps", - "service_name" : "lb_malleefowl", - "service_url" : "http://outarde.crim.ca:58091/wps", - "public_url" : "https://outarde.crim.ca/twitcher/ows/proxy/lb_malleefowl", - "permission_names" : [ ], - "resource_id" : 1 - }, - "lb_flyingpigeon" : { - "service_type" : "wps", - "service_name" : "lb_flyingpigeon", - "service_url" : "http://outarde.crim.ca:58093/wps", - "public_url" : "https://outarde.crim.ca/twitcher/ows/proxy/lb_flyingpigeon", - "permission_names" : [ ], - "resource_id" : 2 - }, - "lb_catalog" : { - "service_type" : "wps", - "service_name" : "lb_catalog", - "service_url" : "http://outarde.crim.ca:58086/pywps", - "public_url" : "https://outarde.crim.ca/twitcher/ows/proxy/lb_catalog", - "permission_names" : [ ], - "resource_id" : 3 - } - } - } - } - } - } - }, - "Service_MatchDictCheck_400" : { - "description" : "invalid inputs for service", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - } ] - }, - "example" : { - "code" : 400, - "type" : "application/json", - "detail" : { - "oneOf" : [ "Invalid `service_name` value specified." ] - } - } - } - } - }, - "Service_MatchDictCheck_403" : { - "description" : "forbidden service operation", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - } ] - }, - "example" : { - "code" : 403, - "type" : "application/json", - "detail" : "Service query by name refused by db" - } - } - } - }, - "Service_MatchDictCheck_404" : { - "description" : "service not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 404, - "type" : "application/json", - "detail" : "Service name not found in db", - "service_name" : "" - } - } - } - }, - "Service_GET_200" : { - "description" : "get service succesful", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - } ], - "oneOf" : [ { - "$ref" : "#/components/schemas/ServiceThredds" - }, { - "$ref" : "#/components/schemas/ServiceGeoserverWMS" - }, { - "$ref" : "#/components/schemas/ServiceNCWMS" - }, { - "$ref" : "#/components/schemas/ServiceWFS" - }, { - "$ref" : "#/components/schemas/ServiceWPS" - } ] - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Get service successful", - "thredds" : { - "service_type" : "thredds", - "service_name" : "thredds", - "service_url" : "http://outarde.crim.ca:8083/thredds", - "public_url" : "https://outarde.crim.ca/twitcher/ows/proxy/thredds", - "permission_names" : [ ], - "resource_id" : 5 - } - } - } - } - }, - "Service_GET_403" : { - "description" : "forbidden get service", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 403, - "type" : "application/json", - "detail" : "Service query by name refused by db" - } - } - } - }, - "Service_GET_404" : { - "description" : "serivce not found", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "type" : "object", - "properties" : { - "service_name" : { - "type" : "string" - } - } - } ] - }, - "example" : { - "code" : 404, - "type" : "application/json", - "detail" : "Service name not found in db", - "service_name" : "service_does_not_exist" - } - } - } - }, - "Service_PUT_200" : { - "description" : "service update successful", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - } ] - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Update service successful" - } - } - } - }, - "Service_PUT_400" : { - "description" : "invalid inputs for service update", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - } ] - }, - "example" : { - "code" : 400, - "type" : "application/json", - "detail" : "Current service values are already equal to update values" - } - } - } - }, - "Service_PUT_403" : { - "description" : "forbidden update service", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "$ref" : "#/components/schemas/Service" - } ] - }, - "example" : { - "code" : 403, - "type" : "application/json", - "detail" : "Update service failed during value assignment", - "service" : { - "service_name" : "thredds", - "serivce_type" : "thredds", - "service_url" : "http://outarde.crim.ca:8083/thredds", - "public_url" : "https://outarde.crim.ca/twitcher/ows/proxy/thredds", - "permission_names" : [ ], - "resource_id" : 5 - }, - "new_service_name" : "some_new_name_for_thredds", - "new_service_url" : "some_new_url_for_thredds" - } - } - } - }, - "Service_PUT_409" : { - "description" : "conflict during service update (rename)", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - } ] - }, - "example" : { - "code" : 409, - "type" : "application/json", - "detail" : "Specified `service_name` value 'thredds' already exists" - } - } - } - }, - "ServicePermissions_GET_200" : { - "description" : "permissions of corresponding service type", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "$ref" : "#/components/schemas/Permissions" - } ] - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Get service permissions successful", - "permission_names" : [ "getcapabilities", "describeprocess", "execute" ] - } - } - } - }, - "ServicePermissions_GET_406" : { - "description" : "invalid service type", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "$ref" : "#/components/schemas/Service" - } ] - }, - "example" : { - "code" : 406, - "type" : "application/json", - "detail" : "Invalid service type specified by service", - "service" : { - "service_name" : "thredds", - "serivce_type" : "thredds", - "service_url" : "http://outarde.crim.ca:8083/thredds", - "public_url" : "https://outarde.crim.ca/twitcher/ows/proxy/thredds", - "permission_names" : [ ], - "resource_id" : 5 - } - } - } - } - }, - "Resources_GET_200" : { - "description" : "get list of all resources", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "$ref" : "#/components/schemas/Resources" - } ] - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Get resources successful", - "resources" : { - "wms" : { }, - "wfs" : { }, - "wps" : { - "lb_malleefowl" : { - "resource_id" : 1, - "permission_names" : [ ], - "service_name" : "lb_malleefowl", - "service_url" : "http://outarde.crim.ca:58091/wps", - "public_url" : "https://outarde.crim.ca/twitcher/ows/proxy/lb_malleefowl", - "service_type" : "wps", - "resources" : { } - }, - "lb_flyingpigeon" : { - "resource_id" : 2, - "permission_names" : [ ], - "service_name" : "lb_flyingpigeon", - "service_url" : "http://outarde.crim.ca:58093/wps", - "public_url" : "https://outarde.crim.ca/twitcher/ows/proxy/lb_flyingpigeon", - "service_type" : "wps", - "resources" : { } - }, - "lb_catalog" : { - "resource_id" : 3, - "permission_names" : [ ], - "service_name" : "lb_catalog", - "service_url" : "http://outarde.crim.ca:58086/pywps", - "public_url" : "https://outarde.crim.ca/twitcher/ows/proxy/lb_catalog", - "service_type" : "wps", - "resources" : { } - } - }, - "thredds" : { - "thredds" : { - "resource_id" : 5, - "permission_names" : [ ], - "service_name" : "thredds", - "service_url" : "http://outarde.crim.ca:8083/thredds", - "public_url" : "https://outarde.crim.ca/twitcher/ows/proxy/thredds", - "service_type" : "thredds", - "resources" : { - "7" : { - "resource_name" : "birdhouse", - "resource_id" : 7, - "permission_names" : [ ], - "parent_id" : 5, - "children" : { - "8" : { - "resource_name" : "nrcan", - "resource_id" : 8, - "permission_names" : [ ], - "parent_id" : 7, - "children" : { - "9" : { - "resource_name" : "nrcan_pr_3.nc", - "resource_id" : 9, - "permission_names" : [ ], - "parent_id" : 8, - "children" : { }, - "resource_type" : "file" - }, - "10" : { - "resource_name" : "nrcan_pr_5.nc", - "resource_id" : 10, - "permission_names" : [ ], - "parent_id" : 8, - "children" : { }, - "resource_type" : "file" - }, - "11" : { - "resource_name" : "nrcan_pr_10.nc", - "resource_id" : 11, - "permission_names" : [ ], - "parent_id" : 8, - "children" : { }, - "resource_type" : "file" - } - }, - "resource_type" : "directory" - }, - "12" : { - "resource_name" : "ouranos", - "resource_id" : 12, - "permission_names" : [ ], - "parent_id" : 7, - "children" : { - "20" : { - "resource_name" : "ouranos_pr_34.nc", - "resource_id" : 20, - "permission_names" : [ ], - "parent_id" : 12, - "children" : { }, - "resource_type" : "file" - }, - "22" : { - "resource_name" : "nimportequioi", - "resource_id" : 22, - "permission_names" : [ ], - "parent_id" : 12, - "children" : { }, - "resource_type" : "directory" - } - }, - "resource_type" : "directory" - }, - "13" : { - "resource_name" : "ec", - "resource_id" : 13, - "permission_names" : [ ], - "parent_id" : 7, - "children" : { }, - "resource_type" : "directory" - }, - "14" : { - "resource_name" : "cmip5", - "resource_id" : 14, - "permission_names" : [ ], - "parent_id" : 7, - "children" : { - "15" : { - "resource_name" : "ccma", - "resource_id" : 15, - "permission_names" : [ ], - "parent_id" : 14, - "children" : { - "16" : { - "resource_name" : "canesm2", - "resource_id" : 16, - "permission_names" : [ ], - "parent_id" : 15, - "children" : { - "17" : { - "resource_name" : "rcp85", - "resource_id" : 17, - "permission_names" : [ ], - "parent_id" : 16, - "children" : { - "18" : { - "resource_name" : "day", - "resource_id" : 18, - "permission_names" : [ ], - "parent_id" : 17, - "children" : { - "19" : { - "resource_name" : "pr_23.nc", - "resource_id" : 19, - "permission_names" : [ ], - "parent_id" : 18, - "children" : { }, - "resource_type" : "file" - } - }, - "resource_type" : "directory" - } - }, - "resource_type" : "directory" - } - }, - "resource_type" : "directory" - } - }, - "resource_type" : "directory" - } - }, - "resource_type" : "directory" - } - }, - "resource_type" : "directory" - }, - "21" : { - "resource_name" : "nimportequioi", - "resource_id" : 21, - "permission_names" : [ ], - "parent_id" : 5, - "children" : { }, - "resource_type" : "directory" - } - } - } - } - } - } - } - } - }, - "Resources_POST_201" : { - "description" : "create new resource successful", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "type" : "object", - "properties" : { - "resource" : { - "$ref" : "#/components/schemas/ResourceBasic" - } - } - } ] - }, - "example" : { - "code" : 201, - "type" : "application/json", - "detail" : "Create resource successful", - "resource" : { - "resource_name" : "my_new_awesome_resource", - "resource_type" : "wps", - "resource_id" : 9000 - } - } - } - } - }, - "Resources_POST_400" : { - "description" : "invalid inputs for resource creation", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 400, - "type" : "application/json", - "detail" : { - "oneOf" : [ "Invalid `resource_name` specified for child resource creation", "Invalid `resource_type` specified for child resource creation", "Invalid `parent_id` specified for child resource creation" ] - } - } - } - } - }, - "Resources_POST_403" : { - "description" : "forbidden resource creation", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 403, - "type" : "application/json", - "detail" : "Failed to insert new resource in service tree using parent id" - } - } - } - }, - "Resources_POST_404" : { - "description" : "parent resource id not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 404, - "type" : "application/json", - "detail" : "Could not find specified resource parent id" - } - } - } - }, - "Resources_POST_406" : { - "description" : "invalid inputs", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 406, - "type" : "application/json", - "detail" : "Invalid `resource_type` specified for service type ``" - } - } - } - }, - "Resources_POST_409" : { - "description" : "conflicting resource name at corresponding tree level", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 409, - "type" : "application/json", - "detail" : "Resource name already exists at requested tree level for creation" - } - } - } - }, - "Resources_POST_500" : { - "description" : "error occured during resource root-service definition", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 500, - "type" : "application/json", - "detail" : { - "oneOf" : [ "Failed retrieving `root_service` from db", "Invalid `root_service` retrieved from db is not a service" ] - } - } - } - } - }, - "Resource_MatchDictCheck_403" : { - "description" : "forbidden resource operation", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 403, - "type" : "application/json", - "detail" : "Resource query by id refused by db" - } - } - } - }, - "Resource_MatchDictCheck_404" : { - "description" : "resource id not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 404, - "type" : "application/json", - "detail" : "Resource ID not found in db" - } - } - } - }, - "Resource_MatchDictCheck_406" : { - "description" : "invalid type for resource id", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 406, - "type" : "application/json", - "detail" : "Resource ID is an invalid literal for `int` type" - } - } - } - }, - "Resource_GET_200" : { - "description" : "get resource information and its children resources", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "$ref" : "#/components/schemas/Resource" - } ] - }, - "example" : { - "type" : "application/json", - "code" : 200, - "detail" : "Get resource successful", - "14" : { - "resource_name" : "cmip5", - "resource_id" : 14, - "permission_names" : [ ], - "parent_id" : 7, - "children" : { - "15" : { - "resource_name" : "ccma", - "resource_id" : 15, - "permission_names" : [ ], - "parent_id" : 14, - "children" : { - "16" : { - "resource_name" : "canesm2", - "resource_id" : 16, - "permission_names" : [ ], - "parent_id" : 15, - "children" : { - "17" : { - "resource_name" : "rcp85", - "resource_id" : 17, - "permission_names" : [ ], - "parent_id" : 16, - "children" : { - "18" : { - "resource_name" : "day", - "resource_id" : 18, - "permission_names" : [ ], - "parent_id" : 17, - "children" : { - "19" : { - "resource_name" : "pr_23.nc", - "resource_id" : 19, - "permission_names" : [ ], - "parent_id" : 18, - "children" : { }, - "resource_type" : "file" - }, - "23" : { - "resource_name" : "test_with_service_resource_direct", - "resource_id" : 23, - "permission_names" : [ ], - "parent_id" : 18, - "children" : { }, - "resource_type" : "file" - } - }, - "resource_type" : "directory" - } - }, - "resource_type" : "directory" - } - }, - "resource_type" : "directory" - } - }, - "resource_type" : "directory" - } - }, - "resource_type" : "directory" - } - } - } - } - }, - "Resource_DELETE_200" : { - "description" : "delete resource by id successful", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Delete resource successful" - } - } - } - }, - "Resource_DELETE_403" : { - "description" : "delete resource forbidden", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "$ref" : "#/components/schemas/Resource" - } ] - }, - "example" : { - "code" : 403, - "type" : "application/json", - "detail" : "Delete resource branch from tree service failed", - "resource_name" : "lb_flyingpigeon", - "resource_type" : "wps", - "resource_id" : 101 - } - } - } - }, - "ResourcePermissions_GET_200" : { - "description" : "available permissions for a specific resource", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "$ref" : "#/components/schemas/Permissions" - } ] - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Get resource permissions successful", - "permission_names" : [ "getcapabilities", "describeprocess", "execute" ] - } - } - } - }, - "Group_MatchDictCheck_400" : { - "description" : "input `group_name` failed validation", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 400, - "type" : "application/json", - "detail" : { - "oneOf" : [ "Invalid `group_name` value specified.", "Invalid `group_name` length specified (>64 characters).", "Invalid `group_name` must be different than current name." ] - } - } - } - } - }, - "Group_MatchDictCheck_403" : { - "description" : "forbidden get group by name", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 403, - "type" : "application/json", - "detail" : "Group query by name refused by db" - } - } - } - }, - "Group_MatchDictCheck_404" : { - "description" : "group name not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 404, - "type" : "application/json", - "detail" : "Group name not found in db" - } - } - } - }, - "Groups_GET_200" : { - "description" : "list of all groups", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "type" : "object", - "properties" : { - "group_names" : { - "type" : "array", - "items" : { - "type" : "string" - } - } - } - } ] - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Get groups successful", - "group_names" : [ "admin", "anonymous", "user", "Francois-Xavier_Derue_dkrz", "David_Byrns_pcmdi", "renaud", "francis" ] - } - } - } - }, - "Groups_GET_403" : { - "description" : "forbidden fetch of group list", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 403, - "type" : "application/json", - "detail" : "Obtain group names refused by db" - } - } - } - }, - "Group_POST_201" : { - "description" : "group creation successful", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "type" : "object", - "properties" : { - "group" : { - "$ref" : "#/components/schemas/GroupBody" - } - } - } ] - }, - "example" : { - "code" : 201, - "type" : "application/json", - "detail" : "Create group successful", - "group" : { - "users" : [ ], - "group_id" : null, - "description" : "None", - "member_count" : null, - "group_name" : "my_new_group" - } - } - } - } - }, - "Group_POST_403" : { - "description" : "forbbiden group creation", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "type" : "object", - "properties" : { - "group_name" : { - "type" : "string" - } - } - } ] - }, - "example" : { - "code" : 403, - "type" : "application/json", - "detail" : "Create new group by name refused by db", - "group_name" : "my_new_group" - } - } - } - }, - "Group_POST_409" : { - "description" : "conflicting group name for creation", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "type" : "object", - "properties" : { - "group_name" : { - "type" : "string" - } - } - } ] - }, - "example" : { - "code" : 409, - "type" : "application/json", - "detail" : "Group already exists with this name", - "group_name" : "my_new_group" - } - } - } - }, - "Group_PUT_200" : { - "description" : "update group name successful", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Update group successful." - } - } - } - }, - "Group_PUT_403" : { - "description" : "forbidden group name update", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 403, - "type" : "application/json", - "detail" : { - "oneOf" : [ "Update group by name refused by db" ] - } - } - } - } - }, - "Group_PUT_404" : { - "description" : "group name not found for update", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 404, - "type" : "application/json", - "detail" : { - "oneOf" : [ "Could not find group in db" ] - } - } - } - } - }, - "Group_PUT_406" : { - "description" : "different group name required for update", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 406, - "type" : "application/json", - "detail" : "Invalid `group_name` must be different than current name." - } - } - } - }, - "Group_PUT_409" : { - "description" : "conflicting group name for update", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 409, - "type" : "application/json", - "detail" : { - "oneOf" : [ "Group name already exists." ] - } - } - } - } - }, - "Group_DELETE_200" : { - "description" : "delete group successful", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Delete group successful" - } - } - } - }, - "Group_DELETE_403" : { - "description" : "forbidden group deletion", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 403, - "type" : "application/json", - "detail" : "Delete group forbidden by db" - } - } - } - }, - "GroupUsers_GET_200" : { - "description" : "users that are part of the group", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "type" : "object", - "properties" : { - "user_names" : { - "type" : "array", - "items" : { - "type" : "string" - } - } - } - } ] - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Get group users successful", - "user_names" : [ "David_Byrns_pcmdi", "Francois-Xavier_Derue_dkrz" ] - } - } - } - }, - "GroupResources_GET_200" : { - "description" : "all resources a group has permissions on", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "$ref" : "#/components/schemas/Services" - } ] - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Get group resources successful", - "resources" : { - "type" : "object", - "properties" : { - "wms" : { - "ncWMS2" : { - "resource_id" : 21, - "permission_names" : [ ], - "service_name" : "ncWMS2", - "service_url" : "http://hirondelle.crim.ca:8080/ncWMS2", - "service_type" : "wms", - "resources" : { } - } - }, - "wfs" : { - "geoserver" : { - "resource_id" : 22, - "permission_names" : [ ], - "service_name" : "geoserver", - "service_url" : "http://hirondelle.crim.ca:8087/geoserver", - "service_type" : "wfs", - "resources" : { } - } - }, - "wps" : { - "malleefowl" : { - "resource_id" : 33, - "permission_names" : [ ], - "service_name" : "mallefowl", - "service_url" : "http://hirondelle.crim.ca:58091/wps", - "service_type" : "wps", - "resources" : { } - } - }, - "thredds" : { - "thredds" : { - "resource_id" : 12, - "permission_names" : [ ], - "service_name" : "thredds", - "service_url" : "http://hirondelle.crim.ca:8083/thredds", - "service_type" : "thredds", - "resources" : { } - } - } - } - } - } - } - } - }, - "GroupResourceOrServicePermissions_GET_200" : { - "description" : "resource/service a group has permissions on", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "$ref" : "#/components/schemas/Permissions" - } ] - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Get group resource permissions successful", - "permission_names" : [ "getcapabilities", "describeprocess" ] - } - } - } - }, - "GroupResourceOrServicePermission_POST_201" : { - "description" : "group resource permission creation succesful", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 201, - "type" : "application/json", - "detail" : "Create group resource permission successful" - } - } - } - }, - "GroupResourceOrServicePermission_POST_403" : { - "description" : "forbidden group resource/service permission creation", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "type" : "object", - "properties" : { - "call" : { - "type" : "object", - "properties" : { - "exception" : { - "type" : "string" - }, - "content" : { - "$ref" : "#/components/schemas/GroupResourcePermission" - } - } - } - } - } ] - }, - "example" : { - "code" : 403, - "type" : "application/json", - "detail" : "Create group resource permission failed", - "call" : { - "exception" : "", - "content" : { - "permission_name" : "getcapabilities", - "resource_id" : 50, - "group_id" : 10 - } - } - } - } - } - }, - "GroupResourceOrServicePermission_POST_409" : { - "description" : "conflicting group resource/service permission creation (relation already exists)", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "type" : "object", - "properties" : { - "call" : { - "type" : "object", - "properties" : { - "exception" : { - "type" : "string" - }, - "content" : { - "$ref" : "#/components/schemas/GroupResourcePermission" - } - } - } - } - } ] - }, - "example" : { - "code" : 409, - "type" : "application/json", - "detail" : "Add group resource permission refused by db", - "call" : { - "exception" : "", - "content" : { - "permission_name" : "getcapabilities", - "resource_id" : 50, - "group_id" : 10 - } - } - } - } - } - }, - "GroupResourceOrServicePermission_DELETE_200" : { - "description" : "group permission deleted", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Delete group resource permission successful" - } - } - } - }, - "GroupResourceOrServicePermission_DELETE_403" : { - "description" : "forbidden group resource/service permission deletion", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "type" : "object", - "properties" : { - "call" : { - "type" : "object", - "properties" : { - "exception" : { - "type" : "string" - }, - "content" : { - "$ref" : "#/components/schemas/GroupResourcePermission" - } - } - } - } - } ] - }, - "example" : { - "code" : 403, - "type" : "application/json", - "detail" : "Delete group resource permission refused by db", - "call" : { - "exception" : "", - "content" : { - "permission_name" : "getcapabilities", - "resource_id" : 50, - "group_id" : 10 - } - } - } - } - } - }, - "GroupServices_GET_200" : { - "description" : "services a group has permissions on", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "$ref" : "#/components/schemas/Services" - } ] - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Get group services successful", - "services" : { - "thredds" : { - "thredds" : { - "service_type" : "thredds", - "service_name" : "thredds", - "service_url" : "http://outarde.crim.ca:8083/thredds", - "permission_names" : [ "read" ], - "resource_id" : 5 - } - }, - "wps" : { - "lb_malleefowl" : { - "service_type" : "wps", - "service_name" : "lb_malleefowl", - "service_url" : "http://outarde.crim.ca:58091/wps", - "permission_names" : [ "getcapabilities" ], - "resource_id" : 1 - }, - "lb_flyingpigeon" : { - "service_type" : "wps", - "service_name" : "lb_flyingpigeon", - "service_url" : "http://outarde.crim.ca:58093/wps", - "permission_names" : [ "execute", "getcapabilities" ], - "resource_id" : 2 - } - } - } - } - } - } - }, - "GroupServiceResources_GET_200" : { - "description" : "resources of service a group has permissions on", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "type" : "object", - "properties" : { - "service" : { - "$ref" : "#/components/schemas/Service" - } - } - } ] - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Get group service resources successful", - "service" : { - "resource_id" : 5, - "permission_names" : [ "read" ], - "service_name" : "thredds", - "service_url" : "http://outarde.crim.ca:8083/thredds", - "service_type" : "thredds", - "resources" : { - "7" : { - "resource_name" : "birdhouse", - "resource_id" : 7, - "permission_names" : [ ], - "parent_id" : 5, - "children" : { - "8" : { - "resource_name" : "nrcan", - "resource_id" : 8, - "permission_names" : [ "read" ], - "parent_id" : 7, - "children" : { - "9" : { - "resource_name" : "nrcan_pr_3.nc", - "resource_id" : 9, - "permission_names" : [ "read" ], - "parent_id" : 8, - "children" : { }, - "resource_type" : "file" - } - }, - "resource_type" : "directory" - }, - "14" : { - "resource_name" : "cmip5", - "resource_id" : 14, - "permission_names" : [ ], - "parent_id" : 7, - "children" : { - "15" : { - "resource_name" : "ccma", - "resource_id" : 15, - "permission_names" : [ ], - "parent_id" : 14, - "children" : { - "16" : { - "resource_name" : "canesm2", - "resource_id" : 16, - "permission_names" : [ "read" ], - "parent_id" : 15, - "children" : { }, - "resource_type" : "directory" - } - }, - "resource_type" : "directory" - } - }, - "resource_type" : "directory" - } - }, - "resource_type" : "directory" - } - } - } - } - } - } - }, - "UserGroups_GET_200" : { - "description" : "groups a user is member of", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "type" : "object", - "properties" : { - "group_names" : { - "type" : "array", - "items" : { - "type" : "string" - } - } - } - } ] - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Get user groups successful", - "group_names" : [ "users", "mario bros" ] - } - } - } - }, - "UserGroup_POST_201" : { - "description" : "create user group successful", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 201, - "type" : "application/json", - "detail" : "Create user-group assignation successful" - } - } - } - }, - "UserGroup_POST_409" : { - "description" : "conflict while creating user group", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "type" : "object", - "properties" : { - "user_name" : { - "type" : "string" - }, - "group_name" : { - "type" : "string" - } - } - } ] - }, - "example" : { - "code" : 409, - "type" : "application/json", - "detail" : "User already belongs to this group", - "user_name" : "grandmaster", - "group_name" : "admin" - } - } - } - }, - "UserGroup_DELETE_201" : { - "description" : "delete user group successful", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Delete user-group successful" - } - } - } - }, - "UserGroup_DELETE_404" : { - "description" : "user group to delete not found", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "$ref" : "#/components/schemas/UserGroup" - } ] - }, - "example" : { - "code" : 404, - "type" : "application/json", - "detail" : "Invalid user-group combination for delete", - "user_name" : "basicuser", - "group_name" : "admins" - } - } - } - }, - "Users_GET_200" : { - "description" : "list of users", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "type" : "object", - "properties" : { - "user_names" : { - "type" : "array", - "items" : { - "type" : "string" - } - } - } - } ] - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Get users successful", - "user_names" : [ "admin", "anonymous", "mario", "luigi", "wario", "waluigi" ] - } - } - } - }, - "Users_GET_403" : { - "description" : "forbidden fetch of user list", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 403, - "type" : "application/json", - "detail" : "Get user query refused by db" - } - } - } - }, - "Users_POST_201" : { - "description" : "user creation successful", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 201, - "type" : "application/json", - "detail" : "Add user to db successful" - } - } - } - }, - "Users_POST_403" : { - "description" : "forbidden user creation", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 403, - "type" : "application/json", - "detail" : { - "oneOf" : [ "Group query was refused by db", "Group check query was refused by db", "User check query was refused by db", "Failed to add group to db", "Failed to add user to db", "New user query was refused by db", "Failed to add user-group to db", "New group query was refused by db" ] - } - } - } - } - }, - "User_MatchDictCheck_400" : { - "description" : "invalid inputs for user", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 400, - "type" : "application/json", - "detail" : { - "oneOf" : [ "Invalid `user_name` value specified", "Invalid `user_name` length specified (>{length} characters)", "Invalid `email` value specified", "Invalid `password` value specified", "Invalid `group_name` value specified" ] - } - } - } - } - }, - "User_MatchDictCheck_403" : { - "description" : "forbidden operation for user", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 403, - "type" : "application/json", - "detail" : { - "oneOf" : [ "User id query refused by db", "Anonymous user query refused by db", "User name query refused by db" ] - } - } - } - } - }, - "User_MatchDictCheck_404" : { - "description" : "user not found", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 404, - "type" : "application/json", - "detail" : { - "oneOf" : [ "User id not found in db", "Anonymous user not found in db", "User name not found in db" ] - } - } - } - } - }, - "User_MatchDictCheck_409" : { - "description" : "conflicting user name", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 409, - "type" : "application/json", - "detail" : { - "oneOf" : [ "Invalid `user_name` already logged in", "User name matches an already existing user name" ] - } - } - } - } - }, - "User_GET_200" : { - "description" : "get user information successful", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "$ref" : "#/components/schemas/User" - } ] - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Get session successful", - "user_name" : "johny", - "email" : "john.rambo@gmail.com", - "group_names" : [ "stars", "special_forces" ] - } - } - } - }, - "User_PUT_200" : { - "description" : "update user information successful", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Update user successful." - } - } - } - }, - "User_PUT_403" : { - "description" : "forbidden user information update", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 403, - "type" : "application/json", - "detail" : { - "oneOf" : [ "Update user by name refused by db" ] - } - } - } - } - }, - "User_PUT_404" : { - "description" : "user not found for update", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 404, - "type" : "application/json", - "detail" : { - "oneOf" : [ "Could not find user in db" ] - } - } - } - } - }, - "User_PUT_409" : { - "description" : "conflicting user name for update", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 409, - "type" : "application/json", - "detail" : { - "oneOf" : [ "Invalid `user_name` already logged in", "User name matches an already existing user name" ] - } - } - } - } - }, - "User_DELETE_200" : { - "description" : "user deletion successful", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Delete user successful" - } - } - } - }, - "User_DELETE_403" : { - "description" : "forbidden user deletion", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 403, - "type" : "application/json", - "detail" : { - "oneOf" : [ "Delete user by name refused by db" ] - } - } - } - } - }, - "UserResources_GET_200" : { - "description" : "resources a user has permissions on", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "$ref" : "#/components/schemas/Services" - } ] - }, - "example" : { - "resources" : { - "thredds" : { - "thredds" : { - "resource_id" : 5, - "permission_names" : [ ], - "service_name" : "thredds", - "service_url" : "http://outarde.crim.ca:8083/thredds", - "service_type" : "thredds", - "resources" : { - "7" : { - "resource_name" : "birdhouse", - "resource_id" : 7, - "permission_names" : [ ], - "parent_id" : 5, - "children" : { - "14" : { - "resource_name" : "cmip5", - "resource_id" : 14, - "permission_names" : [ ], - "parent_id" : 7, - "children" : { - "15" : { - "resource_name" : "ccma", - "resource_id" : 15, - "permission_names" : [ ], - "parent_id" : 14, - "children" : { - "16" : { - "resource_name" : "canesm2", - "resource_id" : 16, - "permission_names" : [ ], - "parent_id" : 15, - "children" : { - "17" : { - "resource_name" : "rcp85", - "resource_id" : 17, - "permission_names" : [ "write" ], - "parent_id" : 16, - "children" : { }, - "resource_type" : "directory" - } - }, - "resource_type" : "directory" - } - }, - "resource_type" : "directory" - } - }, - "resource_type" : "directory" - } - }, - "resource_type" : "directory" - } - } - } - }, - "wps" : { - "lb_malleefowl" : { - "resource_id" : 1, - "permission_names" : [ ], - "service_name" : "lb_malleefowl", - "service_url" : "http://outarde.crim.ca:58091/wps", - "service_type" : "wps", - "resources" : { } - }, - "lb_flyingpigeon" : { - "resource_id" : 2, - "permission_names" : [ ], - "service_name" : "lb_flyingpigeon", - "service_url" : "http://outarde.crim.ca:58093/wps", - "service_type" : "wps", - "resources" : { } - }, - "lb_catalog" : { - "resource_id" : 3, - "permission_names" : [ ], - "service_name" : "lb_catalog", - "service_url" : "http://outarde.crim.ca:58086/pywps", - "service_type" : "wps", - "resources" : { } - } - } - } - } - } - } - }, - "UserResourcePermissions_GET_200" : { - "description" : "permissions the user has on a specific resource", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "$ref" : "#/components/schemas/ResourceTypes" - } ] - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Get user resource permissions successful", - "permission_names" : [ "getcapabilities", "getmap" ] - } - } - } - }, - "UserServiceResources_GET_200" : { - "description" : "resources of a service the user has permissions on", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "$ref" : "#/components/schemas/Services" - } ] - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Get user service resources successful", - "service" : { - "resource_id" : 5, - "permission_names" : [ "read" ], - "service_name" : "thredds", - "service_url" : "http://outarde.crim.ca:8083/thredds", - "service_type" : "thredds", - "resources" : { - "7" : { - "resource_name" : "birdhouse", - "resource_id" : 7, - "permission_names" : [ ], - "parent_id" : 5, - "children" : { - "8" : { - "resource_name" : "nrcan", - "resource_id" : 8, - "permission_names" : [ "read" ], - "parent_id" : 7, - "children" : { - "9" : { - "resource_name" : "nrcan_pr_3.nc", - "resource_id" : 9, - "permission_names" : [ "read" ], - "parent_id" : 8, - "children" : { }, - "resource_type" : "file" - } - }, - "resource_type" : "directory" - }, - "14" : { - "resource_name" : "cmip5", - "resource_id" : 14, - "permission_names" : [ ], - "parent_id" : 7, - "children" : { - "15" : { - "resource_name" : "ccma", - "resource_id" : 15, - "permission_names" : [ ], - "parent_id" : 14, - "children" : { - "16" : { - "resource_name" : "canesm2", - "resource_id" : 16, - "permission_names" : [ "read" ], - "parent_id" : 15, - "children" : { }, - "resource_type" : "directory" - } - }, - "resource_type" : "directory" - } - }, - "resource_type" : "directory" - } - }, - "resource_type" : "directory" - } - } - } - } - } - } - }, - "UserServiceResources_GET_404" : { - "description" : "resources of a service the user has permissions on", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/NotFound" - }, { - "type" : "object", - "properties" : { - "service_name" : { - "type" : "string" - } - } - } ] - }, - "example" : { - "code" : 404, - "type" : "application/json", - "detail" : "Service name not found in db", - "param" : "None", - "service_name" : "foobar", - "request_url" : "https://outarde.crim.ca/magpie/services/foobar/resources", - "route_name" : "/services/foobar/resources" - } - } - } - }, - "ServiceResources_GET_200" : { - "description" : "all resources under a specific service", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "$ref" : "#/components/schemas/Services" - } ] - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Get service resources successful", - "geoserverwms" : { - "resource_id" : 123, - "permission_names" : [ ], - "service_name" : "geoserverwms", - "service_url" : "http://outarde.crim.ca:8087/geoserverwms", - "service_type" : "geoserverwms", - "resources" : { } - } - } - } - } - }, - "ServiceResources_GET_404" : { - "description" : "all resources under a specific service", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/NotFound" - }, { - "type" : "object", - "properties" : { - "service_name" : { - "type" : "string" - } - } - } ] - }, - "example" : { - "code" : 404, - "type" : "application/json", - "detail" : "Service name not found in db", - "param" : "None", - "service_name" : "foobar", - "request_url" : "https://outarde.crim.ca/magpie/services/foobar/resources", - "route_name" : "/services/foobar/resources" - } - } - } - }, - "UserServices_GET_200" : { - "description" : "services the user has permissions on", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "$ref" : "#/components/schemas/Services" - } ] - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Get user services successful", - "services" : { - "thredds" : { - "thredds" : { - "service_type" : "thredds", - "service_name" : "thredds", - "service_url" : "http://outarde.crim.ca:8083/thredds", - "permission_names" : [ "read" ], - "resource_id" : 5 - } - }, - "wps" : { - "lb_malleefowl" : { - "service_type" : "wps", - "service_name" : "lb_malleefowl", - "service_url" : "http://outarde.crim.ca:58091/wps", - "permission_names" : [ "getcapabilities" ], - "resource_id" : 1 - }, - "lb_flyingpigeon" : { - "service_type" : "wps", - "service_name" : "lb_flyingpigeon", - "service_url" : "http://outarde.crim.ca:58093/wps", - "permission_names" : [ "execute", "getcapabilities" ], - "resource_id" : 2 - } - } - } - } - } - } - }, - "UserServicePermissions_GET_200" : { - "description" : "list", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "$ref" : "#/components/schemas/Permissions" - } ] - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Get user service permissions successful", - "permission_names" : [ "getcapabilities", "getmap" ] - } - } - } - }, - "UserServicePermissions_POST_201" : { - "description" : "user resource permission creation successful", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Base" - }, - "example" : { - "code" : 201, - "type" : "application/json", - "detail" : "Create user resource permission successful" - } - } - } - }, - "ServiceTypeResourceTypes_GET_200" : { - "description" : "available resource types under a specific service type", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "$ref" : "#/components/schemas/ResourceTypes" - } ] - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Get service type resource types successful", - "resource_types" : [ "directory", "file" ] - } - } - } - }, - "ServiceTypeResourceTypes_GET_404" : { - "description" : "available resource types under a specific service type", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/NotFound" - }, - "example" : { - "code" : 404, - "type" : "application/json", - "detail" : "Invalid `service_type` does not exist to obtain its resource types", - "param" : "blah", - "paramCompare" : "[u'thredds', u'geoserverwms', u'wfs', u'ncwms', u'wps']", - "request_url" : "https://outarde.crim.ca/magpie/services/types/blah/resources/types", - "route_name" : "/services/types/blah/resources/types" - } - } - } - }, - "Session_GET_200" : { - "description" : "current user session", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "type" : "object", - "properties" : { - "authenticated" : { - "type" : "boolean" - } - } - } ] - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Get session successful", - "authenticated" : false - } - } - } - }, - "Version_GET_200" : { - "description" : "current Magpie Rest API and database versions", - "content" : { - "application/json" : { - "schema" : { - "allOf" : [ { - "$ref" : "#/components/schemas/Base" - }, { - "type" : "object", - "properties" : { - "version" : { - "type" : "string" - } - } - } ] - }, - "example" : { - "code" : 200, - "type" : "application/json", - "detail" : "Get version successful", - "version" : "0.5.3", - "db_version" : "a395ef9d3fe6" - } - } - } - } - } - } -} diff --git a/magpie/ui/swagger-ui/magpie-rest-api.json b/magpie/ui/swagger-ui/magpie-rest-api.json deleted file mode 100644 index 50ee46424..000000000 --- a/magpie/ui/swagger-ui/magpie-rest-api.json +++ /dev/null @@ -1 +0,0 @@ -{"info": {"version": "0.6.0", "title": "Magpie REST API"}, "paths": {"/users/{user_name}/groups": {"parameters": [{"required": true, "type": "string", "name": "user_name", "in": "path"}]}, "/users/current": {}, "/": {}, "/resources/{resource_id}": {"parameters": [{"required": true, "type": "string", "name": "resource_id", "in": "path"}]}, "/groups/{group_name}": {"parameters": [{"required": true, "type": "string", "name": "group_name", "in": "path"}]}, "/services/{service_name}": {"parameters": [{"required": true, "type": "string", "name": "service_name", "in": "path"}]}, "/users/{user_name}": {"parameters": [{"required": true, "type": "string", "name": "user_name", "in": "path"}]}, "/version": {"get": {"responses": {"200": {"description": "Get version successful.", "schema": {"required": ["code", "type", "detail", "version", "db_version"], "type": "object", "properties": {"db_version": {"type": "string", "description": "Database version string", "title": "Db Version"}, "code": {"type": "integer", "description": "HTTP response code", "title": "Code"}, "type": {"type": "string", "description": "Response content type", "title": "Type"}, "detail": {"type": "string", "description": "Response status message", "title": "Detail"}, "version": {"type": "string", "description": "Magpie version string", "title": "Version"}}, "title": "Version_GET_Schema"}}}, "produces": ["application/json"]}}, "__api__": {"get": {"responses": {"default": {"description": "UNDOCUMENTED RESPONSE"}}, "produces": ["application/json"]}}}, "basePath": "/", "swagger": "2.0"} \ No newline at end of file diff --git a/magpie/ui/swagger-ui/oauth2-redirect.html b/magpie/ui/swagger-ui/oauth2-redirect.html deleted file mode 100644 index fb68399d2..000000000 --- a/magpie/ui/swagger-ui/oauth2-redirect.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - diff --git a/magpie/ui/swagger-ui/swagger-ui-bundle.js b/magpie/ui/swagger-ui/swagger-ui-bundle.js deleted file mode 100644 index 94c1b2a7b..000000000 --- a/magpie/ui/swagger-ui/swagger-ui-bundle.js +++ /dev/null @@ -1,104 +0,0 @@ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SwaggerUIBundle=t():e.SwaggerUIBundle=t()}(this,function(){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var i=t[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist",n(n.s=1172)}([function(e,t,n){"use strict";e.exports=n(94)},function(e,t,n){e.exports=n(956)()},function(e,t,n){"use strict";t.__esModule=!0,t.default=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}},function(e,t,n){"use strict";t.__esModule=!0;var r,i=n(329),o=(r=i)&&r.__esModule?r:{default:r};t.default=function(){function e(e,t){for(var n=0;n>>0;if(""+n!==t||4294967295===n)return NaN;t=n}return t<0?S(e)+t:t}function A(){return!0}function D(e,t,n){return(0===e||void 0!==n&&e<=-n)&&(void 0===t||void 0!==n&&t>=n)}function M(e,t){return T(e,t,0)}function O(e,t){return T(e,t,t)}function T(e,t,n){return void 0===e?n:e<0?Math.max(0,t+e):void 0===t?e:Math.min(t,e)}var P=0,I=1,R=2,N="function"==typeof Symbol&&Symbol.iterator,F="@@iterator",j=N||F;function B(e){this.next=e}function L(e,t,n,r){var i=0===e?t:1===e?n:[t,n];return r?r.value=i:r={value:i,done:!1},r}function q(){return{value:void 0,done:!0}}function z(e){return!!V(e)}function U(e){return e&&"function"==typeof e.next}function W(e){var t=V(e);return t&&t.call(e)}function V(e){var t=e&&(N&&e[N]||e[F]);if("function"==typeof t)return t}function H(e){return e&&"number"==typeof e.length}function J(e){return null===e||void 0===e?oe():a(e)?e.toSeq():function(e){var t=ue(e)||"object"==typeof e&&new te(e);if(!t)throw new TypeError("Expected Array or iterable object of values, or keyed object: "+e);return t}(e)}function G(e){return null===e||void 0===e?oe().toKeyedSeq():a(e)?s(e)?e.toSeq():e.fromEntrySeq():ae(e)}function K(e){return null===e||void 0===e?oe():a(e)?s(e)?e.entrySeq():e.toIndexedSeq():se(e)}function X(e){return(null===e||void 0===e?oe():a(e)?s(e)?e.entrySeq():e:se(e)).toSetSeq()}B.prototype.toString=function(){return"[Iterator]"},B.KEYS=P,B.VALUES=I,B.ENTRIES=R,B.prototype.inspect=B.prototype.toSource=function(){return this.toString()},B.prototype[j]=function(){return this},t(J,n),J.of=function(){return J(arguments)},J.prototype.toSeq=function(){return this},J.prototype.toString=function(){return this.__toString("Seq {","}")},J.prototype.cacheResult=function(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().toArray(),this.size=this._cache.length),this},J.prototype.__iterate=function(e,t){return le(this,e,t,!0)},J.prototype.__iterator=function(e,t){return ce(this,e,t,!0)},t(G,J),G.prototype.toKeyedSeq=function(){return this},t(K,J),K.of=function(){return K(arguments)},K.prototype.toIndexedSeq=function(){return this},K.prototype.toString=function(){return this.__toString("Seq [","]")},K.prototype.__iterate=function(e,t){return le(this,e,t,!1)},K.prototype.__iterator=function(e,t){return ce(this,e,t,!1)},t(X,J),X.of=function(){return X(arguments)},X.prototype.toSetSeq=function(){return this},J.isSeq=ie,J.Keyed=G,J.Set=X,J.Indexed=K;var Y,$,Z,Q="@@__IMMUTABLE_SEQ__@@";function ee(e){this._array=e,this.size=e.length}function te(e){var t=Object.keys(e);this._object=e,this._keys=t,this.size=t.length}function ne(e){this._iterable=e,this.size=e.length||e.size}function re(e){this._iterator=e,this._iteratorCache=[]}function ie(e){return!(!e||!e[Q])}function oe(){return Y||(Y=new ee([]))}function ae(e){var t=Array.isArray(e)?new ee(e).fromEntrySeq():U(e)?new re(e).fromEntrySeq():z(e)?new ne(e).fromEntrySeq():"object"==typeof e?new te(e):void 0;if(!t)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+e);return t}function se(e){var t=ue(e);if(!t)throw new TypeError("Expected Array or iterable object of values: "+e);return t}function ue(e){return H(e)?new ee(e):U(e)?new re(e):z(e)?new ne(e):void 0}function le(e,t,n,r){var i=e._cache;if(i){for(var o=i.length-1,a=0;a<=o;a++){var s=i[n?o-a:a];if(!1===t(s[1],r?s[0]:a,e))return a+1}return a}return e.__iterateUncached(t,n)}function ce(e,t,n,r){var i=e._cache;if(i){var o=i.length-1,a=0;return new B(function(){var e=i[n?o-a:a];return a++>o?{value:void 0,done:!0}:L(t,r?e[0]:a-1,e[1])})}return e.__iteratorUncached(t,n)}function pe(e,t){return t?function e(t,n,r,i){if(Array.isArray(n))return t.call(i,r,K(n).map(function(r,i){return e(t,r,i,n)}));if(he(n))return t.call(i,r,G(n).map(function(r,i){return e(t,r,i,n)}));return n}(t,e,"",{"":e}):fe(e)}function fe(e){return Array.isArray(e)?K(e).map(fe).toList():he(e)?G(e).map(fe).toMap():e}function he(e){return e&&(e.constructor===Object||void 0===e.constructor)}function de(e,t){if(e===t||e!=e&&t!=t)return!0;if(!e||!t)return!1;if("function"==typeof e.valueOf&&"function"==typeof t.valueOf){if((e=e.valueOf())===(t=t.valueOf())||e!=e&&t!=t)return!0;if(!e||!t)return!1}return!("function"!=typeof e.equals||"function"!=typeof t.equals||!e.equals(t))}function me(e,t){if(e===t)return!0;if(!a(t)||void 0!==e.size&&void 0!==t.size&&e.size!==t.size||void 0!==e.__hash&&void 0!==t.__hash&&e.__hash!==t.__hash||s(e)!==s(t)||u(e)!==u(t)||c(e)!==c(t))return!1;if(0===e.size&&0===t.size)return!0;var n=!l(e);if(c(e)){var r=e.entries();return t.every(function(e,t){var i=r.next().value;return i&&de(i[1],e)&&(n||de(i[0],t))})&&r.next().done}var i=!1;if(void 0===e.size)if(void 0===t.size)"function"==typeof e.cacheResult&&e.cacheResult();else{i=!0;var o=e;e=t,t=o}var p=!0,f=t.__iterate(function(t,r){if(n?!e.has(t):i?!de(t,e.get(r,y)):!de(e.get(r,y),t))return p=!1,!1});return p&&e.size===f}function ve(e,t){if(!(this instanceof ve))return new ve(e,t);if(this._value=e,this.size=void 0===t?1/0:Math.max(0,t),0===this.size){if($)return $;$=this}}function ge(e,t){if(!e)throw new Error(t)}function ye(e,t,n){if(!(this instanceof ye))return new ye(e,t,n);if(ge(0!==n,"Cannot step a Range by 0"),e=e||0,void 0===t&&(t=1/0),n=void 0===n?1:Math.abs(n),tr?{value:void 0,done:!0}:L(e,i,n[t?r-i++:i++])})},t(te,G),te.prototype.get=function(e,t){return void 0===t||this.has(e)?this._object[e]:t},te.prototype.has=function(e){return this._object.hasOwnProperty(e)},te.prototype.__iterate=function(e,t){for(var n=this._object,r=this._keys,i=r.length-1,o=0;o<=i;o++){var a=r[t?i-o:o];if(!1===e(n[a],a,this))return o+1}return o},te.prototype.__iterator=function(e,t){var n=this._object,r=this._keys,i=r.length-1,o=0;return new B(function(){var a=r[t?i-o:o];return o++>i?{value:void 0,done:!0}:L(e,a,n[a])})},te.prototype[d]=!0,t(ne,K),ne.prototype.__iterateUncached=function(e,t){if(t)return this.cacheResult().__iterate(e,t);var n=W(this._iterable),r=0;if(U(n))for(var i;!(i=n.next()).done&&!1!==e(i.value,r++,this););return r},ne.prototype.__iteratorUncached=function(e,t){if(t)return this.cacheResult().__iterator(e,t);var n=W(this._iterable);if(!U(n))return new B(q);var r=0;return new B(function(){var t=n.next();return t.done?t:L(e,r++,t.value)})},t(re,K),re.prototype.__iterateUncached=function(e,t){if(t)return this.cacheResult().__iterate(e,t);for(var n,r=this._iterator,i=this._iteratorCache,o=0;o=r.length){var t=n.next();if(t.done)return t;r[i]=t.value}return L(e,i,r[i++])})},t(ve,K),ve.prototype.toString=function(){return 0===this.size?"Repeat []":"Repeat [ "+this._value+" "+this.size+" times ]"},ve.prototype.get=function(e,t){return this.has(e)?this._value:t},ve.prototype.includes=function(e){return de(this._value,e)},ve.prototype.slice=function(e,t){var n=this.size;return D(e,t,n)?this:new ve(this._value,O(t,n)-M(e,n))},ve.prototype.reverse=function(){return this},ve.prototype.indexOf=function(e){return de(this._value,e)?0:-1},ve.prototype.lastIndexOf=function(e){return de(this._value,e)?this.size:-1},ve.prototype.__iterate=function(e,t){for(var n=0;n=0&&t=0&&nn?{value:void 0,done:!0}:L(e,o++,a)})},ye.prototype.equals=function(e){return e instanceof ye?this._start===e._start&&this._end===e._end&&this._step===e._step:me(this,e)},t(_e,n),t(be,_e),t(xe,_e),t(ke,_e),_e.Keyed=be,_e.Indexed=xe,_e.Set=ke;var Ee="function"==typeof Math.imul&&-2===Math.imul(4294967295,2)?Math.imul:function(e,t){var n=65535&(e|=0),r=65535&(t|=0);return n*r+((e>>>16)*r+n*(t>>>16)<<16>>>0)|0};function we(e){return e>>>1&1073741824|3221225471&e}function Se(e){if(!1===e||null===e||void 0===e)return 0;if("function"==typeof e.valueOf&&(!1===(e=e.valueOf())||null===e||void 0===e))return 0;if(!0===e)return 1;var t=typeof e;if("number"===t){if(e!=e||e===1/0)return 0;var n=0|e;for(n!==e&&(n^=4294967295*e);e>4294967295;)n^=e/=4294967295;return we(n)}if("string"===t)return e.length>Ie?function(e){var t=Fe[e];void 0===t&&(t=Ce(e),Ne===Re&&(Ne=0,Fe={}),Ne++,Fe[e]=t);return t}(e):Ce(e);if("function"==typeof e.hashCode)return e.hashCode();if("object"===t)return function(e){var t;if(Oe&&void 0!==(t=Me.get(e)))return t;if(void 0!==(t=e[Pe]))return t;if(!De){if(void 0!==(t=e.propertyIsEnumerable&&e.propertyIsEnumerable[Pe]))return t;if(void 0!==(t=function(e){if(e&&e.nodeType>0)switch(e.nodeType){case 1:return e.uniqueID;case 9:return e.documentElement&&e.documentElement.uniqueID}}(e)))return t}t=++Te,1073741824&Te&&(Te=0);if(Oe)Me.set(e,t);else{if(void 0!==Ae&&!1===Ae(e))throw new Error("Non-extensible objects are not allowed as keys.");if(De)Object.defineProperty(e,Pe,{enumerable:!1,configurable:!1,writable:!1,value:t});else if(void 0!==e.propertyIsEnumerable&&e.propertyIsEnumerable===e.constructor.prototype.propertyIsEnumerable)e.propertyIsEnumerable=function(){return this.constructor.prototype.propertyIsEnumerable.apply(this,arguments)},e.propertyIsEnumerable[Pe]=t;else{if(void 0===e.nodeType)throw new Error("Unable to set a non-enumerable property on object.");e[Pe]=t}}return t}(e);if("function"==typeof e.toString)return Ce(e.toString());throw new Error("Value type "+t+" cannot be hashed.")}function Ce(e){for(var t=0,n=0;n=t.length)throw new Error("Missing value for key: "+t[n]);e.set(t[n],t[n+1])}})},Be.prototype.toString=function(){return this.__toString("Map {","}")},Be.prototype.get=function(e,t){return this._root?this._root.get(0,void 0,e,t):t},Be.prototype.set=function(e,t){return Qe(this,e,t)},Be.prototype.setIn=function(e,t){return this.updateIn(e,y,function(){return t})},Be.prototype.remove=function(e){return Qe(this,e,y)},Be.prototype.deleteIn=function(e){return this.updateIn(e,function(){return y})},Be.prototype.update=function(e,t,n){return 1===arguments.length?e(this):this.updateIn([e],t,n)},Be.prototype.updateIn=function(e,t,n){n||(n=t,t=void 0);var r=function e(t,n,r,i){var o=t===y;var a=n.next();if(a.done){var s=o?r:t,u=i(s);return u===s?t:u}ge(o||t&&t.set,"invalid keyPath");var l=a.value;var c=o?y:t.get(l,y);var p=e(c,n,r,i);return p===c?t:p===y?t.remove(l):(o?Ze():t).set(l,p)}(this,nn(e),t,n);return r===y?void 0:r},Be.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):Ze()},Be.prototype.merge=function(){return rt(this,void 0,arguments)},Be.prototype.mergeWith=function(t){return rt(this,t,e.call(arguments,1))},Be.prototype.mergeIn=function(t){var n=e.call(arguments,1);return this.updateIn(t,Ze(),function(e){return"function"==typeof e.merge?e.merge.apply(e,n):n[n.length-1]})},Be.prototype.mergeDeep=function(){return rt(this,it,arguments)},Be.prototype.mergeDeepWith=function(t){var n=e.call(arguments,1);return rt(this,ot(t),n)},Be.prototype.mergeDeepIn=function(t){var n=e.call(arguments,1);return this.updateIn(t,Ze(),function(e){return"function"==typeof e.mergeDeep?e.mergeDeep.apply(e,n):n[n.length-1]})},Be.prototype.sort=function(e){return Mt(Ht(this,e))},Be.prototype.sortBy=function(e,t){return Mt(Ht(this,t,e))},Be.prototype.withMutations=function(e){var t=this.asMutable();return e(t),t.wasAltered()?t.__ensureOwner(this.__ownerID):this},Be.prototype.asMutable=function(){return this.__ownerID?this:this.__ensureOwner(new E)},Be.prototype.asImmutable=function(){return this.__ensureOwner()},Be.prototype.wasAltered=function(){return this.__altered},Be.prototype.__iterator=function(e,t){return new Ke(this,e,t)},Be.prototype.__iterate=function(e,t){var n=this,r=0;return this._root&&this._root.iterate(function(t){return r++,e(t[1],t[0],n)},t),r},Be.prototype.__ensureOwner=function(e){return e===this.__ownerID?this:e?$e(this.size,this._root,e,this.__hash):(this.__ownerID=e,this.__altered=!1,this)},Be.isMap=Le;var qe,ze="@@__IMMUTABLE_MAP__@@",Ue=Be.prototype;function We(e,t){this.ownerID=e,this.entries=t}function Ve(e,t,n){this.ownerID=e,this.bitmap=t,this.nodes=n}function He(e,t,n){this.ownerID=e,this.count=t,this.nodes=n}function Je(e,t,n){this.ownerID=e,this.keyHash=t,this.entries=n}function Ge(e,t,n){this.ownerID=e,this.keyHash=t,this.entry=n}function Ke(e,t,n){this._type=t,this._reverse=n,this._stack=e._root&&Ye(e._root)}function Xe(e,t){return L(e,t[0],t[1])}function Ye(e,t){return{node:e,index:0,__prev:t}}function $e(e,t,n,r){var i=Object.create(Ue);return i.size=e,i._root=t,i.__ownerID=n,i.__hash=r,i.__altered=!1,i}function Ze(){return qe||(qe=$e(0))}function Qe(e,t,n){var r,i;if(e._root){var o=x(_),a=x(b);if(r=et(e._root,e.__ownerID,0,void 0,t,n,o,a),!a.value)return e;i=e.size+(o.value?n===y?-1:1:0)}else{if(n===y)return e;i=1,r=new We(e.__ownerID,[[t,n]])}return e.__ownerID?(e.size=i,e._root=r,e.__hash=void 0,e.__altered=!0,e):r?$e(i,r):Ze()}function et(e,t,n,r,i,o,a,s){return e?e.update(t,n,r,i,o,a,s):o===y?e:(k(s),k(a),new Ge(t,r,[i,o]))}function tt(e){return e.constructor===Ge||e.constructor===Je}function nt(e,t,n,r,i){if(e.keyHash===r)return new Je(t,r,[e.entry,i]);var o,a=(0===n?e.keyHash:e.keyHash>>>n)&g,s=(0===n?r:r>>>n)&g;return new Ve(t,1<>1&1431655765))+(e>>2&858993459))+(e>>4)&252645135,e+=e>>8,127&(e+=e>>16)}function ut(e,t,n,r){var i=r?e:w(e);return i[t]=n,i}Ue[ze]=!0,Ue.delete=Ue.remove,Ue.removeIn=Ue.deleteIn,We.prototype.get=function(e,t,n,r){for(var i=this.entries,o=0,a=i.length;o=lt)return function(e,t,n,r){e||(e=new E);for(var i=new Ge(e,Se(n),[n,r]),o=0;o>>e)&g),o=this.bitmap;return 0==(o&i)?r:this.nodes[st(o&i-1)].get(e+m,t,n,r)},Ve.prototype.update=function(e,t,n,r,i,o,a){void 0===n&&(n=Se(r));var s=(0===t?n:n>>>t)&g,u=1<=ct)return function(e,t,n,r,i){for(var o=0,a=new Array(v),s=0;0!==n;s++,n>>>=1)a[s]=1&n?t[o++]:void 0;return a[r]=i,new He(e,o+1,a)}(e,f,l,s,d);if(c&&!d&&2===f.length&&tt(f[1^p]))return f[1^p];if(c&&d&&1===f.length&&tt(d))return d;var _=e&&e===this.ownerID,b=c?d?l:l^u:l|u,x=c?d?ut(f,p,d,_):function(e,t,n){var r=e.length-1;if(n&&t===r)return e.pop(),e;for(var i=new Array(r),o=0,a=0;a>>e)&g,o=this.nodes[i];return o?o.get(e+m,t,n,r):r},He.prototype.update=function(e,t,n,r,i,o,a){void 0===n&&(n=Se(r));var s=(0===t?n:n>>>t)&g,u=i===y,l=this.nodes,c=l[s];if(u&&!c)return this;var p=et(c,e,t+m,n,r,i,o,a);if(p===c)return this;var f=this.count;if(c){if(!p&&--f0&&r=0&&e=e.size||t<0)return e.withMutations(function(e){t<0?Ct(e,t).set(0,n):Ct(e,0,t+1).set(t,n)});t+=e._origin;var r=e._tail,i=e._root,o=x(b);t>=Dt(e._capacity)?r=Et(r,e.__ownerID,0,t,n,o):i=Et(i,e.__ownerID,e._level,t,n,o);if(!o.value)return e;if(e.__ownerID)return e._root=i,e._tail=r,e.__hash=void 0,e.__altered=!0,e;return xt(e._origin,e._capacity,e._level,i,r)}(this,e,t)},ft.prototype.remove=function(e){return this.has(e)?0===e?this.shift():e===this.size-1?this.pop():this.splice(e,1):this},ft.prototype.insert=function(e,t){return this.splice(e,0,t)},ft.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=this._origin=this._capacity=0,this._level=m,this._root=this._tail=null,this.__hash=void 0,this.__altered=!0,this):kt()},ft.prototype.push=function(){var e=arguments,t=this.size;return this.withMutations(function(n){Ct(n,0,t+e.length);for(var r=0;r>>t&g;if(r>=this.array.length)return new vt([],e);var i,o=0===r;if(t>0){var a=this.array[r];if((i=a&&a.removeBefore(e,t-m,n))===a&&o)return this}if(o&&!i)return this;var s=wt(this,e);if(!o)for(var u=0;u>>t&g;if(i>=this.array.length)return this;if(t>0){var o=this.array[i];if((r=o&&o.removeAfter(e,t-m,n))===o&&i===this.array.length-1)return this}var a=wt(this,e);return a.array.splice(i+1),r&&(a.array[i]=r),a};var gt,yt,_t={};function bt(e,t){var n=e._origin,r=e._capacity,i=Dt(r),o=e._tail;return a(e._root,e._level,0);function a(e,s,u){return 0===s?function(e,a){var s=a===i?o&&o.array:e&&e.array,u=a>n?0:n-a,l=r-a;l>v&&(l=v);return function(){if(u===l)return _t;var e=t?--l:u++;return s&&s[e]}}(e,u):function(e,i,o){var s,u=e&&e.array,l=o>n?0:n-o>>i,c=1+(r-o>>i);c>v&&(c=v);return function(){for(;;){if(s){var e=s();if(e!==_t)return e;s=null}if(l===c)return _t;var n=t?--c:l++;s=a(u&&u[n],i-m,o+(n<>>n&g,u=e&&s0){var l=e&&e.array[s],c=Et(l,t,n-m,r,i,o);return c===l?e:((a=wt(e,t)).array[s]=c,a)}return u&&e.array[s]===i?e:(k(o),a=wt(e,t),void 0===i&&s===a.array.length-1?a.array.pop():a.array[s]=i,a)}function wt(e,t){return t&&e&&t===e.ownerID?e:new vt(e?e.array.slice():[],t)}function St(e,t){if(t>=Dt(e._capacity))return e._tail;if(t<1<0;)n=n.array[t>>>r&g],r-=m;return n}}function Ct(e,t,n){void 0!==t&&(t|=0),void 0!==n&&(n|=0);var r=e.__ownerID||new E,i=e._origin,o=e._capacity,a=i+t,s=void 0===n?o:n<0?o+n:i+n;if(a===i&&s===o)return e;if(a>=s)return e.clear();for(var u=e._level,l=e._root,c=0;a+c<0;)l=new vt(l&&l.array.length?[void 0,l]:[],r),c+=1<<(u+=m);c&&(a+=c,i+=c,s+=c,o+=c);for(var p=Dt(o),f=Dt(s);f>=1<p?new vt([],r):h;if(h&&f>p&&am;y-=m){var _=p>>>y&g;v=v.array[_]=wt(v.array[_],r)}v.array[p>>>m&g]=h}if(s=f)a-=f,s-=f,u=m,l=null,d=d&&d.removeBefore(r,0,a);else if(a>i||f>>u&g;if(b!==f>>>u&g)break;b&&(c+=(1<i&&(l=l.removeBefore(r,u,a-c)),l&&fo&&(o=l.size),a(u)||(l=l.map(function(e){return pe(e)})),r.push(l)}return o>e.size&&(e=e.setSize(o)),at(e,t,r)}function Dt(e){return e>>m<=v&&a.size>=2*o.size?(r=(i=a.filter(function(e,t){return void 0!==e&&s!==t})).toKeyedSeq().map(function(e){return e[0]}).flip().toMap(),e.__ownerID&&(r.__ownerID=i.__ownerID=e.__ownerID)):(r=o.remove(t),i=s===a.size-1?a.pop():a.set(s,void 0))}else if(u){if(n===a.get(s)[1])return e;r=o,i=a.set(s,[t,n])}else r=o.set(t,a.size),i=a.set(a.size,[t,n]);return e.__ownerID?(e.size=r.size,e._map=r,e._list=i,e.__hash=void 0,e):Tt(r,i)}function Rt(e,t){this._iter=e,this._useKeys=t,this.size=e.size}function Nt(e){this._iter=e,this.size=e.size}function Ft(e){this._iter=e,this.size=e.size}function jt(e){this._iter=e,this.size=e.size}function Bt(e){var t=Qt(e);return t._iter=e,t.size=e.size,t.flip=function(){return e},t.reverse=function(){var t=e.reverse.apply(this);return t.flip=function(){return e.reverse()},t},t.has=function(t){return e.includes(t)},t.includes=function(t){return e.has(t)},t.cacheResult=en,t.__iterateUncached=function(t,n){var r=this;return e.__iterate(function(e,n){return!1!==t(n,e,r)},n)},t.__iteratorUncached=function(t,n){if(t===R){var r=e.__iterator(t,n);return new B(function(){var e=r.next();if(!e.done){var t=e.value[0];e.value[0]=e.value[1],e.value[1]=t}return e})}return e.__iterator(t===I?P:I,n)},t}function Lt(e,t,n){var r=Qt(e);return r.size=e.size,r.has=function(t){return e.has(t)},r.get=function(r,i){var o=e.get(r,y);return o===y?i:t.call(n,o,r,e)},r.__iterateUncached=function(r,i){var o=this;return e.__iterate(function(e,i,a){return!1!==r(t.call(n,e,i,a),i,o)},i)},r.__iteratorUncached=function(r,i){var o=e.__iterator(R,i);return new B(function(){var i=o.next();if(i.done)return i;var a=i.value,s=a[0];return L(r,s,t.call(n,a[1],s,e),i)})},r}function qt(e,t){var n=Qt(e);return n._iter=e,n.size=e.size,n.reverse=function(){return e},e.flip&&(n.flip=function(){var t=Bt(e);return t.reverse=function(){return e.flip()},t}),n.get=function(n,r){return e.get(t?n:-1-n,r)},n.has=function(n){return e.has(t?n:-1-n)},n.includes=function(t){return e.includes(t)},n.cacheResult=en,n.__iterate=function(t,n){var r=this;return e.__iterate(function(e,n){return t(e,n,r)},!n)},n.__iterator=function(t,n){return e.__iterator(t,!n)},n}function zt(e,t,n,r){var i=Qt(e);return r&&(i.has=function(r){var i=e.get(r,y);return i!==y&&!!t.call(n,i,r,e)},i.get=function(r,i){var o=e.get(r,y);return o!==y&&t.call(n,o,r,e)?o:i}),i.__iterateUncached=function(i,o){var a=this,s=0;return e.__iterate(function(e,o,u){if(t.call(n,e,o,u))return s++,i(e,r?o:s-1,a)},o),s},i.__iteratorUncached=function(i,o){var a=e.__iterator(R,o),s=0;return new B(function(){for(;;){var o=a.next();if(o.done)return o;var u=o.value,l=u[0],c=u[1];if(t.call(n,c,l,e))return L(i,r?l:s++,c,o)}})},i}function Ut(e,t,n,r){var i=e.size;if(void 0!==t&&(t|=0),void 0!==n&&(n===1/0?n=i:n|=0),D(t,n,i))return e;var o=M(t,i),a=O(n,i);if(o!=o||a!=a)return Ut(e.toSeq().cacheResult(),t,n,r);var s,u=a-o;u==u&&(s=u<0?0:u);var l=Qt(e);return l.size=0===s?s:e.size&&s||void 0,!r&&ie(e)&&s>=0&&(l.get=function(t,n){return(t=C(this,t))>=0&&ts)return{value:void 0,done:!0};var e=i.next();return r||t===I?e:L(t,u-1,t===P?void 0:e.value[1],e)})},l}function Wt(e,t,n,r){var i=Qt(e);return i.__iterateUncached=function(i,o){var a=this;if(o)return this.cacheResult().__iterate(i,o);var s=!0,u=0;return e.__iterate(function(e,o,l){if(!s||!(s=t.call(n,e,o,l)))return u++,i(e,r?o:u-1,a)}),u},i.__iteratorUncached=function(i,o){var a=this;if(o)return this.cacheResult().__iterator(i,o);var s=e.__iterator(R,o),u=!0,l=0;return new B(function(){var e,o,c;do{if((e=s.next()).done)return r||i===I?e:L(i,l++,i===P?void 0:e.value[1],e);var p=e.value;o=p[0],c=p[1],u&&(u=t.call(n,c,o,a))}while(u);return i===R?e:L(i,o,c,e)})},i}function Vt(e,t,n){var r=Qt(e);return r.__iterateUncached=function(r,i){var o=0,s=!1;return function e(u,l){var c=this;u.__iterate(function(i,u){return(!t||l0}function Kt(e,t,r){var i=Qt(e);return i.size=new ee(r).map(function(e){return e.size}).min(),i.__iterate=function(e,t){for(var n,r=this.__iterator(I,t),i=0;!(n=r.next()).done&&!1!==e(n.value,i++,this););return i},i.__iteratorUncached=function(e,i){var o=r.map(function(e){return e=n(e),W(i?e.reverse():e)}),a=0,s=!1;return new B(function(){var n;return s||(n=o.map(function(e){return e.next()}),s=n.some(function(e){return e.done})),s?{value:void 0,done:!0}:L(e,a++,t.apply(null,n.map(function(e){return e.value})))})},i}function Xt(e,t){return ie(e)?t:e.constructor(t)}function Yt(e){if(e!==Object(e))throw new TypeError("Expected [K, V] tuple: "+e)}function $t(e){return je(e.size),S(e)}function Zt(e){return s(e)?r:u(e)?i:o}function Qt(e){return Object.create((s(e)?G:u(e)?K:X).prototype)}function en(){return this._iter.cacheResult?(this._iter.cacheResult(),this.size=this._iter.size,this):J.prototype.cacheResult.call(this)}function tn(e,t){return e>t?1:e=0;n--)t={value:arguments[n],next:t};return this.__ownerID?(this.size=e,this._head=t,this.__hash=void 0,this.__altered=!0,this):An(e,t)},kn.prototype.pushAll=function(e){if(0===(e=i(e)).size)return this;je(e.size);var t=this.size,n=this._head;return e.reverse().forEach(function(e){t++,n={value:e,next:n}}),this.__ownerID?(this.size=t,this._head=n,this.__hash=void 0,this.__altered=!0,this):An(t,n)},kn.prototype.pop=function(){return this.slice(1)},kn.prototype.unshift=function(){return this.push.apply(this,arguments)},kn.prototype.unshiftAll=function(e){return this.pushAll(e)},kn.prototype.shift=function(){return this.pop.apply(this,arguments)},kn.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._head=void 0,this.__hash=void 0,this.__altered=!0,this):Dn()},kn.prototype.slice=function(e,t){if(D(e,t,this.size))return this;var n=M(e,this.size);if(O(t,this.size)!==this.size)return xe.prototype.slice.call(this,e,t);for(var r=this.size-n,i=this._head;n--;)i=i.next;return this.__ownerID?(this.size=r,this._head=i,this.__hash=void 0,this.__altered=!0,this):An(r,i)},kn.prototype.__ensureOwner=function(e){return e===this.__ownerID?this:e?An(this.size,this._head,e,this.__hash):(this.__ownerID=e,this.__altered=!1,this)},kn.prototype.__iterate=function(e,t){if(t)return this.reverse().__iterate(e);for(var n=0,r=this._head;r&&!1!==e(r.value,n++,this);)r=r.next;return n},kn.prototype.__iterator=function(e,t){if(t)return this.reverse().__iterator(e);var n=0,r=this._head;return new B(function(){if(r){var t=r.value;return r=r.next,L(e,n++,t)}return{value:void 0,done:!0}})},kn.isStack=En;var wn,Sn="@@__IMMUTABLE_STACK__@@",Cn=kn.prototype;function An(e,t,n,r){var i=Object.create(Cn);return i.size=e,i._head=t,i.__ownerID=n,i.__hash=r,i.__altered=!1,i}function Dn(){return wn||(wn=An(0))}function Mn(e,t){var n=function(n){e.prototype[n]=t[n]};return Object.keys(t).forEach(n),Object.getOwnPropertySymbols&&Object.getOwnPropertySymbols(t).forEach(n),e}Cn[Sn]=!0,Cn.withMutations=Ue.withMutations,Cn.asMutable=Ue.asMutable,Cn.asImmutable=Ue.asImmutable,Cn.wasAltered=Ue.wasAltered,n.Iterator=B,Mn(n,{toArray:function(){je(this.size);var e=new Array(this.size||0);return this.valueSeq().__iterate(function(t,n){e[n]=t}),e},toIndexedSeq:function(){return new Nt(this)},toJS:function(){return this.toSeq().map(function(e){return e&&"function"==typeof e.toJS?e.toJS():e}).__toJS()},toJSON:function(){return this.toSeq().map(function(e){return e&&"function"==typeof e.toJSON?e.toJSON():e}).__toJS()},toKeyedSeq:function(){return new Rt(this,!0)},toMap:function(){return Be(this.toKeyedSeq())},toObject:function(){je(this.size);var e={};return this.__iterate(function(t,n){e[n]=t}),e},toOrderedMap:function(){return Mt(this.toKeyedSeq())},toOrderedSet:function(){return vn(s(this)?this.valueSeq():this)},toSet:function(){return un(s(this)?this.valueSeq():this)},toSetSeq:function(){return new Ft(this)},toSeq:function(){return u(this)?this.toIndexedSeq():s(this)?this.toKeyedSeq():this.toSetSeq()},toStack:function(){return kn(s(this)?this.valueSeq():this)},toList:function(){return ft(s(this)?this.valueSeq():this)},toString:function(){return"[Iterable]"},__toString:function(e,t){return 0===this.size?e+t:e+" "+this.toSeq().map(this.__toStringMapper).join(", ")+" "+t},concat:function(){return Xt(this,function(e,t){var n=s(e),i=[e].concat(t).map(function(e){return a(e)?n&&(e=r(e)):e=n?ae(e):se(Array.isArray(e)?e:[e]),e}).filter(function(e){return 0!==e.size});if(0===i.length)return e;if(1===i.length){var o=i[0];if(o===e||n&&s(o)||u(e)&&u(o))return o}var l=new ee(i);return n?l=l.toKeyedSeq():u(e)||(l=l.toSetSeq()),(l=l.flatten(!0)).size=i.reduce(function(e,t){if(void 0!==e){var n=t.size;if(void 0!==n)return e+n}},0),l}(this,e.call(arguments,0)))},includes:function(e){return this.some(function(t){return de(t,e)})},entries:function(){return this.__iterator(R)},every:function(e,t){je(this.size);var n=!0;return this.__iterate(function(r,i,o){if(!e.call(t,r,i,o))return n=!1,!1}),n},filter:function(e,t){return Xt(this,zt(this,e,t,!0))},find:function(e,t,n){var r=this.findEntry(e,t);return r?r[1]:n},forEach:function(e,t){return je(this.size),this.__iterate(t?e.bind(t):e)},join:function(e){je(this.size),e=void 0!==e?""+e:",";var t="",n=!0;return this.__iterate(function(r){n?n=!1:t+=e,t+=null!==r&&void 0!==r?r.toString():""}),t},keys:function(){return this.__iterator(P)},map:function(e,t){return Xt(this,Lt(this,e,t))},reduce:function(e,t,n){var r,i;return je(this.size),arguments.length<2?i=!0:r=t,this.__iterate(function(t,o,a){i?(i=!1,r=t):r=e.call(n,r,t,o,a)}),r},reduceRight:function(e,t,n){var r=this.toKeyedSeq().reverse();return r.reduce.apply(r,arguments)},reverse:function(){return Xt(this,qt(this,!0))},slice:function(e,t){return Xt(this,Ut(this,e,t,!0))},some:function(e,t){return!this.every(Rn(e),t)},sort:function(e){return Xt(this,Ht(this,e))},values:function(){return this.__iterator(I)},butLast:function(){return this.slice(0,-1)},isEmpty:function(){return void 0!==this.size?0===this.size:!this.some(function(){return!0})},count:function(e,t){return S(e?this.toSeq().filter(e,t):this)},countBy:function(e,t){return function(e,t,n){var r=Be().asMutable();return e.__iterate(function(i,o){r.update(t.call(n,i,o,e),0,function(e){return e+1})}),r.asImmutable()}(this,e,t)},equals:function(e){return me(this,e)},entrySeq:function(){var e=this;if(e._cache)return new ee(e._cache);var t=e.toSeq().map(In).toIndexedSeq();return t.fromEntrySeq=function(){return e.toSeq()},t},filterNot:function(e,t){return this.filter(Rn(e),t)},findEntry:function(e,t,n){var r=n;return this.__iterate(function(n,i,o){if(e.call(t,n,i,o))return r=[i,n],!1}),r},findKey:function(e,t){var n=this.findEntry(e,t);return n&&n[0]},findLast:function(e,t,n){return this.toKeyedSeq().reverse().find(e,t,n)},findLastEntry:function(e,t,n){return this.toKeyedSeq().reverse().findEntry(e,t,n)},findLastKey:function(e,t){return this.toKeyedSeq().reverse().findKey(e,t)},first:function(){return this.find(A)},flatMap:function(e,t){return Xt(this,function(e,t,n){var r=Zt(e);return e.toSeq().map(function(i,o){return r(t.call(n,i,o,e))}).flatten(!0)}(this,e,t))},flatten:function(e){return Xt(this,Vt(this,e,!0))},fromEntrySeq:function(){return new jt(this)},get:function(e,t){return this.find(function(t,n){return de(n,e)},void 0,t)},getIn:function(e,t){for(var n,r=this,i=nn(e);!(n=i.next()).done;){var o=n.value;if((r=r&&r.get?r.get(o,y):y)===y)return t}return r},groupBy:function(e,t){return function(e,t,n){var r=s(e),i=(c(e)?Mt():Be()).asMutable();e.__iterate(function(o,a){i.update(t.call(n,o,a,e),function(e){return(e=e||[]).push(r?[a,o]:o),e})});var o=Zt(e);return i.map(function(t){return Xt(e,o(t))})}(this,e,t)},has:function(e){return this.get(e,y)!==y},hasIn:function(e){return this.getIn(e,y)!==y},isSubset:function(e){return e="function"==typeof e.includes?e:n(e),this.every(function(t){return e.includes(t)})},isSuperset:function(e){return(e="function"==typeof e.isSubset?e:n(e)).isSubset(this)},keyOf:function(e){return this.findKey(function(t){return de(t,e)})},keySeq:function(){return this.toSeq().map(Pn).toIndexedSeq()},last:function(){return this.toSeq().reverse().first()},lastKeyOf:function(e){return this.toKeyedSeq().reverse().keyOf(e)},max:function(e){return Jt(this,e)},maxBy:function(e,t){return Jt(this,t,e)},min:function(e){return Jt(this,e?Nn(e):Bn)},minBy:function(e,t){return Jt(this,t?Nn(t):Bn,e)},rest:function(){return this.slice(1)},skip:function(e){return this.slice(Math.max(0,e))},skipLast:function(e){return Xt(this,this.toSeq().reverse().skip(e).reverse())},skipWhile:function(e,t){return Xt(this,Wt(this,e,t,!0))},skipUntil:function(e,t){return this.skipWhile(Rn(e),t)},sortBy:function(e,t){return Xt(this,Ht(this,t,e))},take:function(e){return this.slice(0,Math.max(0,e))},takeLast:function(e){return Xt(this,this.toSeq().reverse().take(e).reverse())},takeWhile:function(e,t){return Xt(this,function(e,t,n){var r=Qt(e);return r.__iterateUncached=function(r,i){var o=this;if(i)return this.cacheResult().__iterate(r,i);var a=0;return e.__iterate(function(e,i,s){return t.call(n,e,i,s)&&++a&&r(e,i,o)}),a},r.__iteratorUncached=function(r,i){var o=this;if(i)return this.cacheResult().__iterator(r,i);var a=e.__iterator(R,i),s=!0;return new B(function(){if(!s)return{value:void 0,done:!0};var e=a.next();if(e.done)return e;var i=e.value,u=i[0],l=i[1];return t.call(n,l,u,o)?r===R?e:L(r,u,l,e):(s=!1,{value:void 0,done:!0})})},r}(this,e,t))},takeUntil:function(e,t){return this.takeWhile(Rn(e),t)},valueSeq:function(){return this.toIndexedSeq()},hashCode:function(){return this.__hash||(this.__hash=function(e){if(e.size===1/0)return 0;var t=c(e),n=s(e),r=t?1:0;return function(e,t){return t=Ee(t,3432918353),t=Ee(t<<15|t>>>-15,461845907),t=Ee(t<<13|t>>>-13,5),t=Ee((t=(t+3864292196|0)^e)^t>>>16,2246822507),t=we((t=Ee(t^t>>>13,3266489909))^t>>>16)}(e.__iterate(n?t?function(e,t){r=31*r+Ln(Se(e),Se(t))|0}:function(e,t){r=r+Ln(Se(e),Se(t))|0}:t?function(e){r=31*r+Se(e)|0}:function(e){r=r+Se(e)|0}),r)}(this))}});var On=n.prototype;On[p]=!0,On[j]=On.values,On.__toJS=On.toArray,On.__toStringMapper=Fn,On.inspect=On.toSource=function(){return this.toString()},On.chain=On.flatMap,On.contains=On.includes,Mn(r,{flip:function(){return Xt(this,Bt(this))},mapEntries:function(e,t){var n=this,r=0;return Xt(this,this.toSeq().map(function(i,o){return e.call(t,[o,i],r++,n)}).fromEntrySeq())},mapKeys:function(e,t){var n=this;return Xt(this,this.toSeq().flip().map(function(r,i){return e.call(t,r,i,n)}).flip())}});var Tn=r.prototype;function Pn(e,t){return t}function In(e,t){return[t,e]}function Rn(e){return function(){return!e.apply(this,arguments)}}function Nn(e){return function(){return-e.apply(this,arguments)}}function Fn(e){return"string"==typeof e?JSON.stringify(e):String(e)}function jn(){return w(arguments)}function Bn(e,t){return et?-1:0}function Ln(e,t){return e^t+2654435769+(e<<6)+(e>>2)|0}return Tn[f]=!0,Tn[j]=On.entries,Tn.__toJS=On.toObject,Tn.__toStringMapper=function(e,t){return JSON.stringify(t)+": "+Fn(e)},Mn(i,{toKeyedSeq:function(){return new Rt(this,!1)},filter:function(e,t){return Xt(this,zt(this,e,t,!1))},findIndex:function(e,t){var n=this.findEntry(e,t);return n?n[0]:-1},indexOf:function(e){var t=this.keyOf(e);return void 0===t?-1:t},lastIndexOf:function(e){var t=this.lastKeyOf(e);return void 0===t?-1:t},reverse:function(){return Xt(this,qt(this,!1))},slice:function(e,t){return Xt(this,Ut(this,e,t,!1))},splice:function(e,t){var n=arguments.length;if(t=Math.max(0|t,0),0===n||2===n&&!t)return this;e=M(e,e<0?this.count():this.size);var r=this.slice(0,e);return Xt(this,1===n?r:r.concat(w(arguments,2),this.slice(e+t)))},findLastIndex:function(e,t){var n=this.findLastEntry(e,t);return n?n[0]:-1},first:function(){return this.get(0)},flatten:function(e){return Xt(this,Vt(this,e,!1))},get:function(e,t){return(e=C(this,e))<0||this.size===1/0||void 0!==this.size&&e>this.size?t:this.find(function(t,n){return n===e},void 0,t)},has:function(e){return(e=C(this,e))>=0&&(void 0!==this.size?this.size===1/0||e5e3)return e.textContent;return function(e){for(var n,r,i,o,a,s=e.textContent,u=0,l=s[0],c=1,p=e.innerHTML="",f=0;r=n,n=f<7&&"\\"==n?1:c;){if(c=l,l=s[++u],o=p.length>1,!c||f>8&&"\n"==c||[/\S/.test(c),1,1,!/[$\w]/.test(c),("/"==n||"\n"==n)&&o,'"'==n&&o,"'"==n&&o,s[u-4]+r+n=="--\x3e",r+n=="*/"][f])for(p&&(e.appendChild(a=t.createElement("span")).setAttribute("style",["color: #555; font-weight: bold;","","","color: #555;",""][f?f<3?2:f>6?4:f>3?3:+/^(a(bstract|lias|nd|rguments|rray|s(m|sert)?|uto)|b(ase|egin|ool(ean)?|reak|yte)|c(ase|atch|har|hecked|lass|lone|ompl|onst|ontinue)|de(bugger|cimal|clare|f(ault|er)?|init|l(egate|ete)?)|do|double|e(cho|ls?if|lse(if)?|nd|nsure|num|vent|x(cept|ec|p(licit|ort)|te(nds|nsion|rn)))|f(allthrough|alse|inal(ly)?|ixed|loat|or(each)?|riend|rom|unc(tion)?)|global|goto|guard|i(f|mp(lements|licit|ort)|n(it|clude(_once)?|line|out|stanceof|t(erface|ernal)?)?|s)|l(ambda|et|ock|ong)|m(icrolight|odule|utable)|NaN|n(amespace|ative|ext|ew|il|ot|ull)|o(bject|perator|r|ut|verride)|p(ackage|arams|rivate|rotected|rotocol|ublic)|r(aise|e(adonly|do|f|gister|peat|quire(_once)?|scue|strict|try|turn))|s(byte|ealed|elf|hort|igned|izeof|tatic|tring|truct|ubscript|uper|ynchronized|witch)|t(emplate|hen|his|hrows?|ransient|rue|ry|ype(alias|def|id|name|of))|u(n(checked|def(ined)?|ion|less|signed|til)|se|sing)|v(ar|irtual|oid|olatile)|w(char_t|hen|here|hile|ith)|xor|yield)$/.test(p):0]),a.appendChild(t.createTextNode(p))),i=f&&f<7?f:i,p="",f=11;![1,/[\/{}[(\-+*=<>:;|\\.,?!&@~]/.test(c),/[\])]/.test(c),/[$\w]/.test(c),"/"==c&&i<2&&"<"!=n,'"'==c,"'"==c,c+l+s[u+1]+s[u+2]=="\x3c!--",c+l=="/*",c+l=="//","#"==c][--f];);p+=c}}(e)},t.mapToList=function e(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"key";var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:l.default.Map();if(!l.default.Map.isMap(t)||!t.size)return l.default.List();Array.isArray(n)||(n=[n]);if(n.length<1)return t.merge(r);var a=l.default.List();var s=n[0];var u=!0;var c=!1;var p=void 0;try{for(var f,h=(0,o.default)(t.entries());!(u=(f=h.next()).done);u=!0){var d=f.value,m=(0,i.default)(d,2),v=m[0],g=m[1],y=e(g,n.slice(1),r.set(s,v));a=l.default.List.isList(y)?a.concat(y):a.push(y)}}catch(e){c=!0,p=e}finally{try{!u&&h.return&&h.return()}finally{if(c)throw p}}return a},t.extractFileNameFromContentDispositionHeader=function(e){var t=/filename="([^;]*);?"/i.exec(e);null===t&&(t=/filename=([^;]*);?/i.exec(e));if(null!==t&&t.length>1)return t[1];return null},t.pascalCase=S,t.pascalCaseFilename=function(e){return S(e.replace(/\.[^./]*$/,""))},t.sanitizeUrl=function(e){if("string"!=typeof e||""===e)return"";return(0,c.sanitizeUrl)(e)},t.getAcceptControllingResponse=function(e){if(!l.default.OrderedMap.isOrderedMap(e))return null;if(!e.size)return null;var t=e.find(function(e,t){return t.startsWith("2")&&(0,s.default)(e.get("content")||{}).length>0}),n=e.get("default")||l.default.OrderedMap(),r=(n.get("content")||l.default.OrderedMap()).keySeq().toJS().length?n:null;return t||r},t.deeplyStripKey=function e(t,n){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:function(){return!0};if("object"!==(void 0===t?"undefined":(0,u.default)(t))||Array.isArray(t)||!n)return t;var i=(0,a.default)({},t);(0,s.default)(i).forEach(function(t){t===n&&r(i[t],t)?delete i[t]:i[t]=e(i[t],n,r)});return i};var l=b(n(7)),c=n(492),p=b(n(914)),f=b(n(426)),h=b(n(422)),d=b(n(224)),m=b(n(932)),v=b(n(116)),g=n(171),y=b(n(34)),_=b(n(686));function b(e){return e&&e.__esModule?e:{default:e}}var x="default",k=t.isImmutable=function(e){return l.default.Iterable.isIterable(e)};function E(e){return Array.isArray(e)?e:[e]}function w(e){return!!e&&"object"===(void 0===e?"undefined":(0,u.default)(e))}t.memoize=h.default;function S(e){return(0,f.default)((0,p.default)(e))}t.propChecker=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:[];return(0,s.default)(e).length!==(0,s.default)(t).length||((0,m.default)(e,function(e,n){if(r.includes(n))return!1;var i=t[n];return l.default.Iterable.isIterable(e)?!l.default.is(e,i):("object"!==(void 0===e?"undefined":(0,u.default)(e))||"object"!==(void 0===i?"undefined":(0,u.default)(i)))&&e!==i})||n.some(function(n){return!(0,v.default)(e[n],t[n])}))};var C=t.validateMaximum=function(e,t){if(e>t)return"Value must be less than Maximum"},A=t.validateMinimum=function(e,t){if(et)return"Value must be less than MaxLength"},F=t.validateMinLength=function(e,t){if(e.length2&&void 0!==arguments[2]&&arguments[2],r=[],i=t&&"body"===e.get("in")?e.get("value_xml"):e.get("value"),o=e.get("required"),a=n?e.get("schema"):e;if(!a)return r;var s=a.get("maximum"),c=a.get("minimum"),p=a.get("type"),f=a.get("format"),h=a.get("maxLength"),d=a.get("minLength"),m=a.get("pattern");if(p&&(o||i)){var v="string"===p&&i,g="array"===p&&Array.isArray(i)&&i.length,_="array"===p&&l.default.List.isList(i)&&i.count(),b="file"===p&&i instanceof y.default.File,x="boolean"===p&&(i||!1===i),k="number"===p&&(i||0===i),E="integer"===p&&(i||0===i),w=!1;if(n&&"object"===p)if("object"===(void 0===i?"undefined":(0,u.default)(i)))w=!0;else if("string"==typeof i)try{JSON.parse(i),w=!0}catch(e){return r.push("Parameter string value must be valid JSON"),r}var S=[v,g,_,b,x,k,E,w].some(function(e){return!!e});if(o&&!S)return r.push("Required field is not provided"),r;if(m){var B=j(i,m);B&&r.push(B)}if(h||0===h){var L=N(i,h);L&&r.push(L)}if(d){var q=F(i,d);q&&r.push(q)}if(s||0===s){var z=C(i,s);z&&r.push(z)}if(c||0===c){var U=A(i,c);U&&r.push(U)}if("string"===p){var W=void 0;if(!(W="date-time"===f?I(i):"uuid"===f?R(i):P(i)))return r;r.push(W)}else if("boolean"===p){var V=T(i);if(!V)return r;r.push(V)}else if("number"===p){var H=D(i);if(!H)return r;r.push(H)}else if("integer"===p){var J=M(i);if(!J)return r;r.push(J)}else if("array"===p){var G;if(!_||!i.count())return r;G=a.getIn(["items","type"]),i.forEach(function(e,t){var n=void 0;"number"===G?n=D(e):"integer"===G?n=M(e):"string"===G&&(n=P(e)),n&&r.push({index:t,error:n})})}else if("file"===p){var K=O(i);if(!K)return r;r.push(K)}}return r},t.getSampleSchema=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if(/xml/.test(t)){if(!e.xml||!e.xml.name){if(e.xml=e.xml||{},!e.$$ref)return e.type||e.items||e.properties||e.additionalProperties?'\n\x3c!-- XML example cannot be generated --\x3e':null;var i=e.$$ref.match(/\S*\/(\S+)$/);e.xml.name=i[1]}return(0,g.memoizedCreateXMLExample)(e,n)}return(0,r.default)((0,g.memoizedSampleFromSchema)(e,n),null,2)},t.parseSearch=function(){var e={},t=y.default.location.search;if(!t)return{};if(""!=t){var n=t.substr(1).split("&");for(var r in n)n.hasOwnProperty(r)&&(r=n[r].split("="),e[decodeURIComponent(r[0])]=r[1]&&decodeURIComponent(r[1])||"")}return e},t.serializeSearch=function(e){return(0,s.default)(e).map(function(t){return encodeURIComponent(t)+"="+encodeURIComponent(e[t])}).join("&")},t.btoa=function(t){return(t instanceof e?t:new e(t.toString(),"utf-8")).toString("base64")},t.sorters={operationsSorter:{alpha:function(e,t){return e.get("path").localeCompare(t.get("path"))},method:function(e,t){return e.get("method").localeCompare(t.get("method"))}},tagsSorter:{alpha:function(e,t){return e.localeCompare(t)}}},t.buildFormData=function(e){var t=[];for(var n in e){var r=e[n];void 0!==r&&""!==r&&t.push([n,"=",encodeURIComponent(r).replace(/%20/g,"+")].join(""))}return t.join("&")},t.shallowEqualKeys=function(e,t,n){return!!(0,d.default)(n,function(n){return(0,v.default)(e[n],t[n])})};var B=t.createDeepLinkPath=function(e){return"string"==typeof e||e instanceof String?e.trim().replace(/\s/g,"_"):""};t.escapeDeepLinkPath=function(e){return(0,_.default)(B(e))},t.getExtensions=function(e){return e.filter(function(e,t){return/^x-/.test(t)})},t.getCommonExtensions=function(e){return e.filter(function(e,t){return/^pattern|maxLength|minLength|maximum|minimum/.test(t)})}}).call(t,n(50).Buffer)},function(e,t,n){"use strict";var r=n(35);e.exports=r},function(e,t,n){"use strict";e.exports=function(e){for(var t=arguments.length-1,n="Minified React error #"+e+"; visit http://facebook.github.io/react/docs/error-decoder.html?invariant="+e,r=0;r>",o={listOf:function(e){return l(e,"List",r.List.isList)},mapOf:function(e,t){return c(e,t,"Map",r.Map.isMap)},orderedMapOf:function(e,t){return c(e,t,"OrderedMap",r.OrderedMap.isOrderedMap)},setOf:function(e){return l(e,"Set",r.Set.isSet)},orderedSetOf:function(e){return l(e,"OrderedSet",r.OrderedSet.isOrderedSet)},stackOf:function(e){return l(e,"Stack",r.Stack.isStack)},iterableOf:function(e){return l(e,"Iterable",r.Iterable.isIterable)},recordOf:function(e){return s(function(t,n,i,o,s){for(var u=arguments.length,l=Array(u>5?u-5:0),c=5;c6?u-6:0),c=6;c5?l-5:0),p=5;p5?o-5:0),s=5;s key("+c[p]+")"].concat(a));if(h instanceof Error)return h}})).apply(void 0,o);var u})}function p(e){var t=void 0===arguments[1]?"Iterable":arguments[1],n=void 0===arguments[2]?r.Iterable.isIterable:arguments[2];return s(function(r,i,o,s,u){for(var l=arguments.length,c=Array(l>5?l-5:0),p=5;p?@[\]^_`{|}~-])/g;function a(e){return!(e>=55296&&e<=57343)&&(!(e>=64976&&e<=65007)&&(65535!=(65535&e)&&65534!=(65535&e)&&(!(e>=0&&e<=8)&&(11!==e&&(!(e>=14&&e<=31)&&(!(e>=127&&e<=159)&&!(e>1114111)))))))}function s(e){if(e>65535){var t=55296+((e-=65536)>>10),n=56320+(1023&e);return String.fromCharCode(t,n)}return String.fromCharCode(e)}var u=/&([a-z#][a-z0-9]{1,31});/gi,l=/^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))/i,c=n(475);function p(e,t){var n=0;return i(c,t)?c[t]:35===t.charCodeAt(0)&&l.test(t)&&a(n="x"===t[1].toLowerCase()?parseInt(t.slice(2),16):parseInt(t.slice(1),10))?s(n):e}var f=/[&<>"]/,h=/[&<>"]/g,d={"&":"&","<":"<",">":">",'"':"""};function m(e){return d[e]}t.assign=function(e){return[].slice.call(arguments,1).forEach(function(t){if(t){if("object"!=typeof t)throw new TypeError(t+"must be object");Object.keys(t).forEach(function(n){e[n]=t[n]})}}),e},t.isString=function(e){return"[object String]"===function(e){return Object.prototype.toString.call(e)}(e)},t.has=i,t.unescapeMd=function(e){return e.indexOf("\\")<0?e:e.replace(o,"$1")},t.isValidEntityCode=a,t.fromCodePoint=s,t.replaceEntities=function(e){return e.indexOf("&")<0?e:e.replace(u,p)},t.escapeHtml=function(e){return f.test(e)?e.replace(h,m):e}},function(e,t){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},function(e,t,n){var r=n(31),i=n(57),o=n(63),a=n(79),s=n(132),u=function(e,t,n){var l,c,p,f,h=e&u.F,d=e&u.G,m=e&u.S,v=e&u.P,g=e&u.B,y=d?r:m?r[t]||(r[t]={}):(r[t]||{}).prototype,_=d?i:i[t]||(i[t]={}),b=_.prototype||(_.prototype={});for(l in d&&(n=t),n)p=((c=!h&&y&&void 0!==y[l])?y:n)[l],f=g&&c?s(p,r):v&&"function"==typeof p?s(Function.call,p):p,y&&a(y,l,p,e&u.U),_[l]!=p&&o(_,l,f),v&&b[l]!=p&&(b[l]=p)};r.core=i,u.F=1,u.G=2,u.S=4,u.P=8,u.B=16,u.W=32,u.U=64,u.R=128,e.exports=u},function(e,t){var n=e.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=n)},function(e,t,n){var r=n(30),i=n(108),o=n(58),a=/"/g,s=function(e,t,n,r){var i=String(o(e)),s="<"+t;return""!==n&&(s+=" "+n+'="'+String(r).replace(a,""")+'"'),s+">"+i+""};e.exports=function(e,t){var n={};n[e]=t(s),r(r.P+r.F*i(function(){var t=""[e]('"');return t!==t.toLowerCase()||t.split('"').length>3}),"String",n)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=o(n(25));t.isOAS3=a,t.isSwagger2=function(e){var t=e.get("swagger");if(!t)return!1;return t.startsWith("2.0")},t.OAS3ComponentWrapFactory=function(e){return function(t,n){return function(o){if(n&&n.specSelectors&&n.specSelectors.specJson){var s=n.specSelectors.specJson();return a(s)?i.default.createElement(e,(0,r.default)({},o,n,{Ori:t})):i.default.createElement(t,o)}return console.warn("OAS3 wrapper: couldn't get spec"),null}}};var i=o(n(0));function o(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=e.get("openapi");return!!t&&t.startsWith("3.0.")}},function(e,t,n){"use strict";var r,i=n(97),o=(r=i)&&r.__esModule?r:{default:r};e.exports=function(){var e={location:{},history:{},open:function(){},close:function(){},File:function(){}};if("undefined"==typeof window)return e;try{e=window;var t=!0,n=!1,r=void 0;try{for(var i,a=(0,o.default)(["File","Blob","FormData"]);!(t=(i=a.next()).done);t=!0){var s=i.value;s in window&&(e[s]=window[s])}}catch(e){n=!0,r=e}finally{try{!t&&a.return&&a.return()}finally{if(n)throw r}}}catch(e){console.error(e)}return e}()},function(e,t,n){"use strict";function r(e){return function(){return e}}var i=function(){};i.thatReturns=r,i.thatReturnsFalse=r(!1),i.thatReturnsTrue=r(!0),i.thatReturnsNull=r(null),i.thatReturnsThis=function(){return this},i.thatReturnsArgument=function(e){return e},e.exports=i},function(e,t,n){e.exports={default:n(575),__esModule:!0}},function(e,t,n){var r=n(29);e.exports=function(e){if(!r(e))throw TypeError(e+" is not an object!");return e}},function(e,t,n){var r=n(405),i="object"==typeof self&&self&&self.Object===Object&&self,o=r||i||Function("return this")();e.exports=o},function(e,t){e.exports=function(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}},function(e,t){var n,r,i=e.exports={};function o(){throw new Error("setTimeout has not been defined")}function a(){throw new Error("clearTimeout has not been defined")}function s(e){if(n===setTimeout)return setTimeout(e,0);if((n===o||!n)&&setTimeout)return n=setTimeout,setTimeout(e,0);try{return n(e,0)}catch(t){try{return n.call(null,e,0)}catch(t){return n.call(this,e,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:o}catch(e){n=o}try{r="function"==typeof clearTimeout?clearTimeout:a}catch(e){r=a}}();var u,l=[],c=!1,p=-1;function f(){c&&u&&(c=!1,u.length?l=u.concat(l):p=-1,l.length&&h())}function h(){if(!c){var e=s(f);c=!0;for(var t=l.length;t;){for(u=l,l=[];++p1)for(var n=1;n0&&(o=this.buffer[s-1],e.call("\0\r\n…\u2028\u2029",o)<0);)if(s--,this.pointer-s>n/2-1){i=" ... ",s+=5;break}for(u="",r=this.pointer;rn/2-1){u=" ... ",r-=5;break}return""+new Array(t).join(" ")+i+this.buffer.slice(s,r)+u+"\n"+new Array(t+this.pointer-s+i.length).join(" ")+"^"},t.prototype.toString=function(){var e,t;return e=this.get_snippet(),t=" on line "+(this.line+1)+", column "+(this.column+1),e?t:t+":\n"+e},t}(),this.YAMLError=function(e){function n(e){this.message=e,n.__super__.constructor.call(this),this.stack=this.toString()+"\n"+(new Error).stack.split("\n").slice(1).join("\n")}return t(n,e),n.prototype.toString=function(){return this.message},n}(Error),this.MarkedYAMLError=function(e){function n(e,t,r,i,o){this.context=e,this.context_mark=t,this.problem=r,this.problem_mark=i,this.note=o,n.__super__.constructor.call(this)}return t(n,e),n.prototype.toString=function(){var e;return e=[],null!=this.context&&e.push(this.context),null==this.context_mark||null!=this.problem&&null!=this.problem_mark&&this.context_mark.line===this.problem_mark.line&&this.context_mark.column===this.problem_mark.column||e.push(this.context_mark.toString()),null!=this.problem&&e.push(this.problem),null!=this.problem_mark&&e.push(this.problem_mark.toString()),null!=this.note&&e.push(this.note),e.join("\n")},n}(this.YAMLError)}).call(this)},function(e,t,n){e.exports=!n(54)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t,n){if(n)return[e,t];return e},e.exports=t.default},function(e,t){e.exports=function(e){return null!=e&&"object"==typeof e}},function(e,t,n){"use strict";(function(e){ -/*! - * The buffer module from node.js, for the browser. - * - * @author Feross Aboukhadijeh - * @license MIT - */ -var r=n(558),i=n(743),o=n(383);function a(){return u.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function s(e,t){if(a()=a())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+a().toString(16)+" bytes");return 0|e}function d(e,t){if(u.isBuffer(e))return e.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(e)||e instanceof ArrayBuffer))return e.byteLength;"string"!=typeof e&&(e=""+e);var n=e.length;if(0===n)return 0;for(var r=!1;;)switch(t){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":case void 0:return q(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return z(e).length;default:if(r)return q(e).length;t=(""+t).toLowerCase(),r=!0}}function m(e,t,n){var r=e[t];e[t]=e[n],e[n]=r}function v(e,t,n,r,i){if(0===e.length)return-1;if("string"==typeof n?(r=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),n=+n,isNaN(n)&&(n=i?0:e.length-1),n<0&&(n=e.length+n),n>=e.length){if(i)return-1;n=e.length-1}else if(n<0){if(!i)return-1;n=0}if("string"==typeof t&&(t=u.from(t,r)),u.isBuffer(t))return 0===t.length?-1:g(e,t,n,r,i);if("number"==typeof t)return t&=255,u.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?i?Uint8Array.prototype.indexOf.call(e,t,n):Uint8Array.prototype.lastIndexOf.call(e,t,n):g(e,[t],n,r,i);throw new TypeError("val must be string, number or Buffer")}function g(e,t,n,r,i){var o,a=1,s=e.length,u=t.length;if(void 0!==r&&("ucs2"===(r=String(r).toLowerCase())||"ucs-2"===r||"utf16le"===r||"utf-16le"===r)){if(e.length<2||t.length<2)return-1;a=2,s/=2,u/=2,n/=2}function l(e,t){return 1===a?e[t]:e.readUInt16BE(t*a)}if(i){var c=-1;for(o=n;os&&(n=s-u),o=n;o>=0;o--){for(var p=!0,f=0;fi&&(r=i):r=i;var o=t.length;if(o%2!=0)throw new TypeError("Invalid hex string");r>o/2&&(r=o/2);for(var a=0;a>8,i=n%256,o.push(i),o.push(r);return o}(t,e.length-n),e,n,r)}function w(e,t,n){return 0===t&&n===e.length?r.fromByteArray(e):r.fromByteArray(e.slice(t,n))}function S(e,t,n){n=Math.min(e.length,n);for(var r=[],i=t;i239?4:l>223?3:l>191?2:1;if(i+p<=n)switch(p){case 1:l<128&&(c=l);break;case 2:128==(192&(o=e[i+1]))&&(u=(31&l)<<6|63&o)>127&&(c=u);break;case 3:o=e[i+1],a=e[i+2],128==(192&o)&&128==(192&a)&&(u=(15&l)<<12|(63&o)<<6|63&a)>2047&&(u<55296||u>57343)&&(c=u);break;case 4:o=e[i+1],a=e[i+2],s=e[i+3],128==(192&o)&&128==(192&a)&&128==(192&s)&&(u=(15&l)<<18|(63&o)<<12|(63&a)<<6|63&s)>65535&&u<1114112&&(c=u)}null===c?(c=65533,p=1):c>65535&&(c-=65536,r.push(c>>>10&1023|55296),c=56320|1023&c),r.push(c),i+=p}return function(e){var t=e.length;if(t<=C)return String.fromCharCode.apply(String,e);var n="",r=0;for(;rthis.length)return"";if((void 0===n||n>this.length)&&(n=this.length),n<=0)return"";if((n>>>=0)<=(t>>>=0))return"";for(e||(e="utf8");;)switch(e){case"hex":return M(this,t,n);case"utf8":case"utf-8":return S(this,t,n);case"ascii":return A(this,t,n);case"latin1":case"binary":return D(this,t,n);case"base64":return w(this,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return O(this,t,n);default:if(r)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),r=!0}}.apply(this,arguments)},u.prototype.equals=function(e){if(!u.isBuffer(e))throw new TypeError("Argument must be a Buffer");return this===e||0===u.compare(this,e)},u.prototype.inspect=function(){var e="",n=t.INSPECT_MAX_BYTES;return this.length>0&&(e=this.toString("hex",0,n).match(/.{2}/g).join(" "),this.length>n&&(e+=" ... ")),""},u.prototype.compare=function(e,t,n,r,i){if(!u.isBuffer(e))throw new TypeError("Argument must be a Buffer");if(void 0===t&&(t=0),void 0===n&&(n=e?e.length:0),void 0===r&&(r=0),void 0===i&&(i=this.length),t<0||n>e.length||r<0||i>this.length)throw new RangeError("out of range index");if(r>=i&&t>=n)return 0;if(r>=i)return-1;if(t>=n)return 1;if(t>>>=0,n>>>=0,r>>>=0,i>>>=0,this===e)return 0;for(var o=i-r,a=n-t,s=Math.min(o,a),l=this.slice(r,i),c=e.slice(t,n),p=0;pi)&&(n=i),e.length>0&&(n<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");for(var o=!1;;)switch(r){case"hex":return y(this,e,t,n);case"utf8":case"utf-8":return _(this,e,t,n);case"ascii":return b(this,e,t,n);case"latin1":case"binary":return x(this,e,t,n);case"base64":return k(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return E(this,e,t,n);default:if(o)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),o=!0}},u.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var C=4096;function A(e,t,n){var r="";n=Math.min(e.length,n);for(var i=t;ir)&&(n=r);for(var i="",o=t;on)throw new RangeError("Trying to access beyond buffer length")}function P(e,t,n,r,i,o){if(!u.isBuffer(e))throw new TypeError('"buffer" argument must be a Buffer instance');if(t>i||te.length)throw new RangeError("Index out of range")}function I(e,t,n,r){t<0&&(t=65535+t+1);for(var i=0,o=Math.min(e.length-n,2);i>>8*(r?i:1-i)}function R(e,t,n,r){t<0&&(t=4294967295+t+1);for(var i=0,o=Math.min(e.length-n,4);i>>8*(r?i:3-i)&255}function N(e,t,n,r,i,o){if(n+r>e.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("Index out of range")}function F(e,t,n,r,o){return o||N(e,0,n,4),i.write(e,t,n,r,23,4),n+4}function j(e,t,n,r,o){return o||N(e,0,n,8),i.write(e,t,n,r,52,8),n+8}u.prototype.slice=function(e,t){var n,r=this.length;if(e=~~e,t=void 0===t?r:~~t,e<0?(e+=r)<0&&(e=0):e>r&&(e=r),t<0?(t+=r)<0&&(t=0):t>r&&(t=r),t0&&(i*=256);)r+=this[e+--t]*i;return r},u.prototype.readUInt8=function(e,t){return t||T(e,1,this.length),this[e]},u.prototype.readUInt16LE=function(e,t){return t||T(e,2,this.length),this[e]|this[e+1]<<8},u.prototype.readUInt16BE=function(e,t){return t||T(e,2,this.length),this[e]<<8|this[e+1]},u.prototype.readUInt32LE=function(e,t){return t||T(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},u.prototype.readUInt32BE=function(e,t){return t||T(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},u.prototype.readIntLE=function(e,t,n){e|=0,t|=0,n||T(e,t,this.length);for(var r=this[e],i=1,o=0;++o=(i*=128)&&(r-=Math.pow(2,8*t)),r},u.prototype.readIntBE=function(e,t,n){e|=0,t|=0,n||T(e,t,this.length);for(var r=t,i=1,o=this[e+--r];r>0&&(i*=256);)o+=this[e+--r]*i;return o>=(i*=128)&&(o-=Math.pow(2,8*t)),o},u.prototype.readInt8=function(e,t){return t||T(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},u.prototype.readInt16LE=function(e,t){t||T(e,2,this.length);var n=this[e]|this[e+1]<<8;return 32768&n?4294901760|n:n},u.prototype.readInt16BE=function(e,t){t||T(e,2,this.length);var n=this[e+1]|this[e]<<8;return 32768&n?4294901760|n:n},u.prototype.readInt32LE=function(e,t){return t||T(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},u.prototype.readInt32BE=function(e,t){return t||T(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},u.prototype.readFloatLE=function(e,t){return t||T(e,4,this.length),i.read(this,e,!0,23,4)},u.prototype.readFloatBE=function(e,t){return t||T(e,4,this.length),i.read(this,e,!1,23,4)},u.prototype.readDoubleLE=function(e,t){return t||T(e,8,this.length),i.read(this,e,!0,52,8)},u.prototype.readDoubleBE=function(e,t){return t||T(e,8,this.length),i.read(this,e,!1,52,8)},u.prototype.writeUIntLE=function(e,t,n,r){(e=+e,t|=0,n|=0,r)||P(this,e,t,n,Math.pow(2,8*n)-1,0);var i=1,o=0;for(this[t]=255&e;++o=0&&(o*=256);)this[t+i]=e/o&255;return t+n},u.prototype.writeUInt8=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,1,255,0),u.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),this[t]=255&e,t+1},u.prototype.writeUInt16LE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,2,65535,0),u.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):I(this,e,t,!0),t+2},u.prototype.writeUInt16BE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,2,65535,0),u.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):I(this,e,t,!1),t+2},u.prototype.writeUInt32LE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,4,4294967295,0),u.TYPED_ARRAY_SUPPORT?(this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e):R(this,e,t,!0),t+4},u.prototype.writeUInt32BE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,4,4294967295,0),u.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):R(this,e,t,!1),t+4},u.prototype.writeIntLE=function(e,t,n,r){if(e=+e,t|=0,!r){var i=Math.pow(2,8*n-1);P(this,e,t,n,i-1,-i)}var o=0,a=1,s=0;for(this[t]=255&e;++o>0)-s&255;return t+n},u.prototype.writeIntBE=function(e,t,n,r){if(e=+e,t|=0,!r){var i=Math.pow(2,8*n-1);P(this,e,t,n,i-1,-i)}var o=n-1,a=1,s=0;for(this[t+o]=255&e;--o>=0&&(a*=256);)e<0&&0===s&&0!==this[t+o+1]&&(s=1),this[t+o]=(e/a>>0)-s&255;return t+n},u.prototype.writeInt8=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,1,127,-128),u.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),e<0&&(e=255+e+1),this[t]=255&e,t+1},u.prototype.writeInt16LE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,2,32767,-32768),u.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):I(this,e,t,!0),t+2},u.prototype.writeInt16BE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,2,32767,-32768),u.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):I(this,e,t,!1),t+2},u.prototype.writeInt32LE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,4,2147483647,-2147483648),u.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24):R(this,e,t,!0),t+4},u.prototype.writeInt32BE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),u.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):R(this,e,t,!1),t+4},u.prototype.writeFloatLE=function(e,t,n){return F(this,e,t,!0,n)},u.prototype.writeFloatBE=function(e,t,n){return F(this,e,t,!1,n)},u.prototype.writeDoubleLE=function(e,t,n){return j(this,e,t,!0,n)},u.prototype.writeDoubleBE=function(e,t,n){return j(this,e,t,!1,n)},u.prototype.copy=function(e,t,n,r){if(n||(n=0),r||0===r||(r=this.length),t>=e.length&&(t=e.length),t||(t=0),r>0&&r=this.length)throw new RangeError("sourceStart out of bounds");if(r<0)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length),e.length-t=0;--i)e[i+t]=this[i+n];else if(o<1e3||!u.TYPED_ARRAY_SUPPORT)for(i=0;i>>=0,n=void 0===n?this.length:n>>>0,e||(e=0),"number"==typeof e)for(o=t;o55295&&n<57344){if(!i){if(n>56319){(t-=3)>-1&&o.push(239,191,189);continue}if(a+1===r){(t-=3)>-1&&o.push(239,191,189);continue}i=n;continue}if(n<56320){(t-=3)>-1&&o.push(239,191,189),i=n;continue}n=65536+(i-55296<<10|n-56320)}else i&&(t-=3)>-1&&o.push(239,191,189);if(i=null,n<128){if((t-=1)<0)break;o.push(n)}else if(n<2048){if((t-=2)<0)break;o.push(n>>6|192,63&n|128)}else if(n<65536){if((t-=3)<0)break;o.push(n>>12|224,n>>6&63|128,63&n|128)}else{if(!(n<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;o.push(n>>18|240,n>>12&63|128,n>>6&63|128,63&n|128)}}return o}function z(e){return r.toByteArray(function(e){if((e=function(e){return e.trim?e.trim():e.replace(/^\s+|\s+$/g,"")}(e).replace(B,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function U(e,t,n,r){for(var i=0;i=t.length||i>=e.length);++i)t[i+n]=e[i];return i}}).call(t,n(19))},function(e,t,n){"use strict";var r=n(13),i=n(69),o=n(35),a=(n(10),["dispatchConfig","_targetInst","nativeEvent","isDefaultPrevented","isPropagationStopped","_dispatchListeners","_dispatchInstances"]),s={type:null,target:null,currentTarget:o.thatReturnsNull,eventPhase:null,bubbles:null,cancelable:null,timeStamp:function(e){return e.timeStamp||Date.now()},defaultPrevented:null,isTrusted:null};function u(e,t,n,r){this.dispatchConfig=e,this._targetInst=t,this.nativeEvent=n;var i=this.constructor.Interface;for(var a in i)if(i.hasOwnProperty(a)){0;var s=i[a];s?this[a]=s(n):"target"===a?this.target=r:this[a]=n[a]}var u=null!=n.defaultPrevented?n.defaultPrevented:!1===n.returnValue;return this.isDefaultPrevented=u?o.thatReturnsTrue:o.thatReturnsFalse,this.isPropagationStopped=o.thatReturnsFalse,this}r(u.prototype,{preventDefault:function(){this.defaultPrevented=!0;var e=this.nativeEvent;e&&(e.preventDefault?e.preventDefault():"unknown"!=typeof e.returnValue&&(e.returnValue=!1),this.isDefaultPrevented=o.thatReturnsTrue)},stopPropagation:function(){var e=this.nativeEvent;e&&(e.stopPropagation?e.stopPropagation():"unknown"!=typeof e.cancelBubble&&(e.cancelBubble=!0),this.isPropagationStopped=o.thatReturnsTrue)},persist:function(){this.isPersistent=o.thatReturnsTrue},isPersistent:o.thatReturnsFalse,destructor:function(){var e=this.constructor.Interface;for(var t in e)this[t]=null;for(var n=0;n1?t-1:0),r=1;r2?n-2:0),i=2;i=n?e:e.length+1===n?""+t+e:""+new Array(n-e.length+1).join(t)+e},this.to_hex=function(e){return"string"==typeof e&&(e=e.charCodeAt(0)),e.toString(16)}}).call(this)}).call(t,n(19))},function(e,t,n){var r=n(78);e.exports=function(e){if(!r(e))throw TypeError(e+" is not an object!");return e}},function(e,t,n){var r=n(135),i=n(358);e.exports=n(107)?function(e,t,n){return r.f(e,t,i(1,n))}:function(e,t,n){return e[t]=n,e}},function(e,t,n){"use strict";var r=n(708),i=Math.max;e.exports=function(e){return i(0,r(e))}},function(e,t,n){var r=n(84),i=n(869),o=n(898),a="[object Null]",s="[object Undefined]",u=r?r.toStringTag:void 0;e.exports=function(e){return null==e?void 0===e?s:a:u&&u in Object(e)?i(e):o(e)}},function(e,t,n){var r=n(830),i=n(870);e.exports=function(e,t){var n=i(e,t);return r(n)?n:void 0}},function(e,t,n){var r=n(389),i=n(833),o=n(88);e.exports=function(e){return o(e)?r(e):i(e)}},function(e,t,n){"use strict"},function(e,t,n){"use strict";var r=n(11),i=(n(8),function(e){if(this.instancePool.length){var t=this.instancePool.pop();return this.call(t,e),t}return new this(e)}),o=function(e){e instanceof this||r("25"),e.destructor(),this.instancePool.length=0||Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}},function(e,t,n){"use strict";t.__esModule=!0;var r,i=n(552),o=(r=i)&&r.__esModule?r:{default:r};t.default=function(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t`\\x00-\\x20]+|'[^']*'|\"[^\"]*\"))?)*\\s*/?>",u="]",l=new RegExp("^(?:<[A-Za-z][A-Za-z0-9-]*(?:\\s+[a-zA-Z_:][a-zA-Z0-9:._-]*(?:\\s*=\\s*(?:[^\"'=<>`\\x00-\\x20]+|'[^']*'|\"[^\"]*\"))?)*\\s*/?>|]|\x3c!----\x3e|\x3c!--(?:-?[^>-])(?:-?[^-])*--\x3e|[<][?].*?[?][>]|]*>|)","i"),c=/[\\&]/,p="[!\"#$%&'()*+,./:;<=>?@[\\\\\\]^_`{|}~-]",f=new RegExp("\\\\"+p+"|"+a,"gi"),h=new RegExp('[&<>"]',"g"),d=new RegExp(a+'|[&<>"]',"gi"),m=function(e){return 92===e.charCodeAt(0)?e.charAt(1):o(e)},v=function(e){switch(e){case"&":return"&";case"<":return"<";case">":return">";case'"':return""";default:return e}};e.exports={unescapeString:function(e){return c.test(e)?e.replace(f,m):e},normalizeURI:function(e){try{return r(i(e))}catch(t){return e}},escapeXml:function(e,t){return h.test(e)?t?e.replace(d,v):e.replace(h,v):e},reHtmlTag:l,OPENTAG:s,CLOSETAG:u,ENTITY:a,ESCAPABLE:p}},function(e,t){e.exports={}},function(e,t,n){var r=n(181),i=n(178);e.exports=function(e){return r(i(e))}},function(e,t,n){var r=n(178);e.exports=function(e){return Object(r(e))}},function(e,t){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},function(e,t,n){var r=n(31),i=n(63),o=n(134),a=n(203)("src"),s=Function.toString,u=(""+s).split("toString");n(57).inspectSource=function(e){return s.call(e)},(e.exports=function(e,t,n,s){var l="function"==typeof n;l&&(o(n,"name")||i(n,"name",t)),e[t]!==n&&(l&&(o(n,a)||i(n,a,e[t]?""+e[t]:u.join(String(t)))),e===r?e[t]=n:s?e[t]?e[t]=n:i(e,t,n):(delete e[t],i(e,t,n)))})(Function.prototype,"toString",function(){return"function"==typeof this&&this[a]||s.call(this)})},function(e,t,n){"use strict";var r=n(370)();e.exports=function(e){return e!==r&&null!==e}},function(e,t){"function"==typeof Object.create?e.exports=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}:e.exports=function(e,t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}},function(e,t,n){"use strict";function r(e){return void 0===e||null===e}e.exports.isNothing=r,e.exports.isObject=function(e){return"object"==typeof e&&null!==e},e.exports.toArray=function(e){return Array.isArray(e)?e:r(e)?[]:[e]},e.exports.repeat=function(e,t){var n,r="";for(n=0;n`\\x00-\\x20]+|'[^']*'|\"[^\"]*\"))?)*\\s*/?>",u="]",l=new RegExp("^(?:<[A-Za-z][A-Za-z0-9-]*(?:\\s+[a-zA-Z_:][a-zA-Z0-9:._-]*(?:\\s*=\\s*(?:[^\"'=<>`\\x00-\\x20]+|'[^']*'|\"[^\"]*\"))?)*\\s*/?>|]|\x3c!----\x3e|\x3c!--(?:-?[^>-])(?:-?[^-])*--\x3e|[<][?].*?[?][>]|]*>|)","i"),c=/[\\&]/,p="[!\"#$%&'()*+,./:;<=>?@[\\\\\\]^_`{|}~-]",f=new RegExp("\\\\"+p+"|"+a,"gi"),h=new RegExp('[&<>"]',"g"),d=new RegExp(a+'|[&<>"]',"gi"),m=function(e){return 92===e.charCodeAt(0)?e.charAt(1):o(e)},v=function(e){switch(e){case"&":return"&";case"<":return"<";case">":return">";case'"':return""";default:return e}};e.exports={unescapeString:function(e){return c.test(e)?e.replace(f,m):e},normalizeURI:function(e){try{return r(i(e))}catch(t){return e}},escapeXml:function(e,t){return h.test(e)?t?e.replace(d,v):e.replace(h,v):e},reHtmlTag:l,OPENTAG:s,CLOSETAG:u,ENTITY:a,ESCAPABLE:p}},function(e,t,n){"use strict";var r=n(13),i=n(461),o=n(1063),a=n(1064),s=n(95),u=n(1065),l=n(1066),c=n(1067),p=n(1071),f=s.createElement,h=s.createFactory,d=s.cloneElement,m=r,v=function(e){return e},g={Children:{map:o.map,forEach:o.forEach,count:o.count,toArray:o.toArray,only:p},Component:i.Component,PureComponent:i.PureComponent,createElement:f,cloneElement:d,isValidElement:s.isValidElement,PropTypes:u,createClass:c,createFactory:h,createMixin:v,DOM:a,version:l,__spread:m};e.exports=g},function(e,t,n){"use strict";var r=n(13),i=n(52),o=(n(10),n(465),Object.prototype.hasOwnProperty),a=n(463),s={key:!0,ref:!0,__self:!0,__source:!0};function u(e){return void 0!==e.ref}function l(e){return void 0!==e.key}var c=function(e,t,n,r,i,o,s){var u={$$typeof:a,type:e,key:t,ref:n,props:s,_owner:o};return u};c.createElement=function(e,t,n){var r,a={},p=null,f=null;if(null!=t)for(r in u(t)&&(f=t.ref),l(t)&&(p=""+t.key),void 0===t.__self?null:t.__self,void 0===t.__source?null:t.__source,t)o.call(t,r)&&!s.hasOwnProperty(r)&&(a[r]=t[r]);var h=arguments.length-2;if(1===h)a.children=n;else if(h>1){for(var d=Array(h),m=0;m1){for(var g=Array(v),y=0;y=t.length?{value:void 0,done:!0}:(e=r(t,n),this._i+=e.length,{value:e,done:!1})})},function(e,t,n){n(610);for(var r=n(20),i=n(56),o=n(75),a=n(21)("toStringTag"),s="CSSRuleList,CSSStyleDeclaration,CSSValueList,ClientRectList,DOMRectList,DOMStringList,DOMTokenList,DataTransferItemList,FileList,HTMLAllCollection,HTMLCollection,HTMLFormElement,HTMLSelectElement,MediaList,MimeTypeArray,NamedNodeMap,NodeList,PaintRequestList,Plugin,PluginArray,SVGLengthList,SVGNumberList,SVGPathSegList,SVGPointList,SVGStringList,SVGTransformList,SourceBufferList,StyleSheetList,TextTrackCueList,TextTrackList,TouchList".split(","),u=0;u0?i(r(e),9007199254740991):0}},function(e,t,n){(function(e){function n(e){return Object.prototype.toString.call(e)}t.isArray=function(e){return Array.isArray?Array.isArray(e):"[object Array]"===n(e)},t.isBoolean=function(e){return"boolean"==typeof e},t.isNull=function(e){return null===e},t.isNullOrUndefined=function(e){return null==e},t.isNumber=function(e){return"number"==typeof e},t.isString=function(e){return"string"==typeof e},t.isSymbol=function(e){return"symbol"==typeof e},t.isUndefined=function(e){return void 0===e},t.isRegExp=function(e){return"[object RegExp]"===n(e)},t.isObject=function(e){return"object"==typeof e&&null!==e},t.isDate=function(e){return"[object Date]"===n(e)},t.isError=function(e){return"[object Error]"===n(e)||e instanceof Error},t.isFunction=function(e){return"function"==typeof e},t.isPrimitive=function(e){return null===e||"boolean"==typeof e||"number"==typeof e||"string"==typeof e||"symbol"==typeof e||void 0===e},t.isBuffer=e.isBuffer}).call(t,n(50).Buffer)},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){return"string"==typeof e&&r.test(e)};var r=/-webkit-|-moz-|-ms-/;e.exports=t.default},function(e,t,n){"use strict";var r=n(80);e.exports=function(e){if(!r(e))throw new TypeError("Cannot use null or undefined");return e}},function(e,t,n){"use strict";function r(e,t){Error.call(this),this.name="YAMLException",this.reason=e,this.mark=t,this.message=(this.reason||"(unknown reason)")+(this.mark?" "+this.mark.toString():""),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=(new Error).stack||""}r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r.prototype.toString=function(e){var t=this.name+": ";return t+=this.reason||"(unknown reason)",!e&&this.mark&&(t+=" "+this.mark.toString()),t},e.exports=r},function(e,t,n){"use strict";var r=n(83);e.exports=new r({include:[n(384)],implicit:[n(794),n(787)],explicit:[n(779),n(789),n(790),n(792)]})},function(e,t){e.exports=function(e,t){return e===t||e!=e&&t!=t}},function(e,t,n){"use strict";var r=n(11),i=n(235),o=n(236),a=n(240),s=n(449),u=n(450),l=(n(8),{}),c=null,p=function(e,t){e&&(o.executeDispatchesInOrder(e,t),e.isPersistent()||e.constructor.release(e))},f=function(e){return p(e,!0)},h=function(e){return p(e,!1)},d=function(e){return"."+e._rootNodeID};var m={injection:{injectEventPluginOrder:i.injectEventPluginOrder,injectEventPluginsByName:i.injectEventPluginsByName},putListener:function(e,t,n){"function"!=typeof n&&r("94",t,typeof n);var o=d(e);(l[t]||(l[t]={}))[o]=n;var a=i.registrationNameModules[t];a&&a.didPutListener&&a.didPutListener(e,t,n)},getListener:function(e,t){var n=l[t];if(function(e,t,n){switch(e){case"onClick":case"onClickCapture":case"onDoubleClick":case"onDoubleClickCapture":case"onMouseDown":case"onMouseDownCapture":case"onMouseMove":case"onMouseMoveCapture":case"onMouseUp":case"onMouseUpCapture":return!(!n.disabled||(r=t,"button"!==r&&"input"!==r&&"select"!==r&&"textarea"!==r));default:return!1}var r}(t,e._currentElement.type,e._currentElement.props))return null;var r=d(e);return n&&n[r]},deleteListener:function(e,t){var n=i.registrationNameModules[t];n&&n.willDeleteListener&&n.willDeleteListener(e,t);var r=l[t];r&&delete r[d(e)]},deleteAllListeners:function(e){var t=d(e);for(var n in l)if(l.hasOwnProperty(n)&&l[n][t]){var r=i.registrationNameModules[n];r&&r.willDeleteListener&&r.willDeleteListener(e,n),delete l[n][t]}},extractEvents:function(e,t,n,r){for(var o,a=i.plugins,u=0;u0&&void 0!==arguments[0]?arguments[0]:{};return{type:p,payload:e}},t.clearBy=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:function(){return!0};return{type:f,payload:e}};var r,i=n(257),o=(r=i)&&r.__esModule?r:{default:r};var a=t.NEW_THROWN_ERR="err_new_thrown_err",s=t.NEW_THROWN_ERR_BATCH="err_new_thrown_err_batch",u=t.NEW_SPEC_ERR="err_new_spec_err",l=t.NEW_SPEC_ERR_BATCH="err_new_spec_err_batch",c=t.NEW_AUTH_ERR="err_new_auth_err",p=t.CLEAR="err_clear",f=t.CLEAR_BY="err_clear_by"},function(e,t,n){e.exports={default:n(582),__esModule:!0}},function(e,t,n){var r; -/*! - Copyright (c) 2016 Jed Watson. - Licensed under the MIT License (MIT), see - http://jedwatson.github.io/classnames -*/ -/*! - Copyright (c) 2016 Jed Watson. - Licensed under the MIT License (MIT), see - http://jedwatson.github.io/classnames -*/ -!function(){"use strict";var n={}.hasOwnProperty;function i(){for(var e=[],t=0;t_;_++)if((v=t?y(a(d=e[_])[0],d[1]):y(e[_]))===l||v===c)return v}else for(m=g.call(e);!(d=m.next()).done;)if((v=i(m,y,d.value,t))===l||v===c)return v}).BREAK=l,t.RETURN=c},function(e,t,n){var r=n(130)("meta"),i=n(29),o=n(55),a=n(44).f,s=0,u=Object.isExtensible||function(){return!0},l=!n(54)(function(){return u(Object.preventExtensions({}))}),c=function(e){a(e,r,{value:{i:"O"+ ++s,w:{}}})},p=e.exports={KEY:r,NEED:!1,fastKey:function(e,t){if(!i(e))return"symbol"==typeof e?e:("string"==typeof e?"S":"P")+e;if(!o(e,r)){if(!u(e))return"F";if(!t)return"E";c(e)}return e[r].i},getWeak:function(e,t){if(!o(e,r)){if(!u(e))return!0;if(!t)return!1;c(e)}return e[r].w},onFreeze:function(e){return l&&p.NEED&&u(e)&&!o(e,r)&&c(e),e}}},function(e,t){t.f={}.propertyIsEnumerable},function(e,t,n){var r=n(189),i=Math.min;e.exports=function(e){return e>0?i(r(e),9007199254740991):0}},function(e,t){var n=0,r=Math.random();e.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++n+r).toString(36))}},function(e,t){e.exports=function(e){if("function"!=typeof e)throw TypeError(e+" is not a function!");return e}},function(e,t,n){var r=n(131);e.exports=function(e,t,n){if(r(e),void 0===t)return e;switch(n){case 1:return function(n){return e.call(t,n)};case 2:return function(n,r){return e.call(t,n,r)};case 3:return function(n,r,i){return e.call(t,n,r,i)}}return function(){return e.apply(t,arguments)}}},function(e,t,n){"use strict";var r=n(63),i=n(79),o=n(108),a=n(58),s=n(18);e.exports=function(e,t,n){var u=s(e),l=n(a,u,""[e]),c=l[0],p=l[1];o(function(){var t={};return t[u]=function(){return 7},7!=""[e](t)})&&(i(String.prototype,e,c),r(RegExp.prototype,u,2==t?function(e,t){return p.call(e,this,t)}:function(e){return p.call(e,this)}))}},function(e,t){var n={}.hasOwnProperty;e.exports=function(e,t){return n.call(e,t)}},function(e,t,n){var r=n(62),i=n(630),o=n(649),a=Object.defineProperty;t.f=n(107)?Object.defineProperty:function(e,t,n){if(r(e),t=o(t,!0),r(n),i)try{return a(e,t,n)}catch(e){}if("get"in n||"set"in n)throw TypeError("Accessors not supported!");return"value"in n&&(e[t]=n.value),e}},function(e,t){var n=Math.ceil,r=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?r:n)(e)}},function(e,t,n){var r=n(632),i=n(58);e.exports=function(e){return r(i(e))}},function(e,t,n){"use strict";var r=n(371),i=n(374),o=n(712),a=n(717);(e.exports=function(e,t){var n,o,s,u,l;return arguments.length<2||"string"!=typeof e?(u=t,t=e,e=null):u=arguments[2],null==e?(n=s=!0,o=!1):(n=a.call(e,"c"),o=a.call(e,"e"),s=a.call(e,"w")),l={value:t,configurable:n,enumerable:o,writable:s},u?r(i(u),l):l}).gs=function(e,t,n){var s,u,l,c;return"string"!=typeof e?(l=n,n=t,t=e,e=null):l=arguments[3],null==t?t=void 0:o(t)?null==n?n=void 0:o(n)||(l=n,n=void 0):(l=t,t=n=void 0),null==e?(s=!0,u=!1):(s=a.call(e,"c"),u=a.call(e,"e")),c={get:t,set:n,configurable:s,enumerable:u},l?r(i(l),c):c}},function(e,t,n){var r=n(693),i=n(691);t.decode=function(e,t){return(!t||t<=0?i.XML:i.HTML)(e)},t.decodeStrict=function(e,t){return(!t||t<=0?i.XML:i.HTMLStrict)(e)},t.encode=function(e,t){return(!t||t<=0?r.XML:r.HTML)(e)},t.encodeXML=r.XML,t.encodeHTML4=t.encodeHTML5=t.encodeHTML=r.HTML,t.decodeXML=t.decodeXMLStrict=i.XML,t.decodeHTML4=t.decodeHTML5=t.decodeHTML=i.HTML,t.decodeHTML4Strict=t.decodeHTML5Strict=t.decodeHTMLStrict=i.HTMLStrict,t.escape=r.escape},function(e,t,n){"use strict";e.exports=n(709)("forEach")},function(e,t,n){"use strict";var r={};e.exports=r},function(e,t,n){"use strict";var r=n(83);e.exports=r.DEFAULT=new r({include:[n(115)],explicit:[n(785),n(784),n(783)]})},function(e,t,n){var r=n(884),i=n(885),o=n(886),a=n(887),s=n(888);function u(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t-1&&e%1==0&&e]/;e.exports=function(e){return"boolean"==typeof e||"number"==typeof e?""+e:function(e){var t,n=""+e,i=r.exec(n);if(!i)return n;var o="",a=0,s=0;for(a=i.index;a]/,u=n(242)(function(e,t){if(e.namespaceURI!==o.svg||"innerHTML"in e)e.innerHTML=t;else{(r=r||document.createElement("div")).innerHTML=""+t+"";for(var n=r.firstChild;n.firstChild;)e.appendChild(n.firstChild)}});if(i.canUseDOM){var l=document.createElement("div");l.innerHTML=" ",""===l.innerHTML&&(u=function(e,t){if(e.parentNode&&e.parentNode.replaceChild(e,e),a.test(t)||"<"===t[0]&&s.test(t)){e.innerHTML=String.fromCharCode(65279)+t;var n=e.firstChild;1===n.data.length?e.removeChild(n):n.deleteData(0,1)}else e.innerHTML=t}),l=null}e.exports=u},function(e,t,n){"use strict";t.__esModule=!0,t.default=function(e){var t={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]="number"==typeof e[n]?e[n]:e[n].val);return t},e.exports=t.default},function(e,t,n){"use strict";e.exports=function(e,t){var n,r,i,o=-1,a=e.posMax,s=e.pos,u=e.isInLabel;if(e.isInLabel)return-1;if(e.labelUnmatchedScopes)return e.labelUnmatchedScopes--,-1;for(e.pos=t+1,e.isInLabel=!0,n=1;e.pos1&&void 0!==arguments[1])||arguments[1];return e=(0,r.normalizeArray)(e),{type:s,payload:{thing:e,shown:t}}},t.changeMode=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return e=(0,r.normalizeArray)(e),{type:a,payload:{thing:e,mode:t}}};var r=n(9),i=t.UPDATE_LAYOUT="layout_update_layout",o=t.UPDATE_FILTER="layout_update_filter",a=t.UPDATE_MODE="layout_update_mode",s=t.SHOW="layout_show"},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.setSelectedServer=function(e,t){return{type:r,payload:{selectedServerUrl:e,namespace:t}}},t.setRequestBodyValue=function(e){var t=e.value,n=e.pathMethod;return{type:i,payload:{value:t,pathMethod:n}}},t.setRequestContentType=function(e){var t=e.value,n=e.pathMethod;return{type:o,payload:{value:t,pathMethod:n}}},t.setResponseContentType=function(e){var t=e.value,n=e.path,r=e.method;return{type:a,payload:{value:t,path:n,method:r}}},t.setServerVariableValue=function(e){var t=e.server,n=e.namespace,r=e.key,i=e.val;return{type:s,payload:{server:t,namespace:n,key:r,val:i}}};var r=t.UPDATE_SELECTED_SERVER="oas3_set_servers",i=t.UPDATE_REQUEST_BODY_VALUE="oas3_set_request_body_value",o=t.UPDATE_REQUEST_CONTENT_TYPE="oas3_set_request_content_type",a=t.UPDATE_RESPONSE_CONTENT_TYPE="oas3_set_response_content_type",s=t.UPDATE_SERVER_VARIABLE_VALUE="oas3_set_server_variable_value"},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.memoizedSampleFromSchema=t.memoizedCreateXMLExample=t.sampleXmlFromSchema=t.inferSchema=t.sampleFromSchema=void 0,t.createXMLExample=p;var r=n(9),i=a(n(1160)),o=a(n(944));function a(e){return e&&e.__esModule?e:{default:e}}var s={string:function(){return"string"},string_email:function(){return"user@example.com"},"string_date-time":function(){return(new Date).toISOString()},number:function(){return 0},number_float:function(){return 0},integer:function(){return 0},boolean:function(e){return"boolean"!=typeof e.default||e.default}},u=function(e){var t=e=(0,r.objectify)(e),n=t.type,i=t.format,o=s[n+"_"+i]||s[n];return(0,r.isFunc)(o)?o(e):"Unknown Type: "+e.type},l=t.sampleFromSchema=function e(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=(0,r.objectify)(t),o=i.type,a=i.example,s=i.properties,l=i.additionalProperties,c=i.items,p=n.includeReadOnly,f=n.includeWriteOnly;if(void 0!==a)return(0,r.deeplyStripKey)(a,"$$ref",function(e){return"string"==typeof e&&e.indexOf("#")>-1});if(!o)if(s)o="object";else{if(!c)return;o="array"}if("object"===o){var h=(0,r.objectify)(s),d={};for(var m in h)h[m].readOnly&&!p||h[m].writeOnly&&!f||(d[m]=e(h[m],n));if(!0===l)d.additionalProp1={};else if(l)for(var v=(0,r.objectify)(l),g=e(v,n),y=1;y<4;y++)d["additionalProp"+y]=g;return d}return"array"===o?Array.isArray(c.anyOf)?c.anyOf.map(function(t){return e(t,n)}):Array.isArray(c.oneOf)?c.oneOf.map(function(t){return e(t,n)}):[e(c,n)]:t.enum?t.default?t.default:(0,r.normalizeArray)(t.enum)[0]:"file"!==o?u(t):void 0},c=(t.inferSchema=function(e){return e.schema&&(e=e.schema),e.properties&&(e.type="object"),e},t.sampleXmlFromSchema=function e(t){var n,i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},o=(0,r.objectify)(t),a=o.type,s=o.properties,l=o.additionalProperties,c=o.items,p=o.example,f=i.includeReadOnly,h=i.includeWriteOnly,d=o.default,m={},v={},g=t.xml,y=g.name,_=g.prefix,b=g.namespace,x=o.enum,k=void 0;if(!a)if(s||l)a="object";else{if(!c)return;a="array"}(y=y||"notagname",n=(_?_+":":"")+y,b)&&(v[_?"xmlns:"+_:"xmlns"]=b);if("array"===a&&c){if(c.xml=c.xml||g||{},c.xml.name=c.xml.name||g.name,g.wrapped)return m[n]=[],Array.isArray(p)?p.forEach(function(t){c.example=t,m[n].push(e(c,i))}):Array.isArray(d)?d.forEach(function(t){c.default=t,m[n].push(e(c,i))}):m[n]=[e(c,i)],v&&m[n].push({_attr:v}),m;var E=[];return Array.isArray(p)?(p.forEach(function(t){c.example=t,E.push(e(c,i))}),E):Array.isArray(d)?(d.forEach(function(t){c.default=t,E.push(e(c,i))}),E):e(c,i)}if("object"===a){var w=(0,r.objectify)(s);for(var S in m[n]=[],p=p||{},w)if(w.hasOwnProperty(S)&&(!w[S].readOnly||f)&&(!w[S].writeOnly||h))if(w[S].xml=w[S].xml||{},w[S].xml.attribute){var C=Array.isArray(w[S].enum)&&w[S].enum[0],A=w[S].example,D=w[S].default;v[w[S].xml.name||S]=void 0!==A&&A||void 0!==p[S]&&p[S]||void 0!==D&&D||C||u(w[S])}else{w[S].xml.name=w[S].xml.name||S,void 0===w[S].example&&void 0!==p[S]&&(w[S].example=p[S]);var M=e(w[S]);Array.isArray(M)?m[n]=m[n].concat(M):m[n].push(M)}return!0===l?m[n].push({additionalProp:"Anything can be here"}):l&&m[n].push({additionalProp:u(l)}),v&&m[n].push({_attr:v}),m}return k=void 0!==p?p:void 0!==d?d:Array.isArray(x)?x[0]:u(t),m[n]=v?[{_attr:v},k]:k,m});function p(e,t){var n=c(e,t);if(n)return(0,i.default)(n,{declaration:!0,indent:"\t"})}t.memoizedCreateXMLExample=(0,o.default)(p),t.memoizedSampleFromSchema=(0,o.default)(l)},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.execute=t.executeRequest=t.logRequest=t.setMutatedRequest=t.setRequest=t.setResponse=t.validateParams=t.invalidateResolvedSubtreeCache=t.updateResolvedSubtree=t.requestResolvedSubtree=t.resolveSpec=t.parseToJson=t.SET_SCHEME=t.UPDATE_RESOLVED_SUBTREE=t.UPDATE_RESOLVED=t.UPDATE_OPERATION_META_VALUE=t.CLEAR_VALIDATE_PARAMS=t.CLEAR_REQUEST=t.CLEAR_RESPONSE=t.LOG_REQUEST=t.SET_MUTATED_REQUEST=t.SET_REQUEST=t.SET_RESPONSE=t.VALIDATE_PARAMS=t.UPDATE_PARAM=t.UPDATE_JSON=t.UPDATE_URL=t.UPDATE_SPEC=void 0;var r=_(n(25)),i=_(n(72)),o=_(n(23)),a=_(n(42)),s=_(n(124)),u=_(n(331)),l=_(n(330)),c=_(n(43));t.updateSpec=function(e){var t=F(e).replace(/\t/g," ");if("string"==typeof e)return{type:b,payload:t}},t.updateResolved=function(e){return{type:I,payload:e}},t.updateUrl=function(e){return{type:x,payload:e}},t.updateJsonSpec=function(e){return{type:k,payload:e}},t.changeParam=function(e,t,n,r,i){return{type:E,payload:{path:e,value:r,paramName:t,paramIn:n,isXml:i}}},t.clearValidateParams=function(e){return{type:T,payload:{pathMethod:e}}},t.changeConsumesValue=function(e,t){return{type:P,payload:{path:e,value:t,key:"consumes_value"}}},t.changeProducesValue=function(e,t){return{type:P,payload:{path:e,value:t,key:"produces_value"}}},t.clearResponse=function(e,t){return{type:M,payload:{path:e,method:t}}},t.clearRequest=function(e,t){return{type:O,payload:{path:e,method:t}}},t.setScheme=function(e,t,n){return{type:N,payload:{scheme:e,path:t,method:n}}};var p=_(n(212)),f=n(7),h=_(n(258)),d=_(n(257)),m=_(n(419)),v=_(n(918)),g=_(n(931)),y=n(9);function _(e){return e&&e.__esModule?e:{default:e}}var b=t.UPDATE_SPEC="spec_update_spec",x=t.UPDATE_URL="spec_update_url",k=t.UPDATE_JSON="spec_update_json",E=t.UPDATE_PARAM="spec_update_param",w=t.VALIDATE_PARAMS="spec_validate_param",S=t.SET_RESPONSE="spec_set_response",C=t.SET_REQUEST="spec_set_request",A=t.SET_MUTATED_REQUEST="spec_set_mutated_request",D=t.LOG_REQUEST="spec_log_request",M=t.CLEAR_RESPONSE="spec_clear_response",O=t.CLEAR_REQUEST="spec_clear_request",T=t.CLEAR_VALIDATE_PARAMS="spec_clear_validate_param",P=t.UPDATE_OPERATION_META_VALUE="spec_update_operation_meta_value",I=t.UPDATE_RESOLVED="spec_update_resolved",R=t.UPDATE_RESOLVED_SUBTREE="spec_update_resolved_subtree",N=t.SET_SCHEME="set_scheme",F=function(e){return(0,m.default)(e)?e:""};t.parseToJson=function(e){return function(t){var n=t.specActions,r=t.specSelectors,i=t.errActions,o=r.specStr,a=null;try{e=e||o(),i.clear({source:"parser"}),a=p.default.safeLoad(e)}catch(e){return console.error(e),i.newSpecErr({source:"parser",level:"error",message:e.reason,line:e.mark&&e.mark.line?e.mark.line+1:void 0})}return a&&"object"===(void 0===a?"undefined":(0,c.default)(a))?n.updateJsonSpec(a):{}}};var j=!1,B=(t.resolveSpec=function(e,t){return function(r){var i=r.specActions,o=r.specSelectors,a=r.errActions,s=r.fn,u=s.fetch,l=s.resolve,c=s.AST,p=r.getConfigs;j||(console.warn("specActions.resolveSpec is deprecated since v3.10.0 and will be removed in v4.0.0; use requestResolvedSubtree instead!"),j=!0);var f=p(),h=f.modelPropertyMacro,d=f.parameterMacro,m=f.requestInterceptor,v=f.responseInterceptor;void 0===e&&(e=o.specJson()),void 0===t&&(t=o.url());var g=c.getLineNumberForPath,y=o.specStr();return n(71).start("resolve"),l({fetch:u,spec:e,baseDoc:t,modelPropertyMacro:h,parameterMacro:d,requestInterceptor:m,responseInterceptor:v}).then(function(e){var t=e.spec,r=e.errors;if(a.clear({type:"thrown"}),Array.isArray(r)&&r.length>0){var o=r.map(function(e){return console.error(e),e.line=e.fullPath?g(y,e.fullPath):null,e.path=e.fullPath?e.fullPath.join("."):null,e.level="error",e.type="thrown",e.source="resolver",Object.defineProperty(e,"message",{enumerable:!0,value:e.message}),e});a.newThrownErrBatch(o)}return n(71).stop("resolve"),i.updateResolved(t)})}},[]),L=(0,v.default)((0,l.default)(u.default.mark(function e(){var t,n,r,i,o,a,c,p,h,d,m,v,y,_,b;return u.default.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:if(t=B.system){e.next=4;break}return console.error("debResolveSubtrees: don't have a system to operate on, aborting."),e.abrupt("return");case 4:if(n=t.errActions,r=t.errSelectors,i=t.fn,o=i.resolveSubtree,a=i.AST.getLineNumberForPath,c=t.specSelectors,p=t.specActions,o){e.next=8;break}return console.error("Error: Swagger-Client did not provide a `resolveSubtree` method, doing nothing."),e.abrupt("return");case 8:return h=c.specStr(),d=t.getConfigs(),m=d.modelPropertyMacro,v=d.parameterMacro,y=d.requestInterceptor,_=d.responseInterceptor,e.prev=10,e.next=13,B.reduce(function(){var e=(0,l.default)(u.default.mark(function e(t,i){var s,l,p,f,d,b,x;return u.default.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,t;case 2:return s=e.sent,l=s.resultMap,p=s.specWithCurrentSubtrees,e.next=7,o(p,i,{baseDoc:c.url(),modelPropertyMacro:m,parameterMacro:v,requestInterceptor:y,responseInterceptor:_});case 7:return f=e.sent,d=f.errors,b=f.spec,r.allErrors().size&&n.clear({type:"thrown"}),Array.isArray(d)&&d.length>0&&(x=d.map(function(e){return e.line=e.fullPath?a(h,e.fullPath):null,e.path=e.fullPath?e.fullPath.join("."):null,e.level="error",e.type="thrown",e.source="resolver",Object.defineProperty(e,"message",{enumerable:!0,value:e.message}),e}),n.newThrownErrBatch(x)),(0,g.default)(l,i,b),(0,g.default)(p,i,b),e.abrupt("return",{resultMap:l,specWithCurrentSubtrees:p});case 15:case"end":return e.stop()}},e,void 0)}));return function(t,n){return e.apply(this,arguments)}}(),s.default.resolve({resultMap:(c.specResolvedSubtree([])||(0,f.Map)()).toJS(),specWithCurrentSubtrees:c.specJson().toJS()}));case 13:b=e.sent,delete B.system,B=[],e.next=21;break;case 18:e.prev=18,e.t0=e.catch(10),console.error(e.t0);case 21:p.updateResolvedSubtree([],b.resultMap);case 22:case"end":return e.stop()}},e,void 0,[[10,18]])})),35);t.requestResolvedSubtree=function(e){return function(t){B.push(e),B.system=t,L()}};t.updateResolvedSubtree=function(e,t){return{type:R,payload:{path:e,value:t}}},t.invalidateResolvedSubtreeCache=function(){return{type:R,payload:{path:[],value:(0,f.Map)()}}},t.validateParams=function(e,t){return{type:w,payload:{pathMethod:e,isOAS3:t}}};t.setResponse=function(e,t,n){return{payload:{path:e,method:t,res:n},type:S}},t.setRequest=function(e,t,n){return{payload:{path:e,method:t,req:n},type:C}},t.setMutatedRequest=function(e,t,n){return{payload:{path:e,method:t,req:n},type:A}},t.logRequest=function(e){return{payload:e,type:D}},t.executeRequest=function(e){return function(t){var n=t.fn,r=t.specActions,i=t.specSelectors,s=t.getConfigs,u=t.oas3Selectors,l=e.pathName,c=e.method,p=e.operation,f=s(),m=f.requestInterceptor,v=f.responseInterceptor,g=p.toJS();if(e.contextUrl=(0,h.default)(i.url()).toString(),g&&g.operationId?e.operationId=g.operationId:g&&l&&c&&(e.operationId=n.opId(g,l,c)),i.isOAS3()){var _=l+":"+c;e.server=u.selectedServer(_)||u.selectedServer();var b=u.serverVariables({server:e.server,namespace:_}).toJS(),x=u.serverVariables({server:e.server}).toJS();e.serverVariables=(0,a.default)(b).length?b:x,e.requestContentType=u.requestContentType(l,c),e.responseContentType=u.responseContentType(l,c)||"*/*";var k=u.requestBodyValue(l,c);(0,y.isJSONObject)(k)?e.requestBody=JSON.parse(k):k&&k.toJS?e.requestBody=k.toJS():e.requestBody=k}var E=(0,o.default)({},e);E=n.buildRequest(E),r.setRequest(e.pathName,e.method,E);e.requestInterceptor=function(t){var n=m.apply(this,[t]),i=(0,o.default)({},n);return r.setMutatedRequest(e.pathName,e.method,i),n},e.responseInterceptor=v;var w=Date.now();return n.execute(e).then(function(t){t.duration=Date.now()-w,r.setResponse(e.pathName,e.method,t)}).catch(function(t){return r.setResponse(e.pathName,e.method,{error:!0,err:(0,d.default)(t)})})}};t.execute=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.path,n=e.method,o=(0,i.default)(e,["path","method"]);return function(e){var i=e.fn.fetch,a=e.specSelectors,s=e.specActions,u=a.specJsonWithResolvedSubtrees().toJS(),l=a.operationScheme(t,n),c=a.contentTypeValues([t,n]).toJS(),p=c.requestContentType,f=c.responseContentType,h=/xml/i.test(p),d=a.parameterValues([t,n],h).toJS();return s.executeRequest((0,r.default)({},o,{fetch:i,spec:u,pathName:t,method:n,parameters:d,requestContentType:p,scheme:l,responseContentType:f}))}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.validateBeforeExecute=t.canExecuteScheme=t.operationScheme=t.hasHost=t.parameterWithMeta=t.operationWithMeta=t.allowTryItOutFor=t.mutatedRequestFor=t.requestFor=t.responseFor=t.mutatedRequests=t.requests=t.responses=t.taggedOperations=t.operationsWithTags=t.tagDetails=t.tags=t.operationsWithRootInherited=t.schemes=t.host=t.basePath=t.definitions=t.findDefinition=t.securityDefinitions=t.security=t.produces=t.consumes=t.operations=t.paths=t.semver=t.version=t.externalDocs=t.info=t.isOAS3=t.spec=t.specJsonWithResolvedSubtrees=t.specResolvedSubtree=t.specResolved=t.specJson=t.specSource=t.specStr=t.url=t.lastError=void 0;var r,i=n(73),o=(r=i)&&r.__esModule?r:{default:r};t.getParameter=function(e,t,n,r){return t=t||[],e.getIn(["meta","paths"].concat((0,o.default)(t),["parameters"]),(0,u.fromJS)([])).find(function(e){return u.Map.isMap(e)&&e.get("name")===n&&e.get("in")===r})||(0,u.Map)()},t.parameterValues=function(e,t,n){return t=t||[],D.apply(void 0,[e].concat((0,o.default)(t))).get("parameters",(0,u.List)()).reduce(function(e,t){var r=n&&"body"===t.get("in")?t.get("value_xml"):t.get("value");return e.set(t.get("in")+"."+t.get("name"),r)},(0,u.fromJS)({}))},t.parametersIncludeIn=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";if(u.List.isList(e))return e.some(function(e){return u.Map.isMap(e)&&e.get("in")===t})},t.parametersIncludeType=M,t.contentTypeValues=function(e,t){t=t||[];var n=h(e).getIn(["paths"].concat((0,o.default)(t)),(0,u.fromJS)({})),r=e.getIn(["meta","paths"].concat((0,o.default)(t)),(0,u.fromJS)({})),i=O(e,t),a=n.get("parameters")||new u.List,s=r.get("consumes_value")?r.get("consumes_value"):M(a,"file")?"multipart/form-data":M(a,"formData")?"application/x-www-form-urlencoded":void 0;return(0,u.fromJS)({requestContentType:s,responseContentType:i})},t.operationConsumes=function(e,t){return t=t||[],h(e).getIn(["paths"].concat((0,o.default)(t),["consumes"]),(0,u.fromJS)({}))},t.currentProducesFor=O;var a=n(60),s=n(9),u=n(7);var l=["get","put","post","delete","options","head","patch","trace"],c=function(e){return e||(0,u.Map)()},p=(t.lastError=(0,a.createSelector)(c,function(e){return e.get("lastError")}),t.url=(0,a.createSelector)(c,function(e){return e.get("url")}),t.specStr=(0,a.createSelector)(c,function(e){return e.get("spec")||""}),t.specSource=(0,a.createSelector)(c,function(e){return e.get("specSource")||"not-editor"}),t.specJson=(0,a.createSelector)(c,function(e){return e.get("json",(0,u.Map)())})),f=(t.specResolved=(0,a.createSelector)(c,function(e){return e.get("resolved",(0,u.Map)())}),t.specResolvedSubtree=function(e,t){return e.getIn(["resolvedSubtrees"].concat((0,o.default)(t)),void 0)},function e(t,n){return u.Map.isMap(t)&&u.Map.isMap(n)?n.get("$$ref")?n:(0,u.OrderedMap)().mergeWith(e,t,n):n}),h=t.specJsonWithResolvedSubtrees=(0,a.createSelector)(c,function(e){return(0,u.OrderedMap)().mergeWith(f,e.get("json"),e.get("resolvedSubtrees"))}),d=t.spec=function(e){return p(e)},m=(t.isOAS3=(0,a.createSelector)(d,function(){return!1}),t.info=(0,a.createSelector)(d,function(e){return P(e&&e.get("info"))})),v=(t.externalDocs=(0,a.createSelector)(d,function(e){return P(e&&e.get("externalDocs"))}),t.version=(0,a.createSelector)(m,function(e){return e&&e.get("version")})),g=(t.semver=(0,a.createSelector)(v,function(e){return/v?([0-9]*)\.([0-9]*)\.([0-9]*)/i.exec(e).slice(1)}),t.paths=(0,a.createSelector)(h,function(e){return e.get("paths")})),y=t.operations=(0,a.createSelector)(g,function(e){if(!e||e.size<1)return(0,u.List)();var t=(0,u.List)();return e&&e.forEach?(e.forEach(function(e,n){if(!e||!e.forEach)return{};e.forEach(function(e,r){l.indexOf(r)<0||(t=t.push((0,u.fromJS)({path:n,method:r,operation:e,id:r+"-"+n})))})}),t):(0,u.List)()}),_=t.consumes=(0,a.createSelector)(d,function(e){return(0,u.Set)(e.get("consumes"))}),b=t.produces=(0,a.createSelector)(d,function(e){return(0,u.Set)(e.get("produces"))}),x=(t.security=(0,a.createSelector)(d,function(e){return e.get("security",(0,u.List)())}),t.securityDefinitions=(0,a.createSelector)(d,function(e){return e.get("securityDefinitions")}),t.findDefinition=function(e,t){var n=e.getIn(["resolvedSubtrees","definitions",t],null),r=e.getIn(["json","definitions",t],null);return n||r||null},t.definitions=(0,a.createSelector)(d,function(e){return e.get("definitions")||(0,u.Map)()}),t.basePath=(0,a.createSelector)(d,function(e){return e.get("basePath")}),t.host=(0,a.createSelector)(d,function(e){return e.get("host")}),t.schemes=(0,a.createSelector)(d,function(e){return e.get("schemes",(0,u.Map)())}),t.operationsWithRootInherited=(0,a.createSelector)(y,_,b,function(e,t,n){return e.map(function(e){return e.update("operation",function(e){if(e){if(!u.Map.isMap(e))return;return e.withMutations(function(e){return e.get("consumes")||e.update("consumes",function(e){return(0,u.Set)(e).merge(t)}),e.get("produces")||e.update("produces",function(e){return(0,u.Set)(e).merge(n)}),e})}return(0,u.Map)()})})})),k=t.tags=(0,a.createSelector)(d,function(e){return e.get("tags",(0,u.List)())}),E=t.tagDetails=function(e,t){return(k(e)||(0,u.List)()).filter(u.Map.isMap).find(function(e){return e.get("name")===t},(0,u.Map)())},w=t.operationsWithTags=(0,a.createSelector)(x,k,function(e,t){return e.reduce(function(e,t){var n=(0,u.Set)(t.getIn(["operation","tags"]));return n.count()<1?e.update("default",(0,u.List)(),function(e){return e.push(t)}):n.reduce(function(e,n){return e.update(n,(0,u.List)(),function(e){return e.push(t)})},e)},t.reduce(function(e,t){return e.set(t.get("name"),(0,u.List)())},(0,u.OrderedMap)()))}),S=(t.taggedOperations=function(e){return function(t){var n=(0,t.getConfigs)(),r=n.tagsSorter,i=n.operationsSorter;return w(e).sortBy(function(e,t){return t},function(e,t){var n="function"==typeof r?r:s.sorters.tagsSorter[r];return n?n(e,t):null}).map(function(t,n){var r="function"==typeof i?i:s.sorters.operationsSorter[i],o=r?t.sort(r):t;return(0,u.Map)({tagDetails:E(e,n),operations:o})})}},t.responses=(0,a.createSelector)(c,function(e){return e.get("responses",(0,u.Map)())})),C=t.requests=(0,a.createSelector)(c,function(e){return e.get("requests",(0,u.Map)())}),A=t.mutatedRequests=(0,a.createSelector)(c,function(e){return e.get("mutatedRequests",(0,u.Map)())}),D=(t.responseFor=function(e,t,n){return S(e).getIn([t,n],null)},t.requestFor=function(e,t,n){return C(e).getIn([t,n],null)},t.mutatedRequestFor=function(e,t,n){return A(e).getIn([t,n],null)},t.allowTryItOutFor=function(){return!0},t.operationWithMeta=function(e,t,n){var r=h(e).getIn(["paths",t,n],(0,u.Map)()),i=e.getIn(["meta","paths",t,n],(0,u.Map)()),o=r.get("parameters",(0,u.List)()).map(function(e){return(0,u.Map)().merge(e,i.getIn(["parameters",e.get("name")+"."+e.get("in")]))});return(0,u.Map)().merge(r,i).set("parameters",o)});t.parameterWithMeta=function(e,t,n,r){var i=h(e).getIn(["paths"].concat((0,o.default)(t),["parameters"]),(0,u.Map)()),a=e.getIn(["meta","paths"].concat((0,o.default)(t),["parameters"]),(0,u.Map)());return i.map(function(e){return(0,u.Map)().merge(e,a.get(e.get("name")+"."+e.get("in")))}).find(function(e){return e.get("in")===r&&e.get("name")===n},(0,u.Map)())};t.hasHost=(0,a.createSelector)(d,function(e){var t=e.get("host");return"string"==typeof t&&t.length>0&&"/"!==t[0]});function M(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";if(u.List.isList(e))return e.some(function(e){return u.Map.isMap(e)&&e.get("type")===t})}function O(e,t){t=t||[];var n=h(e).getIn(["paths"].concat((0,o.default)(t)),null);if(null!==n){var r=e.getIn(["meta","paths"].concat((0,o.default)(t),["produces_value"]),null),i=n.getIn(["produces",0],null);return r||i||"application/json"}}var T=t.operationScheme=function(e,t,n){var r=e.get("url").match(/^([a-z][a-z0-9+\-.]*):/),i=Array.isArray(r)?r[1]:null;return e.getIn(["scheme",t,n])||e.getIn(["scheme","_defaultScheme"])||i||""};t.canExecuteScheme=function(e,t,n){return["http","https"].indexOf(T(e,t,n))>-1},t.validateBeforeExecute=function(e,t){t=t||[];var n=!0;return e.getIn(["meta","paths"].concat((0,o.default)(t),["parameters"]),(0,u.fromJS)([])).forEach(function(e){var t=e.get("errors");t&&t.count()&&(n=!1)}),n};function P(e){return u.Map.isMap(e)?e:new u.Map}},function(e,t,n){"use strict";function r(e){switch(e._type){case"document":case"block_quote":case"list":case"item":case"paragraph":case"heading":case"emph":case"strong":case"link":case"image":case"custom_inline":case"custom_block":return!0;default:return!1}}var i=function(e,t){this.current=e,this.entering=!0===t},o=function(){var e=this.current,t=this.entering;if(null===e)return null;var n=r(e);return t&&n?e._firstChild?(this.current=e._firstChild,this.entering=!0):this.entering=!1:e===this.root?this.current=null:null===e._next?(this.current=e._parent,this.entering=!1):(this.current=e._next,this.entering=!0),{entering:t,node:e}},a=function(e,t){this._type=e,this._parent=null,this._firstChild=null,this._lastChild=null,this._prev=null,this._next=null,this._sourcepos=t,this._lastLineBlank=!1,this._open=!0,this._string_content=null,this._literal=null,this._listData={},this._info=null,this._destination=null,this._title=null,this._isFenced=!1,this._fenceChar=null,this._fenceLength=0,this._fenceOffset=null,this._level=null,this._onEnter=null,this._onExit=null},s=a.prototype;Object.defineProperty(s,"isContainer",{get:function(){return r(this)}}),Object.defineProperty(s,"type",{get:function(){return this._type}}),Object.defineProperty(s,"firstChild",{get:function(){return this._firstChild}}),Object.defineProperty(s,"lastChild",{get:function(){return this._lastChild}}),Object.defineProperty(s,"next",{get:function(){return this._next}}),Object.defineProperty(s,"prev",{get:function(){return this._prev}}),Object.defineProperty(s,"parent",{get:function(){return this._parent}}),Object.defineProperty(s,"sourcepos",{get:function(){return this._sourcepos}}),Object.defineProperty(s,"literal",{get:function(){return this._literal},set:function(e){this._literal=e}}),Object.defineProperty(s,"destination",{get:function(){return this._destination},set:function(e){this._destination=e}}),Object.defineProperty(s,"title",{get:function(){return this._title},set:function(e){this._title=e}}),Object.defineProperty(s,"info",{get:function(){return this._info},set:function(e){this._info=e}}),Object.defineProperty(s,"level",{get:function(){return this._level},set:function(e){this._level=e}}),Object.defineProperty(s,"listType",{get:function(){return this._listData.type},set:function(e){this._listData.type=e}}),Object.defineProperty(s,"listTight",{get:function(){return this._listData.tight},set:function(e){this._listData.tight=e}}),Object.defineProperty(s,"listStart",{get:function(){return this._listData.start},set:function(e){this._listData.start=e}}),Object.defineProperty(s,"listDelimiter",{get:function(){return this._listData.delimiter},set:function(e){this._listData.delimiter=e}}),Object.defineProperty(s,"onEnter",{get:function(){return this._onEnter},set:function(e){this._onEnter=e}}),Object.defineProperty(s,"onExit",{get:function(){return this._onExit},set:function(e){this._onExit=e}}),a.prototype.appendChild=function(e){e.unlink(),e._parent=this,this._lastChild?(this._lastChild._next=e,e._prev=this._lastChild,this._lastChild=e):(this._firstChild=e,this._lastChild=e)},a.prototype.prependChild=function(e){e.unlink(),e._parent=this,this._firstChild?(this._firstChild._prev=e,e._next=this._firstChild,this._firstChild=e):(this._firstChild=e,this._lastChild=e)},a.prototype.unlink=function(){this._prev?this._prev._next=this._next:this._parent&&(this._parent._firstChild=this._next),this._next?this._next._prev=this._prev:this._parent&&(this._parent._lastChild=this._prev),this._parent=null,this._next=null,this._prev=null},a.prototype.insertAfter=function(e){e.unlink(),e._next=this._next,e._next&&(e._next._prev=e),e._prev=this,this._next=e,e._parent=this._parent,e._next||(e._parent._lastChild=e)},a.prototype.insertBefore=function(e){e.unlink(),e._prev=this._prev,e._prev&&(e._prev._next=e),e._next=this,this._prev=e,e._parent=this._parent,e._prev||(e._parent._firstChild=e)},a.prototype.walker=function(){return new function(e){return{current:e,root:e,entering:!0,next:o,resumeAt:i}}(this)},e.exports=a},function(e,t){e.exports=function(e,t,n,r){if(!(e instanceof t)||void 0!==r&&r in e)throw TypeError(n+": incorrect invocation!");return e}},function(e,t,n){var r=n(53),i=n(181),o=n(77),a=n(129),s=n(589);e.exports=function(e,t){var n=1==e,u=2==e,l=3==e,c=4==e,p=6==e,f=5==e||p,h=t||s;return function(t,s,d){for(var m,v,g=o(t),y=i(g),_=r(s,d,3),b=a(y.length),x=0,k=n?h(t,b):u?h(t,0):void 0;b>x;x++)if((f||x in y)&&(v=_(m=y[x],x,g),e))if(n)k[x]=v;else if(v)switch(e){case 3:return!0;case 5:return m;case 6:return x;case 2:k.push(m)}else if(c)return!1;return p?-1:l||c?c:k}}},function(e,t,n){var r=n(99),i=n(21)("toStringTag"),o="Arguments"==r(function(){return arguments}());e.exports=function(e){var t,n,a;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=function(e,t){try{return e[t]}catch(e){}}(t=Object(e),i))?n:o?r(t):"Object"==(a=r(t))&&"function"==typeof t.callee?"Arguments":a}},function(e,t){e.exports=function(e){if(void 0==e)throw TypeError("Can't call method on "+e);return e}},function(e,t,n){var r=n(29),i=n(20).document,o=r(i)&&r(i.createElement);e.exports=function(e){return o?i.createElement(e):{}}},function(e,t){e.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(e,t,n){var r=n(99);e.exports=Object("z").propertyIsEnumerable(0)?Object:function(e){return"String"==r(e)?e.split(""):Object(e)}},function(e,t,n){"use strict";var r=n(98);e.exports.f=function(e){return new function(e){var t,n;this.promise=new e(function(e,r){if(void 0!==t||void 0!==n)throw TypeError("Bad Promise constructor");t=e,n=r}),this.resolve=r(t),this.reject=r(n)}(e)}},function(e,t,n){var r=n(37),i=n(598),o=n(180),a=n(187)("IE_PROTO"),s=function(){},u=function(){var e,t=n(179)("iframe"),r=o.length;for(t.style.display="none",n(333).appendChild(t),t.src="javascript:",(e=t.contentWindow.document).open(),e.write(" + + + + +
+ + diff --git a/magpie/ui/swagger-ui/magpie-rest-api-0.1.0.yaml b/magpie/ui/swagger/versions/magpie-rest-api-0.1.0.yaml similarity index 100% rename from magpie/ui/swagger-ui/magpie-rest-api-0.1.0.yaml rename to magpie/ui/swagger/versions/magpie-rest-api-0.1.0.yaml diff --git a/magpie/ui/swagger-ui/magpie-rest-api-0.1.1.yaml b/magpie/ui/swagger/versions/magpie-rest-api-0.1.1.yaml similarity index 100% rename from magpie/ui/swagger-ui/magpie-rest-api-0.1.1.yaml rename to magpie/ui/swagger/versions/magpie-rest-api-0.1.1.yaml diff --git a/magpie/ui/swagger-ui/magpie-rest-api-0.2.0.yaml b/magpie/ui/swagger/versions/magpie-rest-api-0.2.0.yaml similarity index 100% rename from magpie/ui/swagger-ui/magpie-rest-api-0.2.0.yaml rename to magpie/ui/swagger/versions/magpie-rest-api-0.2.0.yaml diff --git a/magpie/ui/swagger-ui/magpie-rest-api-0.2.x.yaml b/magpie/ui/swagger/versions/magpie-rest-api-0.2.x.yaml similarity index 100% rename from magpie/ui/swagger-ui/magpie-rest-api-0.2.x.yaml rename to magpie/ui/swagger/versions/magpie-rest-api-0.2.x.yaml diff --git a/magpie/ui/swagger-ui/magpie-rest-api-0.3.x.yaml b/magpie/ui/swagger/versions/magpie-rest-api-0.3.x.yaml similarity index 100% rename from magpie/ui/swagger-ui/magpie-rest-api-0.3.x.yaml rename to magpie/ui/swagger/versions/magpie-rest-api-0.3.x.yaml diff --git a/magpie/ui/swagger-ui/magpie-rest-api-0.4.x.yaml b/magpie/ui/swagger/versions/magpie-rest-api-0.4.x.yaml similarity index 100% rename from magpie/ui/swagger-ui/magpie-rest-api-0.4.x.yaml rename to magpie/ui/swagger/versions/magpie-rest-api-0.4.x.yaml diff --git a/magpie/ui/swagger-ui/magpie-rest-api-0.5.x.yaml b/magpie/ui/swagger/versions/magpie-rest-api-0.5.x.yaml similarity index 100% rename from magpie/ui/swagger-ui/magpie-rest-api-0.5.x.yaml rename to magpie/ui/swagger/versions/magpie-rest-api-0.5.x.yaml diff --git a/magpie/ui/swagger-ui/magpie-rest-api.yaml b/magpie/ui/swagger/versions/magpie-rest-api-0.6.x.yaml similarity index 100% rename from magpie/ui/swagger-ui/magpie-rest-api.yaml rename to magpie/ui/swagger/versions/magpie-rest-api-0.6.x.yaml diff --git a/magpie/ui/swagger/views.py b/magpie/ui/swagger/views.py new file mode 100644 index 000000000..86ce8d6f5 --- /dev/null +++ b/magpie/ui/swagger/views.py @@ -0,0 +1,16 @@ +from magpie.api.api_rest_schemas import * +from magpie import MAGPIE_MODULE_DIR +import os + + +@SwaggerAPI.get(tags=[APITag]) +@view_config(route_name=SwaggerAPI.name, renderer='templates/swagger_ui.mako', permission=NO_PERMISSION_REQUIRED) +def api_swagger(request): + """ + Swagger UI route to display the Magpie REST API schemas. + """ + swagger_versions_dir = '{}'.format(os.path.abspath(os.path.join(MAGPIE_MODULE_DIR, 'ui/swagger/versions'))) + return_data = {'api_title': TitleAPI, + 'api_schema_path': SwaggerGenerator.path, + 'api_schema_versions_dir': swagger_versions_dir} + return return_data diff --git a/requirements.txt b/requirements.txt index e24a31837..d75fd037c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,9 @@ +tox>=3.0 bumpversion==0.5.3 wheel==0.23.0 watchdog==0.8.3 +pluggy flake8==3.5.0 -tox==3.0 coverage==4.0 Sphinx==1.3.1 #cryptography==1.9 diff --git a/setup.cfg b/setup.cfg index b547c3241..dd2e5f6d5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,11 +1,11 @@ [bumpversion] -current_version = 0.3.0 +current_version = 0.6.3 commit = True tag = True [bumpversion:file:setup.py] -[bumpversion:file:magpie/__init__.py] +[bumpversion:file:magpie/__meta__.py] [wheel] universal = 1 diff --git a/setup.py b/setup.py index 2390c1007..e48d9810d 100644 --- a/setup.py +++ b/setup.py @@ -1,13 +1,19 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import os +import sys +MAGPIE_ROOT = os.path.abspath(os.path.dirname(__file__)) +MAGPIE_MODULE_DIR = os.path.join(MAGPIE_ROOT, 'magpie') +sys.path.insert(0, MAGPIE_MODULE_DIR) + from setuptools import find_packages try: from setuptools import setup except ImportError: from distutils.core import setup -from magpie.__meta__ import __version__, __author__, __email__ +from magpie import __meta__ with open('README.rst') as readme_file: README = readme_file.read() @@ -15,9 +21,8 @@ with open('HISTORY.rst') as history_file: HISTORY = history_file.read().replace('.. :changelog:', '') -# See https://github.com/pypa/pip/issues/3610 -REQUIREMENTS = set([]) # use set to have unique packages by name -LINKS = set([]) +LINKS = set() # See https://github.com/pypa/pip/issues/3610 +REQUIREMENTS = set() # use set to have unique packages by name with open('requirements.txt', 'r') as requirements_file: for line in requirements_file: if 'git+https' in line: @@ -30,22 +35,36 @@ LINKS = list(LINKS) REQUIREMENTS = list(REQUIREMENTS) +# put package test requirements here TEST_REQUIREMENTS = [ - 'nose', + 'nose==1.3.7', 'webtest', - 'pytest' - # TODO: put package test requirements here + 'pytest', ] +raw_requirements = set() +for req in REQUIREMENTS: + raw_req = req.split('>')[0].split('=')[0].split('<')[0].split('!')[0] + raw_requirements.add(raw_req) +filtered_test_requirements = set() +for req in TEST_REQUIREMENTS: + raw_req = req.split('>')[0].split('=')[0].split('<')[0].split('!')[0] + if raw_req not in raw_requirements: + filtered_test_requirements.add(req) +TEST_REQUIREMENTS = list(filtered_test_requirements) + setup( # -- meta information -------------------------------------------------- name='magpie', - version=__version__, - description="Magpie is a service for AuthN and AuthZ based on Ziggurat-Foundations", + version=__meta__.__version__, + description=__meta__.__description__, long_description=README + '\n\n' + HISTORY, - author=__author__, - author_email=__email__, - url='https://github.com/Ouranosinc/Magpie', + author=__meta__.__author__, + maintainer=__meta__.__maintainer__, + maintainer_email=__meta__.__email__, + contact=__meta__.__maintainer__, + contact_email=__meta__.__email__, + url=__meta__.__url__, platforms=['linux_x86_64'], license="ISCL", keywords='magpie', @@ -54,12 +73,8 @@ 'Intended Audience :: Developers', 'License :: OSI Approved :: ISC License (ISCL)', 'Natural Language :: English', - "Programming Language :: Python :: 2", - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', ], # -- Package structure ------------------------------------------------- @@ -67,21 +82,22 @@ # 'magpie', #], packages=find_packages(), - package_dir={'magpie': - 'magpie'}, + package_dir={'magpie': 'magpie'}, include_package_data=True, install_requires=REQUIREMENTS, dependency_links=LINKS, zip_safe=False, # -- self - tests -------------------------------------------------------- - test_suite='tests', + #test_suite='nose.collector', + #test_suite='tests.test_runner', + #test_loader='tests.test_runner:run_suite', tests_require=TEST_REQUIREMENTS, # -- script entry points ----------------------------------------------- entry_points="""\ [paste.app_factory] - main = magpie:main + main = magpiectl:main [console_scripts] """, ) diff --git a/tests/runner.py b/tests/runner.py new file mode 100644 index 000000000..713765cf6 --- /dev/null +++ b/tests/runner.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from magpie.common import str2bool +import sqlalchemy.exc as sa_exc +import unittest +import warnings +import os + + +test_root_path = os.path.abspath(os.path.dirname(__file__)) +test_root_name = os.path.split(test_root_path)[1] +test_modules = [ + '{}.test_magpie_api'.format(test_root_name), + '{}.test_magpie_ui'.format(test_root_name), +] + +# run test options +MAGPIE_TEST_DEFAULTS = str2bool(os.getenv('MAGPIE_TEST_DEFAULTS', True)) +MAGPIE_TEST_LOGIN = str2bool(os.getenv('MAGPIE_TEST_LOGIN', True)) +MAGPIE_TEST_SERVICES = str2bool(os.getenv('MAGPIE_TEST_SERVICES', True)) +MAGPIE_TEST_RESOURCES = str2bool(os.getenv('MAGPIE_TEST_RESOURCES', True)) +MAGPIE_TEST_GROUPS = str2bool(os.getenv('MAGPIE_TEST_GROUPS', True)) +MAGPIE_TEST_USERS = str2bool(os.getenv('MAGPIE_TEST_USERS', True)) +MAGPIE_TEST_STATUS = str2bool(os.getenv('MAGPIE_TEST_STATUS', True)) +MAGPIE_TEST_REMOTE = str2bool(os.getenv('MAGPIE_TEST_REMOTE', True)) +MAGPIE_TEST_LOCAL = str2bool(os.getenv('MAGPIE_TEST_LOCAL', True)) +MAGPIE_TEST_API = str2bool(os.getenv('MAGPIE_TEST_API', True)) +MAGPIE_TEST_UI = str2bool(os.getenv('MAGPIE_TEST_UI', True)) + + +def test_suite(): + suite = unittest.TestSuite() + for t in test_modules: + try: + # If the module defines a suite() function, call it to get the suite. + mod = __import__(t, globals(), locals(), ['suite']) + suite_fn = getattr(mod, 'suite') + suite.addTest(suite_fn()) + except (ImportError, AttributeError): + try: + # else, just load all the test cases from the module. + suite.addTest(unittest.defaultTestLoader.loadTestsFromName(t)) + except AttributeError: + # if still not found, try discovery from root directory + #tests = unittest.defaultTestLoader.loadTestsFromModule(t) + #suite.addTests(tests) + suite.addTest(unittest.defaultTestLoader.discover(test_root_path)) + return suite + + +def run_suite(): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", category=sa_exc.SAWarning) + unittest.TextTestRunner().run(test_suite()) + + +if __name__ == '__main__': + run_suite() diff --git a/tests/test_magpie.py b/tests/test_magpie.py deleted file mode 100644 index 9e193c724..000000000 --- a/tests/test_magpie.py +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -test_magpie ----------------------------------- - -Tests for `magpie` module. -""" - -import unittest -import pytest -import pyramid.testing -from magpie import magpie -from test_utils import * - - -@pytest.mark.online -class TestMagpieNoAuth(unittest.TestCase): - """ - Test any operation that do not require user AuthN/AuthZ. - """ - - @classmethod - def setUpClass(cls): - cls.app = get_test_magpie_app() - - @classmethod - def tearDownClass(cls): - pyramid.testing.tearDown() - - def test_Home_valid(self): - resp = self.app.get('/', headers=json_headers) - assert resp.status_int == 200 - assert resp.content_type == 'text/html' - resp.mustcontain("Magpie Administration") - - def test_GetVersion_valid(self): - resp = self.app.get('/version', headers=json_headers) - assert resp.status_int == 200 - assert resp.content_type == 'application/json' - assert resp.json['version'] == magpie.__meta__.__version__ - - -@pytest.mark.online -class TestMagpieWithUsersAuth(unittest.TestCase): - """ - Test any operation that require at least 'Users' group AuthN/AuthZ. - """ - - @classmethod - def setUpClass(cls): - cls.app = get_test_magpie_app() - - @classmethod - def tearDownClass(cls): - pyramid.testing.tearDown() - - -@pytest.mark.online -class TestMagpieWithAdminAuth(unittest.TestCase): - """ - Test any operation that require at least 'Administrator' group AuthN/AuthZ. - """ - - @classmethod - def setUpClass(cls): - cls.app = get_test_magpie_app() - cls.usr = os.getenv('MAGPIE_TEST_ADMIN_USERNAME') - cls.pwd = os.getenv('MAGPIE_TEST_ADMIN_PASSWORD') - assert cls.usr is not None and cls.pwd is not None, "cannot login with unspecified username/password" - cls.headers = check_or_try_login_user(cls.app, cls.usr, cls.pwd) - cls.require = "cannot run tests without logged in 'administrator' user" - assert cls.headers is not None, cls.require - - @classmethod - def tearDownClass(cls): - pyramid.testing.tearDown() - - - #@pytest.mark.skip(reason='No way to test this now') - def test_GetAPI_valid(self): - assert check_or_try_login_user(self.app, self.usr, self.pwd) is not None, self.require - assert self.headers is not None, self.require - resp = self.app.get('/__api__', headers=json_headers + self.headers) - assert resp.status_int == 200 - assert resp.content_type == 'application/json' - assert resp.json['version'] == magpie.__meta__.__version__ - - -if __name__ == '__main__': - import sys - sys.exit(unittest.main()) diff --git a/tests/test_magpie_api.py b/tests/test_magpie_api.py new file mode 100644 index 000000000..3d4c9980c --- /dev/null +++ b/tests/test_magpie_api.py @@ -0,0 +1,672 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +test_magpie_api +---------------------------------- + +Tests for `magpie.api` module. +""" + +import os +import unittest +import pytest +import pyramid.testing +import yaml +import six +from six.moves.urllib.parse import urlparse +import magpie +from services import service_type_dict +from register import get_twitcher_protected_service_url +from magpie.api.api_rest_schemas import SwaggerGenerator +from magpie import __meta__ +from tests.utils import * +from tests.runner import * + + +@pytest.mark.api +@pytest.mark.local +@unittest.skipUnless(MAGPIE_TEST_API, reason="Skip 'api' tests requested.") +@unittest.skipUnless(MAGPIE_TEST_LOCAL, reason="Skip 'local' tests requested.") +class TestMagpieAPI_NoAuthLocal(unittest.TestCase): + """ + Test any operation that do not require user AuthN/AuthZ. + """ + + @classmethod + def setUpClass(cls): + cls.app = get_test_magpie_app() + cls.url = cls.app # to simplify calls of TestSetup (all use .url) + cls.json_headers = get_headers_content_type(cls.app, 'application/json') + cls.version = magpie.__meta__.__version__ + cls.cookies = None + cls.usr = magpie.ANONYMOUS_USER + + @classmethod + def tearDownClass(cls): + pyramid.testing.tearDown() + + @pytest.mark.login + @unittest.skipUnless(MAGPIE_TEST_LOGIN, reason="Skip 'login' tests requested.") + def test_GetSession_Anonymous(self): + resp = test_request(self.app, 'GET', '/session', headers=self.json_headers) + json_body = check_response_basic_info(resp, 200) + check_val_equal(json_body['authenticated'], False) + if LooseVersion(self.version) >= LooseVersion('0.6.3'): + check_val_not_in('user', json_body) + else: + check_val_not_in('user_name', json_body) + check_val_not_in('user_email', json_body) + check_val_not_in('group_names', json_body) + + def test_GetVersion(self): + resp = test_request(self.app, 'GET', '/version', headers=self.json_headers) + json_body = check_response_basic_info(resp, 200) + check_val_is_in('db_version', json_body) + check_val_is_in('version', json_body) + check_val_equal(json_body['version'], self.version) + + @pytest.mark.users + @unittest.skipUnless(MAGPIE_TEST_USERS, reason="Skip 'users' tests requested.") + def test_GetCurrentUser(self): + resp = test_request(self.url, 'GET', '/users/{}'.format(magpie.LOGGED_USER), headers=self.json_headers) + json_body = check_response_basic_info(resp, 200) + if LooseVersion(self.version) >= LooseVersion('0.6.3'): + check_val_equal(json_body['user']['user_name'], self.usr) + else: + check_val_equal(json_body['user_name'], self.usr) + + +@unittest.skip("Not implemented.") +@pytest.mark.skip(reason="Not implemented.") +@pytest.mark.api +@pytest.mark.local +@unittest.skipUnless(MAGPIE_TEST_API, reason="Skip 'api' tests requested.") +@unittest.skipUnless(MAGPIE_TEST_LOCAL, reason="Skip 'local' tests requested.") +class TestMagpieAPI_UsersAuthLocal(unittest.TestCase): + """ + Test any operation that require at least 'Users' group AuthN/AuthZ. + """ + + @classmethod + def setUpClass(cls): + cls.app = get_test_magpie_app() + + @classmethod + def tearDownClass(cls): + pyramid.testing.tearDown() + + +@unittest.skip("Signin not working, cannot test protected paths.") +@pytest.mark.skip(reason="Signin not working, cannot test protected paths.") +@pytest.mark.api +@pytest.mark.local +@unittest.skipUnless(MAGPIE_TEST_API, reason="Skip 'api' tests requested.") +@unittest.skipUnless(MAGPIE_TEST_LOCAL, reason="Skip 'local' tests requested.") +class TestMagpieAPI_AdminAuthLocal(unittest.TestCase): + """ + Test any operation that require at least 'Administrator' group AuthN/AuthZ. + """ + + @classmethod + def setUpClass(cls): + cls.app = get_test_magpie_app() + cls.url = cls.app # to simplify calls of TestSetup (all use .url) + cls.usr = os.getenv('MAGPIE_TEST_ADMIN_USERNAME') + cls.pwd = os.getenv('MAGPIE_TEST_ADMIN_PASSWORD') + assert cls.usr and cls.pwd, "cannot login with unspecified username/password" + cls.headers, cls.cookies = check_or_try_login_user(cls.app, cls.usr, cls.pwd) + cls.require = "cannot run tests without logged in '{}' user".format(magpie.ADMIN_GROUP) + cls.json_headers = get_headers_content_type(cls.app, 'application/json') + assert cls.headers and cls.cookies, cls.require + cls.app.cookies = cls.cookies + cls.version = TestSetup.get_Version(cls) + + @classmethod + def tearDownClass(cls): + pyramid.testing.tearDown() + + @classmethod + def check_requirements(cls): + headers, cookies = check_or_try_login_user(cls.app, cls.usr, cls.pwd) + assert headers and cookies, cls.require + assert cls.headers and cls.cookies and cls.app.cookies, cls.require + + def setUp(self): + self.check_requirements() + + def test_GetAPI(self): + resp = test_request(self.app, 'GET', SwaggerGenerator.path, headers=self.json_headers) + json_body = check_response_basic_info(resp, 200) + check_val_is_in('db_version', json_body) + check_val_is_in('version', json_body) + check_val_equal(json_body['version'], magpie.__meta__.__version__) + check_val_type(json_body['version'], six.string_types) + version_parts = json_body['version'].split('.') + check_val_equal(len(version_parts), 3) + + @pytest.mark.users + @unittest.skipUnless(MAGPIE_TEST_USERS, reason="Skip 'users' tests requested.") + def test_GetUsers(self): + resp = test_request(self.app, 'GET', '/users', headers=self.json_headers) + json_body = check_response_basic_info(resp, 200) + check_val_is_in('user_names', json_body) + check_val_is_in('anonymous', json_body['user_names']) # anonymous always in users + check_val_equal(len(json_body['user_names']) > 1, True) # should have more than only 'anonymous' + + +@pytest.mark.api +@pytest.mark.remote +@unittest.skipUnless(MAGPIE_TEST_API, reason="Skip 'api' tests requested.") +@unittest.skipUnless(MAGPIE_TEST_REMOTE, reason="Skip 'remote' tests requested.") +class TestMagpieAPI_NoAuthRemote(unittest.TestCase): + """ + Test any operation that do not require user AuthN/AuthZ. + """ + + @classmethod + def setUpClass(cls): + cls.url = os.getenv('MAGPIE_TEST_REMOTE_SERVER_URL') + assert cls.url, "cannot test without a remote server URL" + cls.json_headers = get_headers_content_type(cls.url, 'application/json') + cls.cookies = None + cls.usr = magpie.ANONYMOUS_USER + cls.version = TestSetup.get_Version(cls) + + @classmethod + def tearDownClass(cls): + pyramid.testing.tearDown() + + @pytest.mark.login + @unittest.skipUnless(MAGPIE_TEST_LOGIN, reason="Skip 'login' tests requested.") + def test_GetSession_Anonymous(self): + resp = test_request(self.url, 'GET', '/session', headers=self.json_headers) + json_body = check_response_basic_info(resp, 200) + check_val_equal(json_body['authenticated'], False) + if LooseVersion(self.version) >= LooseVersion('0.6.3'): + check_val_not_in('user', json_body) + else: + check_val_not_in('user_name', json_body) + check_val_not_in('user_email', json_body) + check_val_not_in('group_names', json_body) + + def test_GetVersion(self): + resp = test_request(self.url, 'GET', '/version', headers=self.json_headers) + json_body = check_response_basic_info(resp, 200) + check_val_is_in('db_version', json_body) + check_val_is_in('version', json_body) + # server not necessarily at latest version, ensure at least format + #check_val_equal(json_body['version'], magpie.__meta__.__version__) + check_val_type(json_body['version'], six.string_types) + version_parts = json_body['version'].split('.') + check_val_equal(len(version_parts), 3) + + @pytest.mark.users + @unittest.skipUnless(MAGPIE_TEST_USERS, reason="Skip 'users' tests requested.") + def test_GetCurrentUser(self): + resp = test_request(self.url, 'GET', '/users/current', headers=self.json_headers) + json_body = check_response_basic_info(resp, 200) + if LooseVersion(self.version) >= LooseVersion('0.6.3'): + check_val_equal(json_body['user']['user_name'], self.usr) + else: + check_val_equal(json_body['user_name'], self.usr) + + +@pytest.mark.api +@pytest.mark.remote +@unittest.skipUnless(MAGPIE_TEST_API, reason="Skip 'api' tests requested.") +@unittest.skipUnless(MAGPIE_TEST_REMOTE, reason="Skip 'remote' tests requested.") +class TestMagpieAPI_AdminAuthRemote(unittest.TestCase): + """ + Test any operation that require at least 'Administrator' group AuthN/AuthZ. + Use an already running remote bird server. + """ + + @classmethod + def setUpClass(cls): + cls.usr = os.getenv('MAGPIE_TEST_ADMIN_USERNAME') + cls.pwd = os.getenv('MAGPIE_TEST_ADMIN_PASSWORD') + cls.url = os.getenv('MAGPIE_TEST_REMOTE_SERVER_URL') + assert cls.url, "cannot test without a remote server URL" + assert cls.usr and cls.pwd, "cannot login with unspecified username/password" + cls.headers, cls.cookies = check_or_try_login_user(cls.url, cls.usr, cls.pwd) + cls.require = "cannot run tests without logged in '{}' user".format(magpie.ADMIN_GROUP) + cls.json_headers = get_headers_content_type(cls.url, 'application/json') + cls.check_requirements() + cls.version = TestSetup.get_Version(cls) + cls.get_test_values() + + @classmethod + def tearDownClass(cls): + TestSetup.delete_TestServiceResource(cls) + TestSetup.delete_TestUser(cls) + pyramid.testing.tearDown() + + @classmethod + def check_requirements(cls): + headers, cookies = check_or_try_login_user(cls.url, cls.usr, cls.pwd) + assert headers and cookies, cls.require + assert cls.headers and cls.cookies, cls.require + + @classmethod + def get_test_values(cls): + services_cfg = yaml.load(open(magpie.MAGPIE_PROVIDERS_CONFIG_PATH, 'r')) + provider_services_info = services_cfg['providers'] + # filter impossible providers from possible previous version of remote server + possible_service_types = get_service_types_for_version(cls.version) + cls.test_services_info = dict() + for svc_name in provider_services_info: + if provider_services_info[svc_name]['type'] in possible_service_types: + cls.test_services_info[svc_name] = provider_services_info[svc_name] + + cls.test_service_name = u'project-api' + cls.test_service_type = cls.test_services_info[cls.test_service_name]['type'] + check_val_is_in(cls.test_service_type, cls.test_services_info) + + resp = test_request(cls.url, 'GET', '/services/project-api', headers=cls.json_headers, cookies=cls.cookies) + json_body = check_response_basic_info(resp, 200) + cls.test_service_resource_id = json_body[cls.test_service_name]['resource_id'] + + cls.test_resource_name = u'magpie-unittest-resource' + test_service_resource_types = service_type_dict[cls.test_service_type].resource_types_permissions.keys() + assert len(test_service_resource_types), "test service should allow at least 1 sub-resource for test execution" + cls.test_resource_type = test_service_resource_types[0] + + cls.test_user_name = u'magpie-unittest-toto' + cls.test_user_group = u'users' + + def setUp(self): + self.check_requirements() + TestSetup.delete_TestServiceResource(self) + TestSetup.delete_TestUser(self) + + @pytest.mark.login + @unittest.skipUnless(MAGPIE_TEST_LOGIN, reason="Skip 'login' tests requested.") + def test_GetSession_Administrator(self): + resp = test_request(self.url, 'GET', '/session', headers=self.json_headers, cookies=self.cookies) + json_body = check_response_basic_info(resp, 200) + check_val_equal(json_body['authenticated'], True) + if LooseVersion(self.version) >= LooseVersion('0.6.3'): + check_val_is_in('user', json_body) + check_val_equal(json_body['user']['user_name'], self.usr) + check_val_is_in(magpie.ADMIN_GROUP, json_body['user']['group_names']) + check_val_type(json_body['user']['group_names'], list) + check_val_is_in('email', json_body['user']) + else: + check_val_equal(json_body['user_name'], self.usr) + check_val_is_in(magpie.ADMIN_GROUP, json_body['group_names']) + check_val_type(json_body['group_names'], list) + check_val_is_in('user_email', json_body) + + @pytest.mark.users + @unittest.skipUnless(MAGPIE_TEST_USERS, reason="Skip 'users' tests requested.") + def test_GetUsers(self): + resp = test_request(self.url, 'GET', '/users', headers=self.json_headers, cookies=self.cookies) + json_body = check_response_basic_info(resp, 200) + check_val_is_in('user_names', json_body) + check_val_type(json_body['user_names'], list) + check_val_equal(len(json_body['user_names']) > 1, True) # should have more than only 'anonymous' + check_val_is_in('anonymous', json_body['user_names']) # anonymous always in users + check_val_is_in(self.usr, json_body['user_names']) # current test user in users + + @pytest.mark.users + @pytest.mark.defaults + @unittest.skipUnless(MAGPIE_TEST_USERS, reason="Skip 'users' tests requested.") + @unittest.skipUnless(MAGPIE_TEST_DEFAULTS, reason="Skip 'defaults' tests requested.") + def test_ValidateDefaultUsers(self): + resp = test_request(self.url, 'GET', '/users', headers=self.json_headers, cookies=self.cookies) + json_body = check_response_basic_info(resp, 200) + users = json_body['user_names'] + check_val_is_in(magpie.ANONYMOUS_USER, users) + check_val_is_in(magpie.ADMIN_USER, users) + + @classmethod + def check_GetUserResourcesPermissions(cls, user_name): + route = '/users/{usr}/resources/{res_id}/permissions'.format(res_id=cls.test_service_resource_id, usr=user_name) + resp = test_request(cls.url, 'GET', route, headers=cls.json_headers, cookies=cls.cookies) + json_body = check_response_basic_info(resp, 200) + check_val_is_in('permission_names', json_body) + check_val_type(json_body['permission_names'], list) + + @pytest.mark.users + @unittest.skipUnless(MAGPIE_TEST_USERS, reason="Skip 'users' tests requested.") + def test_GetCurrentUserResourcesPermissions(self): + self.check_GetUserResourcesPermissions(magpie.LOGGED_USER) + + @pytest.mark.users + @unittest.skipUnless(MAGPIE_TEST_USERS, reason="Skip 'users' tests requested.") + def test_GetUserResourcesPermissions(self): + self.check_GetUserResourcesPermissions(self.usr) + + @pytest.mark.users + @unittest.skipUnless(MAGPIE_TEST_USERS, reason="Skip 'users' tests requested.") + def test_GetCurrentUserGroups(self): + resp = test_request(self.url, 'GET', '/users/current/groups', headers=self.json_headers, cookies=self.cookies) + json_body = check_response_basic_info(resp, 200) + check_val_is_in('group_names', json_body) + check_val_type(json_body['group_names'], list) + check_val_is_in(magpie.ADMIN_GROUP, json_body['group_names']) + + @pytest.mark.users + @unittest.skipUnless(MAGPIE_TEST_USERS, reason="Skip 'users' tests requested.") + def test_GetUserInheritedResources(self): + route = '/users/{usr}/inherited_resources'.format(usr=self.usr) + resp = test_request(self.url, 'GET', route, headers=self.json_headers, cookies=self.cookies) + json_body = check_response_basic_info(resp, 200) + check_val_is_in('resources', json_body) + check_val_type(json_body['resources'], dict) + check_all_equal(json_body['resources'].keys(), get_service_types_for_version(self.version), any_order=True) + for svc_type in json_body['resources']: + for svc in json_body['resources'][svc_type]: + svc_dict = json_body['resources'][svc_type][svc] + check_val_type(svc_dict, dict) + check_val_is_in('resource_id', svc_dict) + check_val_is_in('service_name', svc_dict) + check_val_is_in('service_type', svc_dict) + check_val_is_in('service_url', svc_dict) + check_val_is_in('public_url', svc_dict) + check_val_is_in('permission_names', svc_dict) + check_val_is_in('resources', svc_dict) + check_val_type(svc_dict['resource_id'], int) + check_val_type(svc_dict['service_name'], six.string_types) + check_val_type(svc_dict['service_url'], six.string_types) + check_val_type(svc_dict['service_type'], six.string_types) + check_val_type(svc_dict['public_url'], six.string_types) + check_val_type(svc_dict['permission_names'], list) + check_val_type(svc_dict['resources'], dict) + + @pytest.mark.users + @pytest.mark.defaults + @unittest.skipUnless(MAGPIE_TEST_USERS, reason="Skip 'users' tests requested.") + @unittest.skipUnless(MAGPIE_TEST_DEFAULTS, reason="Skip 'defaults' tests requested.") + def test_ValidateDefaultGroups(self): + resp = test_request(self.url, 'GET', '/groups', headers=self.json_headers, cookies=self.cookies) + json_body = check_response_basic_info(resp, 200) + groups = json_body['group_names'] + check_val_is_in(magpie.ANONYMOUS_GROUP, groups) + check_val_is_in(magpie.USER_GROUP, groups) + check_val_is_in(magpie.ADMIN_GROUP, groups) + + @pytest.mark.users + @unittest.skipUnless(MAGPIE_TEST_USERS, reason="Skip 'users' tests requested.") + def test_PostUsers(self): + json_body = TestSetup.create_TestUser(self) + if LooseVersion(self.version) >= LooseVersion('0.6.3'): + check_val_is_in('user', json_body) + check_val_is_in('user_name', json_body['user']) + check_val_type(json_body['user']['user_name'], six.string_types) + check_val_is_in('email', json_body['user']) + check_val_type(json_body['user']['email'], six.string_types) + check_val_is_in('group_names', json_body['user']) + check_val_type(json_body['user']['group_names'], list) + + users = TestSetup.get_RegisteredUsersList(self) + check_val_is_in(self.test_user_name, users) + + @pytest.mark.users + @unittest.skipUnless(MAGPIE_TEST_USERS, reason="Skip 'users' tests requested.") + def test_GetUser_existing(self): + TestSetup.create_TestUser(self) + + route = '/users/{usr}'.format(usr=self.test_user_name) + resp = test_request(self.url, 'GET', route, headers=self.json_headers, cookies=self.cookies) + json_body = check_response_basic_info(resp, 200) + if LooseVersion(self.version) >= LooseVersion('0.6.3'): + check_val_is_in('user', json_body) + check_val_is_in('user_name', json_body['user']) + check_val_type(json_body['user']['user_name'], six.string_types) + check_val_is_in('email', json_body['user']) + check_val_type(json_body['user']['email'], six.string_types) + check_val_is_in('group_names', json_body['user']) + check_val_type(json_body['user']['group_names'], list) + else: + check_val_is_in('user_name', json_body) + check_val_type(json_body['user_name'], six.string_types) + check_val_is_in('email', json_body) + check_val_type(json_body['email'], six.string_types) + check_val_is_in('group_names', json_body) + check_val_type(json_body['group_names'], list) + + @pytest.mark.users + @unittest.skipUnless(MAGPIE_TEST_USERS, reason="Skip 'users' tests requested.") + def test_GetUser_missing(self): + TestSetup.check_NonExistingTestUser(self) + route = '/users/{usr}'.format(usr=self.test_user_name) + resp = test_request(self.url, 'GET', route, headers=self.json_headers, cookies=self.cookies) + check_response_basic_info(resp, 404) + + @pytest.mark.users + @unittest.skipUnless(MAGPIE_TEST_USERS, reason="Skip 'users' tests requested.") + def test_GetCurrentUser(self): + TestSetup.check_NonExistingTestUser(self) + resp = test_request(self.url, 'GET', '/users/current', headers=self.json_headers, cookies=self.cookies) + json_body = check_response_basic_info(resp, 200) + if LooseVersion(self.version) >= LooseVersion('0.6.3'): + check_val_equal(json_body['user']['user_name'], self.usr) + else: + check_val_equal(json_body['user_name'], self.usr) + + @pytest.mark.groups + @unittest.skipUnless(MAGPIE_TEST_GROUPS, reason="Skip 'groups' tests requested.") + def test_GetGroupUsers(self): + route = '/groups/{grp}/users'.format(grp=magpie.ADMIN_GROUP) + resp = test_request(self.url, 'GET', route, headers=self.json_headers, cookies=self.cookies) + json_body = check_response_basic_info(resp, 200) + check_val_is_in('user_names', json_body) + check_val_type(json_body['user_names'], list) + check_val_is_in(magpie.ADMIN_USER, json_body['user_names']) + check_val_is_in(self.usr, json_body['user_names']) + + @pytest.mark.services + @unittest.skipUnless(MAGPIE_TEST_SERVICES, reason="Skip 'services' tests requested.") + def test_GetServiceResources(self): + route = '/services/{svc}/resources'.format(svc=self.test_service_name) + resp = test_request(self.url, 'GET', route, headers=self.json_headers, cookies=self.cookies) + json_body = check_response_basic_info(resp, 200) + svc_dict = json_body[self.test_service_name] + check_val_is_in(self.test_service_name, json_body) + check_val_type(json_body[self.test_service_name], dict) + check_val_is_in('resource_id', svc_dict) + check_val_is_in('service_name', svc_dict) + check_val_is_in('service_type', svc_dict) + check_val_is_in('service_url', svc_dict) + check_val_is_in('public_url', svc_dict) + check_val_is_in('permission_names', svc_dict) + check_val_is_in('resources', svc_dict) + check_val_type(svc_dict['resource_id'], int) + check_val_type(svc_dict['service_name'], six.string_types) + check_val_type(svc_dict['service_url'], six.string_types) + check_val_type(svc_dict['service_type'], six.string_types) + check_val_type(svc_dict['public_url'], six.string_types) + check_val_type(svc_dict['permission_names'], list) + check_resource_children(svc_dict['resources'], svc_dict['resource_id'], svc_dict['resource_id']) + + @pytest.mark.services + @unittest.skipUnless(MAGPIE_TEST_SERVICES, reason="Skip 'services' tests requested.") + def test_GetServicePermissions(self): + services_list = TestSetup.get_RegisteredServicesList(self) + + for svc in services_list: + svc_name = svc['service_name'] + service_perms = service_type_dict[svc['service_type']].permission_names + route = '/services/{svc}/permissions'.format(svc=svc_name) + resp = test_request(self.url, 'GET', route, headers=self.json_headers, cookies=self.cookies) + json_body = check_response_basic_info(resp, 200) + check_val_is_in('permission_names', json_body) + check_val_type(json_body['permission_names'], list) + check_all_equal(json_body['permission_names'], service_perms) + + @pytest.mark.services + @unittest.skipUnless(MAGPIE_TEST_SERVICES, reason="Skip 'services' tests requested.") + def test_PostServiceResources_DirectResource_NoParentID(self): + resources_prior = TestSetup.get_ExistingTestServiceDirectResources(self) + resources_prior_ids = [res['resource_id'] for res in resources_prior] + json_body = TestSetup.create_TestServiceResource(self) + check_val_is_in('resource_id', json_body) + check_val_is_in('resource_name', json_body) + check_val_is_in('resource_type', json_body) + check_val_not_in(json_body['resource_id'], resources_prior_ids) + check_val_equal(json_body['resource_name'], self.test_resource_name) + check_val_equal(json_body['resource_type'], self.test_resource_type) + + @pytest.mark.services + @unittest.skipUnless(MAGPIE_TEST_SERVICES, reason="Skip 'services' tests requested.") + def test_PostServiceResources_DirectResource_WithParentID(self): + resources_prior = TestSetup.get_ExistingTestServiceDirectResources(self) + resources_prior_ids = [res['resource_id'] for res in resources_prior] + service_id = TestSetup.get_ExistingTestServiceInfo(self)['resource_id'] + extra_data = {"parent_id": service_id} + json_body = TestSetup.create_TestServiceResource(self, extra_data) + check_val_is_in('resource_id', json_body) + check_val_is_in('resource_name', json_body) + check_val_is_in('resource_type', json_body) + check_val_not_in(json_body['resource_id'], resources_prior_ids) + check_val_equal(json_body['resource_name'], self.test_resource_name) + check_val_equal(json_body['resource_type'], self.test_resource_type) + + @pytest.mark.services + @unittest.skipUnless(MAGPIE_TEST_SERVICES, reason="Skip 'services' tests requested.") + def test_PostServiceResources_ChildrenResource_ParentID(self): + # create the direct resource + json_body = TestSetup.create_TestServiceResource(self) + resources = TestSetup.get_ExistingTestServiceDirectResources(self) + resources_ids = [res['resource_id'] for res in resources] + test_resource_id = json_body['resource_id'] + check_val_is_in(test_resource_id, resources_ids, msg="service resource must exist to create children resource") + + # create the children resource under the direct resource and validate response info + child_resource_name = self.test_resource_name + "-children" + data_override = { + "resource_name": child_resource_name, + "resource_type": self.test_resource_type, + "parent_id": test_resource_id + } + json_body = TestSetup.create_TestServiceResource(self, data_override) + check_val_is_in('resource_id', json_body) + check_val_not_in(json_body['resource_id'], resources_ids) + check_val_is_in('resource_name', json_body) + check_val_equal(json_body['resource_name'], child_resource_name) + check_val_is_in('resource_type', json_body) + check_val_equal(json_body['resource_type'], self.test_resource_type) + + # validate created children resource info + service_root_id = TestSetup.get_ExistingTestServiceInfo(self)['resource_id'] + child_resource_id = json_body['resource_id'] + route = '/resources/{res_id}'.format(res_id=child_resource_id) + resp = test_request(self.url, 'GET', route, headers=self.json_headers, cookies=self.cookies) + json_body = check_response_basic_info(resp, 200) + check_val_is_in(str(child_resource_id), json_body) + resource_body = json_body[str(child_resource_id)] + check_val_equal(resource_body['root_service_id'], service_root_id) + check_val_equal(resource_body['parent_id'], test_resource_id) + check_val_equal(resource_body['resource_id'], child_resource_id) + check_val_equal(resource_body['resource_name'], child_resource_name) + check_val_equal(resource_body['resource_type'], self.test_resource_type) + check_val_type(resource_body['children'], dict) + check_val_equal(len(resource_body['children']), 0) + + @pytest.mark.services + @unittest.skipUnless(MAGPIE_TEST_SERVICES, reason="Skip 'services' tests requested.") + def test_PostServiceResources_DirectResource_Conflict(self): + TestSetup.create_TestServiceResource(self) + route = '/services/{svc}/resources'.format(svc=self.test_service_name) + data = {"resource_name": self.test_resource_name, "resource_type": self.test_resource_type} + resp = test_request(self.url, 'POST', route, headers=self.json_headers, cookies=self.cookies, json=data) + json_body = check_response_basic_info(resp, 409) + check_error_param_structure(json_body, version=self.version, + isParamValueLiteralUnicode=True, paramCompareExists=True, + paramValue=self.test_resource_name, paramName=u'resource_name') + + @pytest.mark.services + @pytest.mark.defaults + @unittest.skipUnless(MAGPIE_TEST_SERVICES, reason="Skip 'services' tests requested.") + @unittest.skipUnless(MAGPIE_TEST_DEFAULTS, reason="Skip 'defaults' tests requested.") + def test_ValidateDefaultServiceProviders(self): + services_list = TestSetup.get_RegisteredServicesList(self) + + # ensure that registered services information are all matching the providers in config file + # ignore registered services not from providers as their are not explicitly required from the config + for svc in services_list: + svc_name = svc['service_name'] + if svc_name in self.test_services_info: + check_val_equal(svc['service_type'], self.test_services_info[svc_name]['type']) + hostname = urlparse(self.url).hostname + check_val_equal(svc['public_url'], get_twitcher_protected_service_url(svc_name, hostname=hostname)) + svc_url = self.test_services_info[svc_name]['url'].replace('${HOSTNAME}', hostname) + check_val_equal(svc['service_url'], svc_url) + + # ensure that no providers are missing from registered services + registered_svc_names = [svc['service_name'] for svc in services_list] + for svc_name in self.test_services_info: + check_val_is_in(svc_name, registered_svc_names) + + # ensure that 'getcapabilities' permission is given to anonymous for applicable services + services_list_getcap = [svc for svc in services_list if 'getcapabilities' in svc['permission_names']] + route = '/users/{usr}/services'.format(usr=magpie.ANONYMOUS_USER) + resp = test_request(self.url, 'GET', route, headers=self.json_headers, cookies=self.cookies) + json_body = check_response_basic_info(resp, 200) + services_body = json_body['services'] + for svc in services_list_getcap: + svc_name = svc['service_name'] + svc_type = svc['service_type'] + msg = "Service `{name}` of type `{type}` is expected to have `{perm}` permissions for user `{usr}`" \ + .format(name=svc_name, type=svc_type, perm='getcapabilities', usr=magpie.ANONYMOUS_USER) + check_val_is_in(svc_name, services_body[svc_type], msg=msg) + check_val_is_in('getcapabilities', services_body[svc_type][svc_name]['permission_names']) + + @pytest.mark.resources + @unittest.skipUnless(MAGPIE_TEST_RESOURCES, reason="Skip 'resources' tests requested.") + def test_PostResources_DirectServiceResource(self): + service_info = TestSetup.get_ExistingTestServiceInfo(self) + service_resource_id = service_info['resource_id'] + + data = { + "resource_name": self.test_resource_name, + "resource_type": self.test_resource_type, + "parent_id": service_resource_id + } + resp = test_request(self.url, 'POST', '/resources', headers=self.json_headers, cookies=self.cookies, data=data) + json_body = check_response_basic_info(resp, 201) + check_post_resource_structure(json_body, self.test_resource_name, self.test_resource_type, self.version) + + @pytest.mark.resources + @unittest.skipUnless(MAGPIE_TEST_RESOURCES, reason="Skip 'resources' tests requested.") + def test_PostResources_ChildrenResource(self): + resource_info = TestSetup.create_TestServiceResource(self) + direct_resource_id = resource_info['resource_id'] + + data = { + "resource_name": self.test_resource_name, + "resource_type": self.test_resource_type, + "parent_id": direct_resource_id + } + resp = test_request(self.url, 'POST', '/resources', headers=self.json_headers, cookies=self.cookies, data=data) + json_body = check_response_basic_info(resp, 201) + check_post_resource_structure(json_body, self.test_resource_name, self.test_resource_type, self.version) + + @pytest.mark.resources + @unittest.skipUnless(MAGPIE_TEST_RESOURCES, reason="Skip 'resources' tests requested.") + def test_PostResources_MissingParentID(self): + data = { + "resource_name": self.test_resource_name, + "resource_type": self.test_resource_type, + } + resp = test_request(self.url, 'POST', '/resources', headers=self.json_headers, cookies=self.cookies, data=data) + json_body = check_response_basic_info(resp, 422) + check_error_param_structure(json_body, paramName='parent_id', paramValue=repr(None), version=self.version) + + @pytest.mark.resources + @unittest.skipUnless(MAGPIE_TEST_RESOURCES, reason="Skip 'resources' tests requested.") + def test_DeleteResource(self): + json_body = TestSetup.create_TestServiceResource(self) + resource_id = json_body['resource_id'] + + route = '/resources/{res_id}'.format(res_id=resource_id) + resp = test_request(self.url, 'DELETE', route, headers=self.json_headers, cookies=self.cookies) + check_response_basic_info(resp, 200) + TestSetup.check_NonExistingTestResource(self) + + +if __name__ == '__main__': + import sys + sys.exit(unittest.main()) diff --git a/tests/test_magpie_ui.py b/tests/test_magpie_ui.py new file mode 100644 index 000000000..e758344ae --- /dev/null +++ b/tests/test_magpie_ui.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +test_magpie_ui +---------------------------------- + +Tests for `magpie.ui` module. +""" + +import os +import unittest +import pytest +import pyramid.testing +import six +import yaml +import magpie +from services import service_type_dict +from magpie import * +from tests.utils import * +from tests.runner import * + + +@pytest.mark.ui +@pytest.mark.local +@unittest.skipUnless(MAGPIE_TEST_UI, reason="Skip 'ui' tests requested.") +@unittest.skipUnless(MAGPIE_TEST_LOCAL, reason="Skip 'local' tests requested.") +class TestMagpieUI_NoAuthLocal(unittest.TestCase): + """ + Test any operation that do not require user AuthN/AuthZ. + """ + + @classmethod + def setUpClass(cls): + cls.app = get_test_magpie_app() + cls.url = cls.app # to simplify calls of TestSetup (all use .url) + cls.json_headers = get_headers_content_type(cls.app, 'application/json') + cls.cookies = None + + @classmethod + def tearDownClass(cls): + pyramid.testing.tearDown() + + @pytest.mark.status + @unittest.skipUnless(MAGPIE_TEST_STATUS, reason="Skip 'status' tests requested.") + def test_Home(self): + TestSetup.check_UpStatus(self, method='GET', path='/') + + @pytest.mark.status + @unittest.skipUnless(MAGPIE_TEST_STATUS, reason="Skip 'status' tests requested.") + def test_Login(self): + TestSetup.check_UpStatus(self, method='GET', path='/ui/login') + + +@unittest.skip("Not implemented.") +@pytest.mark.skip(reason="Not implemented.") +@pytest.mark.ui +@pytest.mark.local +@unittest.skipUnless(MAGPIE_TEST_UI, reason="Skip 'ui' tests requested.") +@unittest.skipUnless(MAGPIE_TEST_LOCAL, reason="Skip 'local' tests requested.") +class TestMagpieUI_AdminAuthLocal(unittest.TestCase): + """ + Test any operation that require at least 'administrator' group AuthN/AuthZ. + """ + + @classmethod + def setUpClass(cls): + cls.app = get_test_magpie_app() + + @classmethod + def tearDownClass(cls): + pyramid.testing.tearDown() + + +@pytest.mark.ui +@pytest.mark.remote +@unittest.skipUnless(MAGPIE_TEST_UI, reason="Skip 'ui' tests requested.") +@unittest.skipUnless(MAGPIE_TEST_REMOTE, reason="Skip 'remote' tests requested.") +class TestMagpieUI_NoAuthRemote(unittest.TestCase): + """ + Test any operation that do not require user AuthN/AuthZ. + """ + + @classmethod + def setUpClass(cls): + cls.url = os.getenv('MAGPIE_TEST_REMOTE_SERVER_URL') + assert cls.url, "cannot test without a remote server URL" + cls.json_headers = get_headers_content_type(cls.url, 'application/json') + cls.cookies = None + cls.usr = magpie.ANONYMOUS_USER + cls.version = TestSetup.get_Version(cls) + + @classmethod + def tearDownClass(cls): + pyramid.testing.tearDown() + + @pytest.mark.status + @unittest.skipUnless(MAGPIE_TEST_STATUS, reason="Skip 'status' tests requested.") + def test_Home(self): + TestSetup.check_UpStatus(self, method='GET', path='/') + + @pytest.mark.status + @unittest.skipUnless(MAGPIE_TEST_STATUS, reason="Skip 'status' tests requested.") + def test_Login(self): + TestSetup.check_UpStatus(self, method='GET', path='/ui/login') + + +@pytest.mark.ui +@pytest.mark.remote +@unittest.skipUnless(MAGPIE_TEST_UI, reason="Skip 'ui' tests requested.") +@unittest.skipUnless(MAGPIE_TEST_REMOTE, reason="Skip 'remote' tests requested.") +class TestMagpieUI_AdminAuthRemote(unittest.TestCase): + """ + Test any operation that require at least 'Administrator' group AuthN/AuthZ. + """ + + @classmethod + def setUpClass(cls): + cls.url = os.getenv('MAGPIE_TEST_REMOTE_SERVER_URL') + cls.usr = os.getenv('MAGPIE_TEST_ADMIN_USERNAME') + cls.pwd = os.getenv('MAGPIE_TEST_ADMIN_PASSWORD') + assert cls.url, "cannot test without a remote server URL" + assert cls.usr and cls.pwd, "cannot login with unspecified username/password" + cls.headers, cls.cookies = check_or_try_login_user(cls.url, cls.usr, cls.pwd) + cls.require = "cannot run tests without logged in '{}' user".format(magpie.ADMIN_GROUP) + cls.json_headers = get_headers_content_type(cls.url, 'application/json') + cls.check_requirements() + cls.version = TestSetup.get_Version(cls) + cls.test_service_type = get_service_types_for_version(cls.version)[0] + cls.test_service_name = TestSetup.get_AnyServiceOfTestServiceType(cls)['service_name'] + + @classmethod + def tearDownClass(cls): + pyramid.testing.tearDown() + + @classmethod + def check_requirements(cls): + headers, cookies = check_or_try_login_user(cls.url, cls.usr, cls.pwd) + assert headers and cookies, cls.require + assert cls.headers and cls.cookies, cls.require + + def setUp(self): + self.check_requirements() + + @pytest.mark.status + @unittest.skipUnless(MAGPIE_TEST_STATUS, reason="Skip 'status' tests requested.") + def test_Home(self): + TestSetup.check_UpStatus(self, method='GET', path='/') + + @pytest.mark.status + @unittest.skipUnless(MAGPIE_TEST_STATUS, reason="Skip 'status' tests requested.") + def test_Login(self): + TestSetup.check_UpStatus(self, method='GET', path='/ui/login') + + @pytest.mark.status + @unittest.skipUnless(MAGPIE_TEST_STATUS, reason="Skip 'status' tests requested.") + def test_ViewUsers(self): + TestSetup.check_UpStatus(self, method='GET', path='/ui/users') + + @pytest.mark.status + @unittest.skipUnless(MAGPIE_TEST_STATUS, reason="Skip 'status' tests requested.") + def test_EditUser(self): + TestSetup.check_UpStatus(self, method='GET', path='/ui/users/anonymous/default') + + @pytest.mark.status + @unittest.skipUnless(MAGPIE_TEST_STATUS, reason="Skip 'status' tests requested.") + def test_EditUserService(self): + TestSetup.check_UpStatus(self, method='GET', path='/ui/users/anonymous/{}'.format(self.test_service_type)) + + @pytest.mark.status + @unittest.skipUnless(MAGPIE_TEST_STATUS, reason="Skip 'status' tests requested.") + def test_ViewGroups(self): + TestSetup.check_UpStatus(self, method='GET', path='/ui/groups') + + @pytest.mark.status + @unittest.skipUnless(MAGPIE_TEST_STATUS, reason="Skip 'status' tests requested.") + def test_EditGroup(self): + TestSetup.check_UpStatus(self, method='GET', path='/ui/groups/anonymous/default') + + @pytest.mark.status + @unittest.skipUnless(MAGPIE_TEST_STATUS, reason="Skip 'status' tests requested.") + def test_EditGroupService(self): + TestSetup.check_UpStatus(self, method='GET', path='/ui/groups/anonymous/{}'.format(self.test_service_type)) + + @pytest.mark.status + @unittest.skipUnless(MAGPIE_TEST_STATUS, reason="Skip 'status' tests requested.") + def test_ViewService(self): + TestSetup.check_UpStatus(self, method='GET', path='/ui/services/default') + + @pytest.mark.status + @unittest.skipUnless(MAGPIE_TEST_STATUS, reason="Skip 'status' tests requested.") + def test_ViewServiceSpecific(self): + TestSetup.check_UpStatus(self, method='GET', path='/ui/services/{}'.format(self.test_service_type)) + + @pytest.mark.status + @unittest.skipUnless(MAGPIE_TEST_STATUS, reason="Skip 'status' tests requested.") + def test_EditService(self): + path = '/ui/services/{type}/{name}'.format(type=self.test_service_type, name=self.test_service_name) + TestSetup.check_UpStatus(self, method='GET', path=path) diff --git a/tests/test_utils.py b/tests/test_utils.py deleted file mode 100644 index 098a1b60f..000000000 --- a/tests/test_utils.py +++ /dev/null @@ -1,56 +0,0 @@ -import os -import json -import pyramid -from webtest import TestApp -from magpie import magpie, db -MAGPIE_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) - - -json_headers = [('Content-Type', 'application/json')] - - -def config_setup_from_ini(config_ini_file_path): - settings = db.get_settings_from_config_ini(config_ini_file_path) - config = pyramid.testing.setUp(settings=settings) - return config - - -def get_test_magpie_app(): - # parse settings from ini file to pass them to the application - magpie_ini = '{}/magpie/magpie.ini'.format(MAGPIE_DIR) - config = config_setup_from_ini(magpie_ini) - # required redefinition because root models' location is not the same from within this test file - config.add_settings({'ziggurat_foundations.model_locations.User': 'magpie.models:User'}) - # scan dependencies - config.include('magpie') - config.scan('magpie') - # create the test application - app = TestApp(magpie.main({}, **config.registry.settings)) - return app - - -def check_or_try_login_user(app, username=None, password=None): - """ - Verifies that the required user is already logged in (or none is if username=None), or tries to login him otherwise. - - :param app: instance of the test application - :param username: name of the user to login or None otherwise - :param password: password to use for login if the user was not already logged in - :return: cookie headers of the user session or None - :raise: Exception on any login/logout failure as required by the caller's specifications (username/password) - """ - resp = app.get('/session', headers=json_headers) - if resp.status_int != 200: - raise Exception('cannot retrieve logged in user information') - auth = resp.json.get('authenticated', False) - user = resp.json.get('user_name', '') - if auth is False and username is None: - return None - if auth is False and username is not None: - data = {'user_name': username, 'password': password, 'provider_name': 'ziggurat'} - resp = app.post_json('/signin', data, headers=json_headers) - if resp.status_int == 200: - return resp.json['headers'] - return None - if auth is True and username != user: - raise Exception("invalid user") diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 000000000..316a55898 --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,431 @@ +import six +import pyramid +import requests +from distutils.version import * +from webtest import TestApp +from webtest.response import TestResponse +import magpie +from magpie import __meta__, db, models, services, magpiectl, MAGPIE_INI_FILE_PATH + + +def config_setup_from_ini(config_ini_file_path): + settings = db.get_settings_from_config_ini(config_ini_file_path) + config = pyramid.testing.setUp(settings=settings) + return config + + +def get_test_magpie_app(): + # parse settings from ini file to pass them to the application + config = config_setup_from_ini(MAGPIE_INI_FILE_PATH) + # required redefinition because root models' location is not the same from within this test file + config.add_settings({'ziggurat_foundations.model_locations.User': 'models:User', + 'ziggurat_foundations.model_locations.user': 'models:User'}) + config.include('ziggurat_foundations.ext.pyramid.sign_in') + # remove API which cause duplicate view errors (?) TODO: figure out why it does so, because it shouldn't + config.registry.settings['magpie.api_generation_disabled'] = True + config.registry.settings['magpie.db_migration_disabled'] = True + # scan dependencies + config.include('magpie') + # create the test application + app = TestApp(magpiectl.main({}, **config.registry.settings)) + return app + + +def get_headers_content_type(app_or_url, content_type): + if isinstance(app_or_url, TestApp): + return [('Content-Type', content_type)] + return {'Content-Type': content_type} + + +def get_response_content_types_list(response): + return [ct.strip() for ct in response.headers['Content-Type'].split(';')] + + +def get_service_types_for_version(version): + available_service_types = set(services.service_type_dict.keys()) + if LooseVersion(version) <= LooseVersion('0.6.1'): + available_service_types = available_service_types - {'access'} + return list(available_service_types) + + +def test_request(app_or_url, method, path, timeout=5, allow_redirects=True, **kwargs): + """ + Calls the request using either a `webtest.TestApp` instance or a `requests` instance from a string URL. + :param app_or_url: `webtest.TestApp` instance of the test application or remote server URL to call with `requests` + :param method: request method (GET, POST, PUT, DELETE) + :param path: test path starting at base path + :return: response of the request + """ + method = method.upper() + + # obtain json body from any json/data/body/params kw and empty {} if not specified + # reapply with the expected webtest/requests method kw afterward + json_body = None + for kw in ['json', 'data', 'body', 'params']: + json_body = kwargs.get(kw, json_body) + if kw in kwargs: + kwargs.pop(kw) + json_body = json_body or {} + + if isinstance(app_or_url, TestApp): + # remove any 'cookies' keyword handled by the 'TestApp' instance + if 'cookies' in kwargs: + cookies = kwargs.pop('cookies') + if cookies and not app_or_url.cookies: + app_or_url.cookies.update(cookies) + + kwargs['params'] = json_body + if method == 'GET': + return app_or_url.get(path, **kwargs) + elif method == 'POST': + return app_or_url.post_json(path, **kwargs) + elif method == 'PUT': + return app_or_url.put_json(path, **kwargs) + elif method == 'DELETE': + return app_or_url.delete_json(path, **kwargs) + else: + kwargs['json'] = json_body + url = '{url}{path}'.format(url=app_or_url, path=path) + return requests.request(method, url, timeout=timeout, allow_redirects=allow_redirects, **kwargs) + + +def check_or_try_login_user(app_or_url, username=None, password=None, headers=None): + """ + Verifies that the required user is already logged in (or none is if username=None), or tries to login him otherwise. + + :param app_or_url: `webtest.TestApp` instance of the test application or remote server URL to call with `requests` + :param username: name of the user to login or None otherwise + :param password: password to use for login if the user was not already logged in + :param headers: headers to include in the test request + :return: headers and cookies of the user session or (None, None) + :raise: Exception on any login/logout failure as required by the caller's specifications (username/password) + """ + + headers = headers or {} + + if isinstance(app_or_url, TestApp): + resp = app_or_url.get('/session', headers=headers) + body = resp.json + else: + resp = requests.get('{}/session'.format(app_or_url), headers=headers) + body = resp.json() + + if resp.status_code != 200: + raise Exception('cannot retrieve logged in user information') + + auth = body.get('authenticated', False) + user = body.get('user_name', '') + if auth is False and username is None: + return None, None + if auth is False and username is not None: + data = {'user_name': username, 'password': password, 'provider_name': 'ziggurat'} + + if isinstance(app_or_url, TestApp): + resp = app_or_url.post_json('/signin', data, headers=headers) + else: + resp = requests.post('{}/signin'.format(app_or_url), json=data, headers=headers) + + if resp.status_code == 200: + return resp.headers, resp.cookies + return None, None + + if auth is True and username != user: + raise Exception("invalid user") + + +def format_test_val_ref(val, ref, pre='Fail'): + return '({0}) Test value: `{1}`, Reference value: `{2}`'.format(pre, val, ref) + + +def all_equal(iter_val, iter_ref, any_order=False): + if not (hasattr(iter_val, '__iter__') and hasattr(iter_ref, '__iter__')): + return False + if len(iter_val) != len(iter_ref): + return False + if any_order: + return all([it in iter_ref for it in iter_val]) + return all(it == ir for it, ir in zip(iter_val, iter_ref)) + + +def check_all_equal(iter_val, iter_ref, any_order=False, msg=None): + r_it_val = repr(iter_val) + r_it_ref = repr(iter_ref) + assert all_equal(iter_val, iter_ref, any_order), msg or format_test_val_ref(r_it_val, r_it_ref, pre='Equal Fail') + + +def check_val_equal(val, ref, msg=None): + assert isinstance(ref, null) or val == ref, msg or format_test_val_ref(val, ref, pre='Equal Fail') + + +def check_val_not_equal(val, ref, msg=None): + assert isinstance(ref, null) or val != ref, msg or format_test_val_ref(val, ref, pre='Equal Fail') + + +def check_val_is_in(val, ref, msg=None): + assert isinstance(ref, null) or val in ref, msg or format_test_val_ref(val, ref, pre='Is In Fail') + + +def check_val_not_in(val, ref, msg=None): + assert isinstance(ref, null) or val not in ref, msg or format_test_val_ref(val, ref, pre='Not In Fail') + + +def check_val_type(val, ref, msg=None): + assert isinstance(val, ref), msg or format_test_val_ref(val, repr(ref), pre='Type Fail') + + +def check_response_basic_info(response, expected_code=200): + """ + Validates basic Magpie API response metadata. + :param response: response to validate. + :param expected_code: status code to validate from the response. + :return: json body of the response for convenience. + """ + if isinstance(response, TestResponse): + json_body = response.json + else: + json_body = response.json() + content_types = get_response_content_types_list(response) + check_val_is_in('application/json', content_types) + check_val_equal(response.status_code, expected_code) + check_val_equal(json_body['code'], expected_code) + check_val_equal(json_body['type'], 'application/json') + assert json_body['detail'] != '' + return json_body + + +class null(object): + """ Represents a null value to differentiate from None. """ + def __repr__(self): + return '' + + +Null = null() + + +def check_error_param_structure(json_body, paramValue=Null, paramName=Null, paramCompare=Null, + isParamValueLiteralUnicode=False, paramCompareExists=False, version=None): + """ + Validates error response 'param' information based on different Magpie version formats. + :param json_body: json body of the response to validate. + :param paramValue: expected 'value' of param, not verified if + :param paramName: expected 'name' of param, not verified if or non existing for Magpie version + :param paramCompare: expected 'compare'/'paramCompare' value, not verified if + :param isParamValueLiteralUnicode: param value is represented as `u'{paramValue}'` for older Magpie version + :param paramCompareExists: verify that 'compare'/'paramCompare' is in the body, not necessarily validating the value + :param version: version of application/remote server to use for format validation, use local Magpie version if None + :raise failing condition + """ + check_val_type(json_body, dict) + check_val_is_in('param', json_body) + version = version or __meta__.__version__ + if LooseVersion(version) >= LooseVersion('0.6.3'): + check_val_type(json_body['param'], dict) + check_val_is_in('value', json_body['param']) + check_val_is_in('name', json_body['param']) + check_val_equal(json_body['param']['name'], paramName) + check_val_equal(json_body['param']['value'], paramValue) + if paramCompareExists: + check_val_is_in('compare', json_body['param']) + check_val_equal(json_body['param']['compare'], paramCompare) + else: + # unicode representation was explicitly returned in value only when of string type + if isParamValueLiteralUnicode and isinstance(paramValue, six.string_types): + paramValue = u'u\'{}\''.format(paramValue) + check_val_equal(json_body['param'], paramValue) + if paramCompareExists: + check_val_is_in('paramCompare', json_body) + check_val_equal(json_body['paramCompare'], paramCompare) + + +def check_post_resource_structure(json_body, resource_name, resource_type, version=None): + """ + Validates POST /resource response information based on different Magpie version formats. + :param json_body: json body of the response to validate. + :param resource_name: name of the resource to validate. + :param resource_type: type of the resource to validate. + :param version: version of application/remote server to use for format validation, use local Magpie version if None. + :raise failing condition + """ + version = version or __meta__.__version__ + if LooseVersion(version) >= LooseVersion('0.6.3'): + check_val_is_in('resource', json_body) + check_val_type(json_body['resource'], dict) + check_val_is_in('resource_name', json_body['resource']) + check_val_is_in('resource_type', json_body['resource']) + check_val_is_in('resource_id', json_body['resource']) + check_val_equal(json_body['resource']['resource_name'], resource_name) + check_val_equal(json_body['resource']['resource_name'], resource_type) + check_val_type(json_body['resource']['resource_id'], int) + else: + check_val_is_in('resource_name', json_body) + check_val_is_in('resource_type', json_body) + check_val_is_in('resource_id', json_body) + check_val_equal(json_body['resource_name'], resource_name) + check_val_equal(json_body['resource_type'], resource_type) + check_val_type(json_body['resource_id'], int) + + +def check_resource_children(resource_dict, parent_resource_id, root_service_id): + """ + Crawls through a resource-children tree to validate data field, types and corresponding values. + :param resource_dict: top-level 'resources' dictionary possibly containing children resources. + :param parent_resource_id: top-level resource/service id (int) + :param root_service_id: top-level service id (int) + :raise any invalid match on expected data field, type or value + """ + assert isinstance(resource_dict, dict) + + for resource_id in resource_dict: + check_val_type(resource_id, six.string_types) + resource_int_id = int(resource_id) # should by an 'int' string, no error raised + resource_info = resource_dict[resource_id] + check_val_is_in('root_service_id', resource_info) + check_val_type(resource_info['root_service_id'], int) + check_val_equal(resource_info['root_service_id'], root_service_id) + check_val_is_in('resource_id', resource_info) + check_val_type(resource_info['resource_id'], int) + check_val_equal(resource_info['resource_id'], resource_int_id) + check_val_is_in('parent_id', resource_info) + check_val_type(resource_info['parent_id'], int) + check_val_equal(resource_info['parent_id'], parent_resource_id) + check_val_is_in('resource_name', resource_info) + check_val_type(resource_info['resource_name'], six.string_types) + check_val_is_in('permission_names', resource_info) + check_val_type(resource_info['permission_names'], list) + check_val_is_in('children', resource_info) + check_resource_children(resource_info['children'], resource_int_id, root_service_id) + + +# Generic setup and validation methods across unittests +class TestSetup(object): + @staticmethod + def get_Version(test_class): + resp = test_request(test_class.url, 'GET', '/version', + headers=test_class.json_headers, cookies=test_class.cookies) + json_body = check_response_basic_info(resp, 200) + return json_body['version'] + + @staticmethod + def check_UpStatus(test_class, method, path): + """ + Verifies that the Magpie UI page at very least returned an Ok response with the displayed title. + Validates that at the bare minimum, no underlying internal error occurred from the API or UI calls. + """ + resp = test_request(test_class.url, method, path, cookies=test_class.cookies) + check_val_equal(resp.status_code, 200) + check_val_is_in('Content-Type', resp.headers) + check_val_is_in('text/html', get_response_content_types_list(resp)) + check_val_is_in("Magpie Administration", resp.text) + + @staticmethod + def get_AnyServiceOfTestServiceType(test_class): + route = '/services/types/{}'.format(test_class.test_service_type) + resp = test_request(test_class.url, 'GET', route, headers=test_class.json_headers, cookies=test_class.cookies) + check_val_equal(resp.status_code, 200) + json_body = resp.json() + check_val_is_in('services', json_body) + check_val_is_in(test_class.test_service_type, json_body['services']) + check_val_not_equal(len(json_body['services'][test_class.test_service_type]), 0, + msg="Missing any required service of type: `{}`".format(test_class.test_service_type)) + services = json_body['services'][test_class.test_service_type] + return services[services.keys()[0]] + + @staticmethod + def create_TestServiceResource(test_class, data_override=None): + route = '/services/{svc}/resources'.format(svc=test_class.test_service_name) + data = { + "resource_name": test_class.test_resource_name, + "resource_type": test_class.test_resource_type, + } + if data_override: + data.update(data_override) + resp = test_request(test_class.url, 'POST', route, + headers=test_class.json_headers, + cookies=test_class.cookies, json=data) + return check_response_basic_info(resp, 201) + + @staticmethod + def get_ExistingTestServiceInfo(test_class): + route = '/services/{svc}'.format(svc=test_class.test_service_name) + resp = test_request(test_class.url, 'GET', route, headers=test_class.json_headers, cookies=test_class.cookies) + return resp.json()[test_class.test_service_name] + + @staticmethod + def get_ExistingTestServiceDirectResources(test_class): + route = '/services/{svc}/resources'.format(svc=test_class.test_service_name) + resp = test_request(test_class.url, 'GET', route, headers=test_class.json_headers, cookies=test_class.cookies) + json_body = resp.json() + resources = json_body[test_class.test_service_name]['resources'] + return [resources[res] for res in resources] + + @staticmethod + def check_NonExistingTestResource(test_class): + resources = TestSetup.get_ExistingTestServiceDirectResources(test_class) + resources_names = [res['resource_name'] for res in resources] + check_val_not_in(test_class.test_resource_name, resources_names) + + @staticmethod + def delete_TestServiceResource(test_class): + resources = TestSetup.get_ExistingTestServiceDirectResources(test_class) + test_resource = filter(lambda r: r['resource_name'] == test_class.test_resource_name, resources) + # delete as required, skip if non-existing + if len(test_resource) > 0: + resource_id = test_resource[0]['resource_id'] + route = '/services/{svc}/resources/{res_id}'.format(svc=test_class.test_service_name, res_id=resource_id) + resp = test_request(test_class.url, 'DELETE', route, + headers=test_class.json_headers, + cookies=test_class.cookies) + check_val_equal(resp.status_code, 200) + TestSetup.check_NonExistingTestResource(test_class) + + @staticmethod + def get_RegisteredServicesList(test_class): + resp = test_request(test_class.url, 'GET', '/services', + headers=test_class.json_headers, + cookies=test_class.cookies) + json_body = check_response_basic_info(resp, 200) + + # prepare a flat list of registered services + services_list = list() + for svc_type in json_body['services']: + services_of_type = json_body['services'][svc_type] + services_list.extend(services_of_type.values()) + return services_list + + @staticmethod + def get_RegisteredUsersList(test_class): + resp = test_request(test_class.url, 'GET', '/users', + headers=test_class.json_headers, + cookies=test_class.cookies) + json_body = check_response_basic_info(resp, 200) + return json_body['user_names'] + + @staticmethod + def check_NonExistingTestUser(test_class): + users = TestSetup.get_RegisteredUsersList(test_class) + check_val_not_in(test_class.test_user_name, users) + + @staticmethod + def create_TestUser(test_class): + data = { + "user_name": test_class.test_user_name, + "email": '{}@mail.com'.format(test_class.test_user_name), + "password": test_class.test_user_name, + "group_name": test_class.test_user_group, + } + resp = test_request(test_class.url, 'POST', '/users', + headers=test_class.json_headers, + cookies=test_class.cookies, json=data) + return check_response_basic_info(resp, 201) + + @staticmethod + def delete_TestUser(test_class): + users = TestSetup.get_RegisteredUsersList(test_class) + # delete as required, skip if non-existing + if test_class.test_user_name in users: + route = '/users/{usr}'.format(usr=test_class.test_user_name) + resp = test_request(test_class.url, 'DELETE', route, + headers=test_class.json_headers, + cookies=test_class.cookies) + check_val_equal(resp.status_code, 200) + TestSetup.check_NonExistingTestUser(test_class) diff --git a/travis_pypi_setup.py b/travis_pypi_setup.py index d2cc617b7..092de8c60 100644 --- a/travis_pypi_setup.py +++ b/travis_pypi_setup.py @@ -13,12 +13,7 @@ from cryptography.hazmat.primitives.serialization import load_pem_public_key from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 - - -try: - from urllib import urlopen -except: - from urllib.request import urlopen +from six.moves.urllib.request import urlopen GITHUB_REPO = 'fderue/magpie'