diff --git a/app.py b/app.py index 0ee5dd845..3870e9b93 100644 --- a/app.py +++ b/app.py @@ -7,7 +7,8 @@ from flask.ext.principal import Principal from flask.ext.login import LoginManager from flask.ext.mail import Mail -from config import ProductionConfig, DebugConfig, LocalHostedConfig +from config import (ProductionConfig, DebugConfig, LocalHostedConfig, + TestConfig) from util import analytics @@ -22,6 +23,9 @@ if stack.startswith('prod'): elif stack.startswith('localhosted'): logger.info('Running with debug config on production data.') config = LocalHostedConfig() +elif stack.startswith('test'): + logger.info('Running with test config on ephemeral data.') + config = TestConfig() else: logger.info('Running with debug config.') config = DebugConfig() @@ -36,6 +40,6 @@ login_manager.init_app(app) mail = Mail() mail.init_app(app) -stripe.api_key = app.config['STRIPE_SECRET_KEY'] +stripe.api_key = app.config.get('STRIPE_SECRET_KEY', None) -mixpanel = analytics.init_app(app) +mixpanel = app.config['ANALYTICS'].init_app(app) diff --git a/auth/permissions.py b/auth/permissions.py index c0b98f874..0e1655337 100644 --- a/auth/permissions.py +++ b/auth/permissions.py @@ -65,7 +65,8 @@ class ModifyRepositoryPermission(Permission): admin_need = _RepositoryNeed(namespace, name, 'admin') write_need = _RepositoryNeed(namespace, name, 'write') org_admin_need = _OrganizationNeed(namespace, 'admin') - super(ModifyRepositoryPermission, self).__init__(admin_need, write_need) + super(ModifyRepositoryPermission, self).__init__(admin_need, write_need, + org_admin_need) class ReadRepositoryPermission(Permission): diff --git a/config.py b/config.py index de81bdfe6..8616f47d4 100644 --- a/config.py +++ b/config.py @@ -2,6 +2,13 @@ import logging import sys from peewee import MySQLDatabase, SqliteDatabase +from storage.s3 import S3Storage +from storage.local import LocalStorage +from data.userfiles import UserRequestFiles +from util import analytics + +from test.teststorage import FakeStorage, FakeUserfiles +from test import analytics as fake_analytics LOG_FORMAT = '%(asctime)-15s - %(levelname)s - %(pathname)s - ' + \ @@ -31,6 +38,12 @@ class SQLiteDB(object): DB_DRIVER = SqliteDatabase +class EphemeralDB(object): + DB_NAME = ':memory:' + DB_CONNECTION_ARGS = {} + DB_DRIVER = SqliteDatabase + + class RDSMySQL(object): DB_NAME = 'quay' DB_CONNECTION_ARGS = { @@ -49,12 +62,27 @@ class AWSCredentials(object): class S3Storage(AWSCredentials): - STORAGE_KIND = 's3' + STORAGE = S3Storage('', AWSCredentials.AWS_ACCESS_KEY, + AWSCredentials.AWS_SECRET_KEY, + AWSCredentials.REGISTRY_S3_BUCKET) class LocalStorage(object): - STORAGE_KIND = 'local' - LOCAL_STORAGE_DIR = 'test/data/registry' + STORAGE = LocalStorage('test/data/registry') + + +class FakeStorage(object): + STORAGE = FakeStorage() + + +class FakeUserfiles(object): + USERFILES = FakeUserfiles() + + +class S3Userfiles(AWSCredentials): + USERFILES = UserRequestFiles(AWSCredentials.AWS_ACCESS_KEY, + AWSCredentials.AWS_SECRET_KEY, + AWSCredentials.REGISTRY_S3_BUCKET) class StripeTestConfig(object): @@ -67,11 +95,16 @@ class StripeLiveConfig(object): STRIPE_PUBLISHABLE_KEY = 'pk_live_P5wLU0vGdHnZGyKnXlFG4oiu' +class FakeAnalytics(object): + ANALYTICS = fake_analytics + + class MixpanelTestConfig(object): + ANALYTICS = analytics MIXPANEL_KEY = '38014a0f27e7bdc3ff8cc7cc29c869f9' -class MixpanelProdConfig(object): +class MixpanelProdConfig(MixpanelTestConfig): MIXPANEL_KEY = '50ff2b2569faa3a51c8f5724922ffb7e' @@ -100,9 +133,20 @@ class BuildNodeConfig(object): BUILD_NODE_PULL_TOKEN = 'F02O2E86CQLKZUQ0O81J8XDHQ6F0N1V36L9JTOEEK6GKKMT1GI8PTJQT4OU88Y6G' +class TestConfig(FlaskConfig, FakeStorage, EphemeralDB, FakeUserfiles, + FakeAnalytics): + LOGGING_CONFIG = { + 'level': logging.DEBUG, + 'format': LOG_FORMAT + } + POPULATE_DB_TEST_DATA = True + TESTING = True + INCLUDE_TEST_ENDPOINTS = True + + class DebugConfig(FlaskConfig, MailConfig, LocalStorage, SQLiteDB, StripeTestConfig, MixpanelTestConfig, GitHubTestConfig, - DigitalOceanConfig, AWSCredentials, BuildNodeConfig): + DigitalOceanConfig, BuildNodeConfig, S3Userfiles): LOGGING_CONFIG = { 'level': logging.DEBUG, 'format': LOG_FORMAT @@ -115,7 +159,7 @@ class DebugConfig(FlaskConfig, MailConfig, LocalStorage, SQLiteDB, class LocalHostedConfig(FlaskConfig, MailConfig, S3Storage, RDSMySQL, StripeLiveConfig, MixpanelTestConfig, GitHubProdConfig, DigitalOceanConfig, - BuildNodeConfig): + BuildNodeConfig, S3Userfiles): LOGGING_CONFIG = { 'level': logging.DEBUG, 'format': LOG_FORMAT @@ -125,7 +169,8 @@ class LocalHostedConfig(FlaskConfig, MailConfig, S3Storage, RDSMySQL, class ProductionConfig(FlaskConfig, MailConfig, S3Storage, RDSMySQL, StripeLiveConfig, MixpanelProdConfig, - GitHubProdConfig, DigitalOceanConfig, BuildNodeConfig): + GitHubProdConfig, DigitalOceanConfig, BuildNodeConfig, + S3Userfiles): LOGGING_CONFIG = { 'stream': sys.stderr, 'level': logging.DEBUG, diff --git a/data/model.py b/data/model.py index cf0621463..05d9ff681 100644 --- a/data/model.py +++ b/data/model.py @@ -149,7 +149,11 @@ def remove_team(org_name, team_name, removed_by_username): def add_user_to_team(user, team): - return TeamMember.create(user=user, team=team) + try: + return TeamMember.create(user=user, team=team) + except Exception: + raise DataModelException('Unable to add user \'%s\' to team: \'%s\'' % + (user.username, team.name)) def remove_user_from_team(org_name, team_name, username, removed_by_username): @@ -164,7 +168,7 @@ def remove_user_from_team(org_name, team_name, username, removed_by_username): if not found: raise DataModelException('User %s does not belong to team %s' % - (username, teamname)) + (username, team_name)) if username == removed_by_username: admin_team_query = __get_user_admin_teams(org_name, team_name, username) @@ -722,15 +726,13 @@ def delete_team_permission(team_name, namespace_name, repository_name): fetched[0].delete_instance() -def __set_entity_repo_permission(entity_id, entity_table, entity_id_property, - permission_entity_property, namespace_name, - repository_name, role_name): - entity = entity_table.get(entity_id_property == entity_id) +def __set_entity_repo_permission(entity, permission_entity_property, + namespace_name, repository_name, role_name): repo = Repository.get(Repository.name == repository_name, Repository.namespace == namespace_name) new_role = Role.get(Role.name == role_name) - # Fetch any existing permission for this user on the repo + # Fetch any existing permission for this entity on the repo try: entity_attr = getattr(RepositoryPermission, permission_entity_property) perm = RepositoryPermission.get(entity_attr == entity, @@ -750,16 +752,21 @@ def set_user_repo_permission(username, namespace_name, repository_name, if username == namespace_name: raise DataModelException('Namespace owner must always be admin.') - return __set_entity_repo_permission(username, User, User.username, 'user', - namespace_name, repository_name, - role_name) + user = User.get(User.username == username) + return __set_entity_repo_permission(user, 'user', namespace_name, + repository_name, role_name) def set_team_repo_permission(team_name, namespace_name, repository_name, role_name): - return __set_entity_repo_permission(team_name, Team, Team.name, 'team', - namespace_name, repository_name, - role_name) + team = list(Team.select().join(User).where(Team.name == team_name, + User.username == namespace_name)) + if not team: + raise DataModelException('No team \'%s\' in organization \'%s\'.' % + (team_name, namespace_name)) + + return __set_entity_repo_permission(team[0], 'team', namespace_name, + repository_name, role_name) def purge_repository(namespace_name, repository_name): diff --git a/endpoints/api.py b/endpoints/api.py index 897e56b85..f2b5874c2 100644 --- a/endpoints/api.py +++ b/endpoints/api.py @@ -11,10 +11,7 @@ from flask.ext.principal import identity_changed, AnonymousIdentity from functools import wraps from collections import defaultdict -import storage - from data import model -from data.userfiles import UserRequestFiles from data.queue import dockerfile_build_queue from data.plans import USER_PLANS, BUSINESS_PLANS, get_plan from app import app @@ -33,7 +30,8 @@ from endpoints.web import common_login from util.cache import cache_control -store = storage.load() +store = app.config['STORAGE'] +user_files = app.config['USERFILES'] logger = logging.getLogger(__name__) @@ -46,24 +44,16 @@ def api_login_required(f): return decorated_view -def required_json_args(*required_args): - def wrap(f): - @wraps(f) - def wrapped(*args, **kwargs): - request_data = request.get_json() - for arg in required_args: - if arg not in request_data: - abort(400) - return f(*args, **kwargs) - return wrapped - return wrap - - @app.errorhandler(model.DataModelException) def handle_dme(ex): return make_response(ex.message, 400) +@app.errorhandler(KeyError) +def handle_dme(ex): + return make_response(ex.message, 400) + + @app.route('/api/') def welcome(): return make_response('welcome', 200) @@ -135,7 +125,6 @@ def change_user_details(): @app.route('/api/user/', methods=['POST']) -@required_json_args('username', 'password', 'email') def create_user_api(): user_data = request.get_json() @@ -162,7 +151,6 @@ def create_user_api(): @app.route('/api/signin', methods=['POST']) -@required_json_args('username', 'password') def signin_api(): signin_data = request.get_json() @@ -202,7 +190,6 @@ def logout(): @app.route("/api/recovery", methods=['POST']) -@required_json_args('email') def send_recovery(): email = request.get_json()['email'] code = model.create_reset_password_email_code(email) @@ -265,11 +252,6 @@ def get_matching_entities(prefix): }) -user_files = UserRequestFiles(app.config['AWS_ACCESS_KEY'], - app.config['AWS_SECRET_KEY'], - app.config['REGISTRY_S3_BUCKET']) - - def team_view(orgname, t): view_permission = ViewTeamPermission(orgname, t.name) role = model.get_team_org_role(t).name @@ -526,7 +508,7 @@ def create_repo_api(): if existing: return make_response('Repository already exists', 400) - visibility = request.get_json()['visibility'] + visibility = json['visibility'] repo = model.create_repository(namespace_name, repository_name, owner, visibility) @@ -621,7 +603,7 @@ def update_repo_api(namespace, repository): 'success': True }) - abort(404) + abort(403) @app.route('/api/repository//changevisibility', @@ -750,7 +732,6 @@ def get_repo_builds(namespace, repository): @app.route('/api/filedrop/', methods=['POST']) @api_login_required -@required_json_args('mimeType') def get_filedrop_url(): mime_type = request.get_json()['mimeType'] (url, file_id) = user_files.prepare_for_drop(mime_type) @@ -778,9 +759,11 @@ def request_repo_build(namespace, repository): tag) dockerfile_build_queue.put(json.dumps({'build_id': build_request.id})) - return jsonify({ + resp = jsonify({ 'started': True }) + resp.status_code = 201 + return resp abort(403) # Permissions denied @@ -961,7 +944,7 @@ def get_team_permissions(namespace, repository, teamname): (namespace, repository, teamname)) permission = AdministerRepositoryPermission(namespace, repository) if permission.can(): - perm = model.get_team_reponame_permission(username, namespace, repository) + perm = model.get_team_reponame_permission(teamname, namespace, repository) return jsonify(role_view(perm)) abort(403) # Permission denied @@ -979,13 +962,8 @@ def change_user_permissions(namespace, repository, username): logger.debug('Setting permission to: %s for user %s' % (new_permission['role'], username)) - try: - perm = model.set_user_repo_permission(username, namespace, repository, - new_permission['role']) - except model.DataModelException: - logger.warning('User tried to remove themselves as admin.') - abort(409) - + perm = model.set_user_repo_permission(username, namespace, repository, + new_permission['role']) perm_view = role_view(perm) try: @@ -1017,12 +995,8 @@ def change_team_permissions(namespace, repository, teamname): logger.debug('Setting permission to: %s for team %s' % (new_permission['role'], teamname)) - try: - perm = model.set_team_repo_permission(teamname, namespace, repository, - new_permission['role']) - except model.DataModelException: - logger.warning('User tried to remove themselves as admin.') - abort(409) + perm = model.set_team_repo_permission(teamname, namespace, repository, + new_permission['role']) resp = jsonify(role_view(perm)) if request.method == 'POST': @@ -1039,12 +1013,7 @@ def change_team_permissions(namespace, repository, teamname): def delete_user_permissions(namespace, repository, username): permission = AdministerRepositoryPermission(namespace, repository) if permission.can(): - try: - model.delete_user_permission(username, namespace, repository) - except model.DataModelException: - logger.warning('User tried to remove themselves as admin.') - abort(409) - + model.delete_user_permission(username, namespace, repository) return make_response('Deleted', 204) abort(403) # Permission denied @@ -1057,12 +1026,7 @@ def delete_user_permissions(namespace, repository, username): def delete_team_permissions(namespace, repository, teamname): permission = AdministerRepositoryPermission(namespace, repository) if permission.can(): - try: - model.delete_team_permission(teamname, namespace, repository) - except model.DataModelException: - logger.warning('User tried to remove themselves as admin.') - abort(409) - + model.delete_team_permission(teamname, namespace, repository) return make_response('Deleted', 204) abort(403) # Permission denied diff --git a/endpoints/registry.py b/endpoints/registry.py index b736dc95e..5d71a1f81 100644 --- a/endpoints/registry.py +++ b/endpoints/registry.py @@ -6,7 +6,6 @@ from functools import wraps from datetime import datetime from time import time -import storage from data.queue import image_diff_queue from app import app @@ -17,7 +16,7 @@ from auth.permissions import (ReadRepositoryPermission, from data import model -store = storage.load() +store = app.config['STORAGE'] logger = logging.getLogger(__name__) diff --git a/endpoints/tags.py b/endpoints/tags.py index 2607bbde2..c9b2af2ae 100644 --- a/endpoints/tags.py +++ b/endpoints/tags.py @@ -4,8 +4,6 @@ import json from flask import abort, request, jsonify, make_response -import storage - from app import app from util.names import parse_repository_name from auth.auth import process_auth @@ -14,7 +12,6 @@ from auth.permissions import (ReadRepositoryPermission, from data import model -store = storage.load() logger = logging.getLogger(__name__) diff --git a/initdb.py b/initdb.py index d80852f25..9dd2c8be9 100644 --- a/initdb.py +++ b/initdb.py @@ -8,15 +8,13 @@ from datetime import datetime, timedelta from flask import url_for from peewee import SqliteDatabase, create_model_tables, drop_model_tables -import storage - from data.database import * from data import model from app import app logger = logging.getLogger(__name__) -store = storage.load() +store = app.config['STORAGE'] SAMPLE_DIFFS = ['test/data/sample/diffs/diffs%s.json' % i for i in range(1, 10)] @@ -141,6 +139,10 @@ def populate_database(): new_user_3.verified = True new_user_3.save() + reader = model.create_user('reader', 'password', 'no1@thanks.com') + reader.verified = True + reader.save() + __generate_repository(new_user_1, 'simple', 'Simple repository.', False, [], (4, [], ['latest', 'prod'])) @@ -168,7 +170,8 @@ def populate_database(): __generate_repository(new_user_1, 'shared', 'Shared repository, another user can write.', False, - [(new_user_2, 'write')], (5, [], 'latest')) + [(new_user_2, 'write'), (reader, 'read')], + (5, [], 'latest')) building = __generate_repository(new_user_1, 'building', 'Empty repository which is building.', @@ -186,6 +189,7 @@ def populate_database(): model.set_team_repo_permission(reader_team.name, org_repo.namespace, org_repo.name, 'read') model.add_user_to_team(new_user_2, reader_team) + model.add_user_to_team(reader, reader_team) token = model.create_access_token(building, 'write') tag = 'ci.devtable.com:5000/%s/%s' % (building.namespace, building.name) diff --git a/storage/__init__.py b/storage/__init__.py index de86f4be4..e69de29bb 100644 --- a/storage/__init__.py +++ b/storage/__init__.py @@ -1,147 +0,0 @@ -import logging - -import contextlib -import tempfile - -from app import app - - -__all__ = ['load'] -logger = logging.getLogger(__name__) - - -class Storage(object): - - """Storage is organized as follow: - $ROOT/images//json - $ROOT/images//layer - $ROOT/repositories/// - """ - - # Useful if we want to change those locations later without rewriting - # the code which uses Storage - repositories = 'repositories' - images = 'images' - # Set the IO buffer to 64kB - buffer_size = 64 * 1024 - - #FIXME(samalba): Move all path resolver in each module (out of the base) - def images_list_path(self, namespace, repository): - return '{0}/{1}/{2}/_images_list'.format(self.repositories, - namespace, - repository) - - def image_json_path(self, namespace, repository, image_id): - return '{0}/{1}/{2}/{3}/json'.format(self.images, namespace, - repository, image_id) - - def image_mark_path(self, namespace, repository, image_id): - return '{0}/{1}/{2}/{3}/_inprogress'.format(self.images, namespace, - repository, image_id) - - def image_checksum_path(self, namespace, repository, image_id): - return '{0}/{1}/{2}/{3}/_checksum'.format(self.images, namespace, - repository, image_id) - - def image_layer_path(self, namespace, repository, image_id): - return '{0}/{1}/{2}/{3}/layer'.format(self.images, namespace, - repository, image_id) - - def image_ancestry_path(self, namespace, repository, image_id): - return '{0}/{1}/{2}/{3}/ancestry'.format(self.images, namespace, - repository, image_id) - - def repository_namespace_path(self, namespace, repository): - return '{0}/{1}/{2}/'.format(self.images, namespace, repository) - - def image_file_trie_path(self, namespace, repository, image_id): - return '{0}/{1}/{2}/{3}/files.trie'.format(self.images, namespace, - repository, image_id) - - def image_file_diffs_path(self, namespace, repository, image_id): - return '{0}/{1}/{2}/{3}/diffs.json'.format(self.images, namespace, - repository, image_id) - - def get_content(self, path): - raise NotImplementedError - - def put_content(self, path, content): - raise NotImplementedError - - def stream_read(self, path): - raise NotImplementedError - - def stream_read_file(self, path): - raise NotImplementedError - - def stream_write(self, path, fp): - raise NotImplementedError - - def list_directory(self, path=None): - raise NotImplementedError - - def exists(self, path): - raise NotImplementedError - - def remove(self, path): - raise NotImplementedError - - def get_size(self, path): - raise NotImplementedError - - -@contextlib.contextmanager -def store_stream(stream): - """Stores the entire stream to a temporary file.""" - tmpf = tempfile.TemporaryFile() - while True: - try: - buf = stream.read(4096) - if not buf: - break - tmpf.write(buf) - except IOError: - break - tmpf.seek(0) - yield tmpf - tmpf.close() - - -def temp_store_handler(): - tmpf = tempfile.TemporaryFile() - - def fn(buf): - try: - tmpf.write(buf) - except IOError: - pass - - return tmpf, fn - - -from local import LocalStorage -from s3 import S3Storage - - -_storage = {} - - -def load(kind=None): - """Returns the right storage class according to the configuration.""" - global _storage - - kind = app.config['STORAGE_KIND'] - if kind in _storage: - return _storage[kind] - if kind == 's3': - logger.debug('Using s3 storage.') - store = S3Storage('', app.config['AWS_ACCESS_KEY'], - app.config['AWS_SECRET_KEY'], - app.config['REGISTRY_S3_BUCKET']) - elif kind == 'local': - logger.debug('Using local storage.') - store = LocalStorage(app.config['LOCAL_STORAGE_DIR']) - else: - raise ValueError('Not supported storage \'{0}\''.format(kind)) - _storage[kind] = store - return store diff --git a/storage/basestorage.py b/storage/basestorage.py new file mode 100644 index 000000000..283fde227 --- /dev/null +++ b/storage/basestorage.py @@ -0,0 +1,78 @@ +class Storage(object): + + """Storage is organized as follow: + $ROOT/images//json + $ROOT/images//layer + $ROOT/repositories/// + """ + + # Useful if we want to change those locations later without rewriting + # the code which uses Storage + repositories = 'repositories' + images = 'images' + # Set the IO buffer to 64kB + buffer_size = 64 * 1024 + + #FIXME(samalba): Move all path resolver in each module (out of the base) + def images_list_path(self, namespace, repository): + return '{0}/{1}/{2}/_images_list'.format(self.repositories, + namespace, + repository) + + def image_json_path(self, namespace, repository, image_id): + return '{0}/{1}/{2}/{3}/json'.format(self.images, namespace, + repository, image_id) + + def image_mark_path(self, namespace, repository, image_id): + return '{0}/{1}/{2}/{3}/_inprogress'.format(self.images, namespace, + repository, image_id) + + def image_checksum_path(self, namespace, repository, image_id): + return '{0}/{1}/{2}/{3}/_checksum'.format(self.images, namespace, + repository, image_id) + + def image_layer_path(self, namespace, repository, image_id): + return '{0}/{1}/{2}/{3}/layer'.format(self.images, namespace, + repository, image_id) + + def image_ancestry_path(self, namespace, repository, image_id): + return '{0}/{1}/{2}/{3}/ancestry'.format(self.images, namespace, + repository, image_id) + + def repository_namespace_path(self, namespace, repository): + return '{0}/{1}/{2}/'.format(self.images, namespace, repository) + + def image_file_trie_path(self, namespace, repository, image_id): + return '{0}/{1}/{2}/{3}/files.trie'.format(self.images, namespace, + repository, image_id) + + def image_file_diffs_path(self, namespace, repository, image_id): + return '{0}/{1}/{2}/{3}/diffs.json'.format(self.images, namespace, + repository, image_id) + + def get_content(self, path): + raise NotImplementedError + + def put_content(self, path, content): + raise NotImplementedError + + def stream_read(self, path): + raise NotImplementedError + + def stream_read_file(self, path): + raise NotImplementedError + + def stream_write(self, path, fp): + raise NotImplementedError + + def list_directory(self, path=None): + raise NotImplementedError + + def exists(self, path): + raise NotImplementedError + + def remove(self, path): + raise NotImplementedError + + def get_size(self, path): + raise NotImplementedError diff --git a/storage/local.py b/storage/local.py index 93b2103ca..84bc29260 100644 --- a/storage/local.py +++ b/storage/local.py @@ -2,7 +2,7 @@ import os import shutil -from . import Storage +from basestorage import Storage class LocalStorage(Storage): diff --git a/storage/s3.py b/storage/s3.py index cac2fc227..10e7bb50d 100644 --- a/storage/s3.py +++ b/storage/s3.py @@ -5,7 +5,7 @@ import logging import boto.s3.connection import boto.s3.key -from . import Storage +from basestorage import Storage logger = logging.getLogger(__name__) diff --git a/test/analytics.py b/test/analytics.py new file mode 100644 index 000000000..f181a8ad0 --- /dev/null +++ b/test/analytics.py @@ -0,0 +1,6 @@ +class FakeMixpanel(object): + def send(self, endpoint, json_message): + pass + +def init_app(app): + return FakeMixpanel() diff --git a/test/data/test.db b/test/data/test.db index 73072ab0e..97c915274 100644 Binary files a/test/data/test.db and b/test/data/test.db differ diff --git a/test/specs.py b/test/specs.py index c0d10938b..e1de69850 100644 --- a/test/specs.py +++ b/test/specs.py @@ -7,423 +7,379 @@ from uuid import uuid4 PUBLIC_REPO = 'public/publicrepo' -PRIVATE_REPO = 'devtable/complex' +PRIVATE_REPO = 'devtable/shared' ORG = 'devtableorg' ORG_REPO = ORG + '/orgrepo' -ORG_OWNERS = 'owners' ORG_READERS = 'readers' ORG_OWNER = 'devtable' +ORG_OWNERS = 'owners' +ORG_READERS = 'readers' -FAKE_IMAGE_ID = uuid4() -FAKE_TAG_NAME = uuid4() -FAKE_USERNAME = uuid4() -FAKE_TEAMNAME = uuid4() -FAKE_TOKEN = uuid4() +FAKE_IMAGE_ID = str(uuid4()) +FAKE_TAG_NAME = str(uuid4()) +FAKE_USERNAME = str(uuid4()) +FAKE_TOKEN = str(uuid4()) +NEW_ORG_REPO_DETAILS = { + 'repository': str(uuid4()), + 'visibility': 'private', + 'description': '', + 'namespace': ORG, +} -def open_kwargs(method='GET', json_object=None): - kwargs = { - 'method': method, - } +NEW_USER_DETAILS = { + 'username': 'bob', + 'password': 'password', + 'email': 'jake@devtable.com', +} - if json_object is not None: - kwargs['data'] = json.dumps(json_object) - kwargs['content_type'] = 'application/json' +SEND_RECOVERY_DETAILS = { + 'email': 'jacob.moshenko@gmail.com', +} - elif method == 'POST' or method == 'PUT': - kwargs['data'] = json.dumps({ - 'fake': 'json', - 'data': 'here', - }) - kwargs['content_type'] = 'application/json' +SIGNIN_DETAILS = { + 'username': 'devtable', + 'password': 'password', +} - return kwargs +FILE_DROP_DETAILS = { + 'mimeType': 'application/zip', +} +CHANGE_PERMISSION_DETAILS = { + 'role': 'admin', +} -def build_anon_spec(): - return OrderedDict([ - (url_for('welcome'), (200, open_kwargs())), +CREATE_BUILD_DETAILS = { + 'file_id': str(uuid4()), +} - (url_for('plans_list'), (200, open_kwargs())), +CHANGE_VISIBILITY_DETAILS = { + 'visibility': 'public', +} - (url_for('get_logged_in_user'), (200, open_kwargs())), +CREATE_TOKEN_DETAILS = { + 'friendlyName': 'A new token', +} - (url_for('change_user_details'), (401, open_kwargs('PUT'))), +UPDATE_REPO_DETAILS = { + 'description': 'A new description', +} - (url_for('create_user_api'), (400, open_kwargs('POST'))), - (url_for('signin_api'), (400, open_kwargs('POST'))), +class TestSpec(object): + def __init__(self, url, anon_code=401, no_access_code=403, read_code=403, + admin_code=200): + self._url = url + self._data = None + self._method = 'GET' - (url_for('send_recovery'), (400, open_kwargs('POST'))), + self.anon_code = anon_code + self.no_access_code = no_access_code + self.read_code = read_code + self.admin_code = admin_code - (url_for('get_matching_users', prefix='dev'), (401, open_kwargs())), - - (url_for('get_matching_entities', prefix='dev'), (401, open_kwargs())), - - (url_for('get_organization', orgname=ORG), (401, open_kwargs())), - - (url_for('get_organization_private_allowed', orgname=ORG), - (401, open_kwargs())), - - (url_for('update_organization_team', orgname=ORG, teamname=ORG_OWNERS), - (401, open_kwargs('PUT'))), - - (url_for('delete_organization_team', orgname=ORG, teamname=ORG_OWNERS), - (401, open_kwargs('DELETE'))), - - (url_for('get_organization_team_members', orgname=ORG, - teamname=ORG_OWNERS), (401, open_kwargs())), - - (url_for('update_organization_team_member', orgname=ORG, - teamname=ORG_OWNERS, membername=ORG_OWNER), - (401, open_kwargs('PUT'))), - - (url_for('delete_organization_team_member', orgname=ORG, - teamname=ORG_OWNERS, membername=ORG_OWNER), - (401, open_kwargs('DELETE'))), - - (url_for('create_repo_api'), (401, open_kwargs('POST'))), - - (url_for('match_repos_api'), (200, open_kwargs())), - - (url_for('list_repos_api'), (200, open_kwargs())), - - (url_for('update_repo_api', repository=PUBLIC_REPO), - (401, open_kwargs('PUT'))), - (url_for('update_repo_api', repository=ORG_REPO), - (401, open_kwargs('PUT'))), - (url_for('update_repo_api', repository=PRIVATE_REPO), - (401, open_kwargs('PUT'))), - - (url_for('change_repo_visibility_api', repository=PUBLIC_REPO), - (401, open_kwargs('POST'))), - (url_for('change_repo_visibility_api', repository=ORG_REPO), - (401, open_kwargs('POST'))), - (url_for('change_repo_visibility_api', repository=PRIVATE_REPO), - (401, open_kwargs('POST'))), - - (url_for('delete_repository', repository=PUBLIC_REPO), - (401, open_kwargs('DELETE'))), - (url_for('delete_repository', repository=ORG_REPO), - (401, open_kwargs('DELETE'))), - (url_for('delete_repository', repository=PRIVATE_REPO), - (401, open_kwargs('DELETE'))), - - (url_for('get_repo_api', repository=PUBLIC_REPO),(200, open_kwargs())), - (url_for('get_repo_api', repository=ORG_REPO), (403, open_kwargs())), - (url_for('get_repo_api', repository=PRIVATE_REPO), (403, open_kwargs())), - - (url_for('get_repo_builds', repository=PUBLIC_REPO), - (401, open_kwargs())), - (url_for('get_repo_builds', repository=ORG_REPO), (401, open_kwargs())), - (url_for('get_repo_builds', repository=PRIVATE_REPO), - (401, open_kwargs())), - - (url_for('get_filedrop_url'), (401, open_kwargs('POST'))), - - (url_for('request_repo_build', repository=PUBLIC_REPO), - (401, open_kwargs('POST'))), - (url_for('request_repo_build', repository=ORG_REPO), - (401, open_kwargs('POST'))), - (url_for('request_repo_build', repository=PRIVATE_REPO), - (401, open_kwargs('POST'))), - - (url_for('list_repository_images', repository=PUBLIC_REPO), - (200, open_kwargs())), - (url_for('list_repository_images', repository=ORG_REPO), - (403, open_kwargs())), - (url_for('list_repository_images', repository=PRIVATE_REPO), - (403, open_kwargs())), - - (url_for('get_image', repository=PUBLIC_REPO, image_id=FAKE_IMAGE_ID), - (404, open_kwargs())), - (url_for('get_image', repository=ORG_REPO, image_id=FAKE_IMAGE_ID), - (403, open_kwargs())), - (url_for('get_image', repository=PRIVATE_REPO, image_id=FAKE_IMAGE_ID), - (403, open_kwargs())), - - (url_for('get_image_changes', repository=PUBLIC_REPO, - image_id=FAKE_IMAGE_ID), (404, open_kwargs())), - (url_for('get_image_changes', repository=ORG_REPO, - image_id=FAKE_IMAGE_ID), (403, open_kwargs())), - (url_for('get_image_changes', repository=PRIVATE_REPO, - image_id=FAKE_IMAGE_ID), (403, open_kwargs())), - - (url_for('list_tag_images', repository=PUBLIC_REPO, tag=FAKE_TAG_NAME), - (404, open_kwargs())), - (url_for('list_tag_images', repository=ORG_REPO, tag=FAKE_TAG_NAME), - (403, open_kwargs())), - (url_for('list_tag_images', repository=PRIVATE_REPO, tag=FAKE_TAG_NAME), - (403, open_kwargs())), - - (url_for('list_repo_team_permissions', repository=PUBLIC_REPO), - (401, open_kwargs())), - (url_for('list_repo_team_permissions', repository=ORG_REPO), - (401, open_kwargs())), - (url_for('list_repo_team_permissions', repository=PRIVATE_REPO), - (401, open_kwargs())), - - (url_for('list_repo_user_permissions', repository=PUBLIC_REPO), - (401, open_kwargs())), - (url_for('list_repo_user_permissions', repository=ORG_REPO), - (401, open_kwargs())), - (url_for('list_repo_user_permissions', repository=PRIVATE_REPO), - (401, open_kwargs())), - - (url_for('get_user_permissions', repository=PUBLIC_REPO, - username=FAKE_USERNAME), (401, open_kwargs())), - (url_for('get_user_permissions', repository=ORG_REPO, - username=FAKE_USERNAME), (401, open_kwargs())), - (url_for('get_user_permissions', repository=PRIVATE_REPO, - username=FAKE_USERNAME), (401, open_kwargs())), - - (url_for('get_team_permissions', repository=PUBLIC_REPO, - teamname=FAKE_TEAMNAME), (401, open_kwargs())), - (url_for('get_team_permissions', repository=ORG_REPO, - teamname=FAKE_TEAMNAME), (401, open_kwargs())), - (url_for('get_team_permissions', repository=PRIVATE_REPO, - teamname=FAKE_TEAMNAME), (401, open_kwargs())), - - (url_for('change_user_permissions', repository=PUBLIC_REPO, - username=FAKE_USERNAME), (401, open_kwargs('PUT'))), - (url_for('change_user_permissions', repository=ORG_REPO, - username=FAKE_USERNAME), (401, open_kwargs('PUT'))), - (url_for('change_user_permissions', repository=PRIVATE_REPO, - username=FAKE_USERNAME), (401, open_kwargs('PUT'))), - - (url_for('change_team_permissions', repository=PUBLIC_REPO, - teamname=FAKE_TEAMNAME), (401, open_kwargs('PUT'))), - (url_for('change_team_permissions', repository=ORG_REPO, - teamname=FAKE_TEAMNAME), (401, open_kwargs('PUT'))), - (url_for('change_team_permissions', repository=PRIVATE_REPO, - teamname=FAKE_TEAMNAME), (401, open_kwargs('PUT'))), - - (url_for('delete_user_permissions', repository=PUBLIC_REPO, - username=FAKE_USERNAME), (401, open_kwargs('DELETE'))), - (url_for('delete_user_permissions', repository=ORG_REPO, - username=FAKE_USERNAME), (401, open_kwargs('DELETE'))), - (url_for('delete_user_permissions', repository=PRIVATE_REPO, - username=FAKE_USERNAME), (401, open_kwargs('DELETE'))), - - (url_for('delete_team_permissions', repository=PUBLIC_REPO, - teamname=FAKE_TEAMNAME), (401, open_kwargs('DELETE'))), - (url_for('delete_team_permissions', repository=ORG_REPO, - teamname=FAKE_TEAMNAME), (401, open_kwargs('DELETE'))), - (url_for('delete_team_permissions', repository=PRIVATE_REPO, - teamname=FAKE_TEAMNAME), (401, open_kwargs('DELETE'))), - - (url_for('list_repo_tokens', repository=PUBLIC_REPO), - (401, open_kwargs())), - (url_for('list_repo_tokens', repository=ORG_REPO), (401, open_kwargs())), - (url_for('list_repo_tokens', repository=PRIVATE_REPO), - (401, open_kwargs())), - - (url_for('get_tokens', repository=PUBLIC_REPO, code=FAKE_TOKEN), - (401, open_kwargs())), - (url_for('get_tokens', repository=ORG_REPO, code=FAKE_TOKEN), - (401, open_kwargs())), - (url_for('get_tokens', repository=PRIVATE_REPO, code=FAKE_TOKEN), - (401, open_kwargs())), - - (url_for('create_token', repository=PUBLIC_REPO), - (401, open_kwargs('POST'))), - (url_for('create_token', repository=ORG_REPO), - (401, open_kwargs('POST'))), - (url_for('create_token', repository=PRIVATE_REPO), - (401, open_kwargs('POST'))), - - (url_for('change_token', repository=PUBLIC_REPO, code=FAKE_TOKEN), - (401, open_kwargs('PUT'))), - (url_for('change_token', repository=ORG_REPO, code=FAKE_TOKEN), - (401, open_kwargs('PUT'))), - (url_for('change_token', repository=PRIVATE_REPO, code=FAKE_TOKEN), - (401, open_kwargs('PUT'))), - - (url_for('delete_token', repository=PUBLIC_REPO, code=FAKE_TOKEN), - (401, open_kwargs('DELETE'))), - (url_for('delete_token', repository=ORG_REPO, code=FAKE_TOKEN), - (401, open_kwargs('DELETE'))), - (url_for('delete_token', repository=PRIVATE_REPO, code=FAKE_TOKEN), - (401, open_kwargs('DELETE'))), - - (url_for('subscribe_api'), (401, open_kwargs('PUT'))), - - (url_for('subscribe_org_api', orgname=ORG), (401, open_kwargs('PUT'))), - - (url_for('get_subscription'), (401, open_kwargs())), - - (url_for('get_org_subscription', orgname=ORG), (401, open_kwargs())), - ]) - - -def build_no_access_spec(): - changes = OrderedDict([ - (url_for('change_user_details'), (200, open_kwargs('PUT'))), - - (url_for('get_matching_users', prefix='dev'), (200, open_kwargs())), - - (url_for('get_matching_entities', prefix='dev'), (200, open_kwargs())), - - (url_for('get_organization', orgname=ORG), (403, open_kwargs())), - - (url_for('get_organization_private_allowed', orgname=ORG), - (403, open_kwargs())), - - (url_for('update_organization_team', orgname=ORG, teamname=ORG_OWNERS), - (403, open_kwargs('PUT'))), - - (url_for('delete_organization_team', orgname=ORG, teamname=ORG_OWNERS), - (403, open_kwargs('DELETE'))), - - (url_for('get_organization_team_members', orgname=ORG, - teamname=ORG_OWNERS), (403, open_kwargs())), - - (url_for('update_organization_team_member', orgname=ORG, - teamname=ORG_OWNERS, membername=ORG_OWNER), - (403, open_kwargs('PUT'))), - - (url_for('delete_organization_team_member', orgname=ORG, - teamname=ORG_OWNERS, membername=ORG_OWNER), - (403, open_kwargs('DELETE'))), - - (url_for('create_repo_api'), (403, open_kwargs('POST'))), - - (url_for('update_repo_api', repository=PUBLIC_REPO), - (403, open_kwargs('PUT'))), - (url_for('update_repo_api', repository=ORG_REPO), - (403, open_kwargs('PUT'))), - (url_for('update_repo_api', repository=PRIVATE_REPO), - (403, open_kwargs('PUT'))), - - (url_for('change_repo_visibility_api', repository=PUBLIC_REPO), - (403, open_kwargs('POST'))), - (url_for('change_repo_visibility_api', repository=ORG_REPO), - (403, open_kwargs('POST'))), - (url_for('change_repo_visibility_api', repository=PRIVATE_REPO), - (403, open_kwargs('POST'))), - - (url_for('delete_repository', repository=PUBLIC_REPO), - (403, open_kwargs('DELETE'))), - (url_for('delete_repository', repository=ORG_REPO), - (403, open_kwargs('DELETE'))), - (url_for('delete_repository', repository=PRIVATE_REPO), - (403, open_kwargs('DELETE'))), - - (url_for('get_repo_builds', repository=PUBLIC_REPO), - (403, open_kwargs())), - (url_for('get_repo_builds', repository=ORG_REPO), (403, open_kwargs())), - (url_for('get_repo_builds', repository=PRIVATE_REPO), - (403, open_kwargs())), - - (url_for('get_filedrop_url'), (400, open_kwargs('POST'))), - - (url_for('request_repo_build', repository=PUBLIC_REPO), - (403, open_kwargs('POST'))), - (url_for('request_repo_build', repository=ORG_REPO), - (403, open_kwargs('POST'))), - (url_for('request_repo_build', repository=PRIVATE_REPO), - (403, open_kwargs('POST'))), - - (url_for('list_repo_team_permissions', repository=PUBLIC_REPO), - (403, open_kwargs())), - (url_for('list_repo_team_permissions', repository=ORG_REPO), - (403, open_kwargs())), - (url_for('list_repo_team_permissions', repository=PRIVATE_REPO), - (403, open_kwargs())), - - (url_for('list_repo_user_permissions', repository=PUBLIC_REPO), - (403, open_kwargs())), - (url_for('list_repo_user_permissions', repository=ORG_REPO), - (403, open_kwargs())), - (url_for('list_repo_user_permissions', repository=PRIVATE_REPO), - (403, open_kwargs())), - - (url_for('get_user_permissions', repository=PUBLIC_REPO, - username=FAKE_USERNAME), (403, open_kwargs())), - (url_for('get_user_permissions', repository=ORG_REPO, - username=FAKE_USERNAME), (403, open_kwargs())), - (url_for('get_user_permissions', repository=PRIVATE_REPO, - username=FAKE_USERNAME), (403, open_kwargs())), - - (url_for('get_team_permissions', repository=PUBLIC_REPO, - teamname=FAKE_TEAMNAME), (403, open_kwargs())), - (url_for('get_team_permissions', repository=ORG_REPO, - teamname=FAKE_TEAMNAME), (403, open_kwargs())), - (url_for('get_team_permissions', repository=PRIVATE_REPO, - teamname=FAKE_TEAMNAME), (403, open_kwargs())), - - (url_for('change_user_permissions', repository=PUBLIC_REPO, - username=FAKE_USERNAME), (403, open_kwargs('PUT'))), - (url_for('change_user_permissions', repository=ORG_REPO, - username=FAKE_USERNAME), (403, open_kwargs('PUT'))), - (url_for('change_user_permissions', repository=PRIVATE_REPO, - username=FAKE_USERNAME), (403, open_kwargs('PUT'))), - - (url_for('change_team_permissions', repository=PUBLIC_REPO, - teamname=FAKE_TEAMNAME), (403, open_kwargs('PUT'))), - (url_for('change_team_permissions', repository=ORG_REPO, - teamname=FAKE_TEAMNAME), (403, open_kwargs('PUT'))), - (url_for('change_team_permissions', repository=PRIVATE_REPO, - teamname=FAKE_TEAMNAME), (403, open_kwargs('PUT'))), - - (url_for('delete_user_permissions', repository=PUBLIC_REPO, - username=FAKE_USERNAME), (403, open_kwargs('DELETE'))), - (url_for('delete_user_permissions', repository=ORG_REPO, - username=FAKE_USERNAME), (403, open_kwargs('DELETE'))), - (url_for('delete_user_permissions', repository=PRIVATE_REPO, - username=FAKE_USERNAME), (403, open_kwargs('DELETE'))), - - (url_for('delete_team_permissions', repository=PUBLIC_REPO, - teamname=FAKE_TEAMNAME), (403, open_kwargs('DELETE'))), - (url_for('delete_team_permissions', repository=ORG_REPO, - teamname=FAKE_TEAMNAME), (403, open_kwargs('DELETE'))), - (url_for('delete_team_permissions', repository=PRIVATE_REPO, - teamname=FAKE_TEAMNAME), (403, open_kwargs('DELETE'))), - - (url_for('list_repo_tokens', repository=PUBLIC_REPO), - (403, open_kwargs())), - (url_for('list_repo_tokens', repository=ORG_REPO), (403, open_kwargs())), - (url_for('list_repo_tokens', repository=PRIVATE_REPO), - (403, open_kwargs())), - - (url_for('get_tokens', repository=PUBLIC_REPO, code=FAKE_TOKEN), - (403, open_kwargs())), - (url_for('get_tokens', repository=ORG_REPO, code=FAKE_TOKEN), - (403, open_kwargs())), - (url_for('get_tokens', repository=PRIVATE_REPO, code=FAKE_TOKEN), - (403, open_kwargs())), - - (url_for('create_token', repository=PUBLIC_REPO), - (403, open_kwargs('POST'))), - (url_for('create_token', repository=ORG_REPO), - (403, open_kwargs('POST'))), - (url_for('create_token', repository=PRIVATE_REPO), - (403, open_kwargs('POST'))), - - (url_for('change_token', repository=PUBLIC_REPO, code=FAKE_TOKEN), - (403, open_kwargs('PUT'))), - (url_for('change_token', repository=ORG_REPO, code=FAKE_TOKEN), - (403, open_kwargs('PUT'))), - (url_for('change_token', repository=PRIVATE_REPO, code=FAKE_TOKEN), - (403, open_kwargs('PUT'))), - - (url_for('delete_token', repository=PUBLIC_REPO, code=FAKE_TOKEN), - (403, open_kwargs('DELETE'))), - (url_for('delete_token', repository=ORG_REPO, code=FAKE_TOKEN), - (403, open_kwargs('DELETE'))), - (url_for('delete_token', repository=PRIVATE_REPO, code=FAKE_TOKEN), - (403, open_kwargs('DELETE'))), - - (url_for('subscribe_api'), (403, open_kwargs('PUT'))), - - (url_for('subscribe_org_api', orgname=ORG), (403, open_kwargs('PUT'))), - - (url_for('get_subscription'), (200, open_kwargs())), - - (url_for('get_org_subscription', orgname=ORG), (403, open_kwargs())), - ]) - - to_update = build_anon_spec() - to_update.update(changes) - return to_update \ No newline at end of file + def set_data_from_obj(self, json_serializable): + self._data = json.dumps(json_serializable) + return self + + def set_method(self, method): + self._method = method + return self + + def get_client_args(self): + kwargs = { + 'method': self._method + } + + if self._data or self._method == 'POST' or self._method == 'PUT': + kwargs['data'] = self._data if self._data else '{}' + kwargs['content_type'] = 'application/json' + + return self._url, kwargs + + +def build_specs(): + return [ + TestSpec(url_for('welcome'), 200, 200, 200, 200), + + TestSpec(url_for('plans_list'), 200, 200, 200, 200), + + TestSpec(url_for('get_logged_in_user'), 200, 200, 200, 200), + + TestSpec(url_for('change_user_details'), + 401, 200, 200, 200).set_method('PUT'), + + TestSpec(url_for('create_user_api'), 201, 201, 201, + 201).set_method('POST').set_data_from_obj(NEW_USER_DETAILS), + + TestSpec(url_for('signin_api'), 200, 200, 200, + 200).set_method('POST').set_data_from_obj(SIGNIN_DETAILS), + + TestSpec(url_for('send_recovery'), 201, 201, 201, + 201).set_method('POST').set_data_from_obj(SEND_RECOVERY_DETAILS), + + TestSpec(url_for('get_matching_users', prefix='dev'), 401, 200, 200, 200), + + TestSpec(url_for('get_matching_entities', prefix='dev'), 401, 200, 200, + 200), + + TestSpec(url_for('get_organization', orgname=ORG), 401, 403, 200, 200), + + TestSpec(url_for('get_organization_private_allowed', orgname=ORG)), + + TestSpec(url_for('update_organization_team', orgname=ORG, + teamname=ORG_OWNERS)).set_method('PUT'), + TestSpec(url_for('update_organization_team', orgname=ORG, + teamname=ORG_READERS)).set_method('PUT'), + + TestSpec(url_for('delete_organization_team', orgname=ORG, + teamname=ORG_OWNERS), + admin_code=400).set_method('DELETE'), + TestSpec(url_for('delete_organization_team', orgname=ORG, + teamname=ORG_READERS), + admin_code=204).set_method('DELETE'), + + TestSpec(url_for('get_organization_team_members', orgname=ORG, + teamname=ORG_OWNERS)), + TestSpec(url_for('get_organization_team_members', orgname=ORG, + teamname=ORG_READERS), read_code=200), + + TestSpec(url_for('update_organization_team_member', orgname=ORG, + teamname=ORG_OWNERS, membername=ORG_OWNER), + admin_code=400).set_method('PUT'), + TestSpec(url_for('update_organization_team_member', orgname=ORG, + teamname=ORG_READERS, + membername=ORG_OWNER)).set_method('PUT'), + + TestSpec(url_for('delete_organization_team_member', orgname=ORG, + teamname=ORG_OWNERS, membername=ORG_OWNER), + admin_code=400).set_method('DELETE'), + TestSpec(url_for('delete_organization_team_member', orgname=ORG, + teamname=ORG_READERS, membername=ORG_OWNER), + admin_code=400).set_method('DELETE'), + + (TestSpec(url_for('create_repo_api')) + .set_method('POST') + .set_data_from_obj(NEW_ORG_REPO_DETAILS)), + + TestSpec(url_for('match_repos_api'), 200, 200, 200, 200), + + TestSpec(url_for('list_repos_api'), 200, 200, 200, 200), + + TestSpec(url_for('update_repo_api', repository=PUBLIC_REPO), + admin_code=403).set_method('PUT'), + (TestSpec(url_for('update_repo_api', repository=ORG_REPO)) + .set_method('PUT') + .set_data_from_obj(UPDATE_REPO_DETAILS)), + (TestSpec(url_for('update_repo_api', repository=PRIVATE_REPO)) + .set_method('PUT') + .set_data_from_obj(UPDATE_REPO_DETAILS)), + + (TestSpec(url_for('change_repo_visibility_api', repository=PUBLIC_REPO), + admin_code=403).set_method('POST') + .set_data_from_obj(CHANGE_VISIBILITY_DETAILS)), + (TestSpec(url_for('change_repo_visibility_api', repository=ORG_REPO)) + .set_method('POST').set_data_from_obj(CHANGE_VISIBILITY_DETAILS)), + (TestSpec(url_for('change_repo_visibility_api', repository=PRIVATE_REPO)) + .set_method('POST').set_data_from_obj(CHANGE_VISIBILITY_DETAILS)), + + TestSpec(url_for('delete_repository', repository=PUBLIC_REPO), + admin_code=403).set_method('DELETE'), + TestSpec(url_for('delete_repository', repository=ORG_REPO), + admin_code=204).set_method('DELETE'), + TestSpec(url_for('delete_repository', repository=PRIVATE_REPO), + admin_code=204).set_method('DELETE'), + + TestSpec(url_for('get_repo_api', repository=PUBLIC_REPO), + 200, 200, 200,200), + TestSpec(url_for('get_repo_api', repository=ORG_REPO), + 403, 403, 200, 200), + TestSpec(url_for('get_repo_api', repository=PRIVATE_REPO), + 403, 403, 200, 200), + + TestSpec(url_for('get_repo_builds', repository=PUBLIC_REPO), + admin_code=403), + TestSpec(url_for('get_repo_builds', repository=ORG_REPO)), + TestSpec(url_for('get_repo_builds', repository=PRIVATE_REPO)), + + TestSpec(url_for('get_filedrop_url'), 401, 200, 200, + 200).set_method('POST').set_data_from_obj(FILE_DROP_DETAILS), + + (TestSpec(url_for('request_repo_build', repository=PUBLIC_REPO), + admin_code=403).set_method('POST') + .set_data_from_obj(CREATE_BUILD_DETAILS)), + (TestSpec(url_for('request_repo_build', repository=ORG_REPO), + admin_code=201).set_method('POST') + .set_data_from_obj(CREATE_BUILD_DETAILS)), + (TestSpec(url_for('request_repo_build', repository=PRIVATE_REPO), + admin_code=201).set_method('POST') + .set_data_from_obj(CREATE_BUILD_DETAILS)), + + TestSpec(url_for('list_repository_images', repository=PUBLIC_REPO), + 200, 200, 200, 200), + TestSpec(url_for('list_repository_images', repository=ORG_REPO), + 403, 403, 200, 200), + TestSpec(url_for('list_repository_images', repository=PRIVATE_REPO), + 403, 403, 200, 200), + + TestSpec(url_for('get_image', repository=PUBLIC_REPO, + image_id=FAKE_IMAGE_ID), 404, 404, 404, 404), + TestSpec(url_for('get_image', repository=ORG_REPO, + image_id=FAKE_IMAGE_ID), 403, 403, 404, 404), + TestSpec(url_for('get_image', repository=PRIVATE_REPO, + image_id=FAKE_IMAGE_ID), 403, 403, 404, 404), + + TestSpec(url_for('get_image_changes', repository=PUBLIC_REPO, + image_id=FAKE_IMAGE_ID), 404, 404, 404, 404), + TestSpec(url_for('get_image_changes', repository=ORG_REPO, + image_id=FAKE_IMAGE_ID), 403, 403, 404, 404), + TestSpec(url_for('get_image_changes', repository=PRIVATE_REPO, + image_id=FAKE_IMAGE_ID), 403, 403, 404, 404), + + TestSpec(url_for('list_tag_images', repository=PUBLIC_REPO, + tag=FAKE_TAG_NAME), 404, 404, 404, 404), + TestSpec(url_for('list_tag_images', repository=ORG_REPO, + tag=FAKE_TAG_NAME), 403, 403, 404, 404), + TestSpec(url_for('list_tag_images', repository=PRIVATE_REPO, + tag=FAKE_TAG_NAME), 403, 403, 404, 404), + + TestSpec(url_for('list_repo_team_permissions', repository=PUBLIC_REPO), + admin_code=403), + TestSpec(url_for('list_repo_team_permissions', repository=ORG_REPO)), + TestSpec(url_for('list_repo_team_permissions', repository=PRIVATE_REPO)), + + TestSpec(url_for('list_repo_user_permissions', repository=PUBLIC_REPO), + admin_code=403), + TestSpec(url_for('list_repo_user_permissions', repository=ORG_REPO)), + TestSpec(url_for('list_repo_user_permissions', repository=PRIVATE_REPO)), + + TestSpec(url_for('get_user_permissions', repository=PUBLIC_REPO, + username=FAKE_USERNAME), admin_code=403), + TestSpec(url_for('get_user_permissions', repository=ORG_REPO, + username=FAKE_USERNAME), admin_code=400), + TestSpec(url_for('get_user_permissions', repository=PRIVATE_REPO, + username=FAKE_USERNAME), admin_code=400), + + TestSpec(url_for('get_team_permissions', repository=PUBLIC_REPO, + teamname=ORG_OWNERS), admin_code=403), + TestSpec(url_for('get_team_permissions', repository=PUBLIC_REPO, + teamname=ORG_READERS), admin_code=403), + TestSpec(url_for('get_team_permissions', repository=ORG_REPO, + teamname=ORG_OWNERS), admin_code=400), + TestSpec(url_for('get_team_permissions', repository=ORG_REPO, + teamname=ORG_READERS)), + TestSpec(url_for('get_team_permissions', repository=PRIVATE_REPO, + teamname=ORG_OWNERS), admin_code=400), + TestSpec(url_for('get_team_permissions', repository=PRIVATE_REPO, + teamname=ORG_READERS), admin_code=400), + + TestSpec(url_for('change_user_permissions', repository=PUBLIC_REPO, + username=FAKE_USERNAME), + admin_code=403).set_method('PUT'), + TestSpec(url_for('change_user_permissions', repository=ORG_REPO, + username=FAKE_USERNAME), + admin_code=400).set_method('PUT'), + TestSpec(url_for('change_user_permissions', repository=PRIVATE_REPO, + username=FAKE_USERNAME), + admin_code=400).set_method('PUT'), + + (TestSpec(url_for('change_team_permissions', repository=PUBLIC_REPO, + teamname=ORG_OWNERS), admin_code=403) + .set_method('PUT') + .set_data_from_obj(CHANGE_PERMISSION_DETAILS)), + (TestSpec(url_for('change_team_permissions', repository=PUBLIC_REPO, + teamname=ORG_READERS), admin_code=403) + .set_method('PUT') + .set_data_from_obj(CHANGE_PERMISSION_DETAILS)), + (TestSpec(url_for('change_team_permissions', repository=ORG_REPO, + teamname=ORG_OWNERS)) + .set_method('PUT') + .set_data_from_obj(CHANGE_PERMISSION_DETAILS)), + (TestSpec(url_for('change_team_permissions', repository=ORG_REPO, + teamname=ORG_READERS)) + .set_method('PUT') + .set_data_from_obj(CHANGE_PERMISSION_DETAILS)), + (TestSpec(url_for('change_team_permissions', repository=PRIVATE_REPO, + teamname=ORG_OWNERS), admin_code=400) + .set_method('PUT') + .set_data_from_obj(CHANGE_PERMISSION_DETAILS)), + (TestSpec(url_for('change_team_permissions', repository=PRIVATE_REPO, + teamname=ORG_READERS), admin_code=400) + .set_method('PUT') + .set_data_from_obj(CHANGE_PERMISSION_DETAILS)), + + TestSpec(url_for('delete_user_permissions', repository=PUBLIC_REPO, + username=FAKE_USERNAME), + admin_code=403).set_method('DELETE'), + TestSpec(url_for('delete_user_permissions', repository=ORG_REPO, + username=FAKE_USERNAME), + admin_code=400).set_method('DELETE'), + TestSpec(url_for('delete_user_permissions', repository=PRIVATE_REPO, + username=FAKE_USERNAME), + admin_code=400).set_method('DELETE'), + + TestSpec(url_for('delete_team_permissions', repository=PUBLIC_REPO, + teamname=ORG_OWNERS), + admin_code=403).set_method('DELETE'), + TestSpec(url_for('delete_team_permissions', repository=PUBLIC_REPO, + teamname=ORG_READERS), + admin_code=403).set_method('DELETE'), + TestSpec(url_for('delete_team_permissions', repository=ORG_REPO, + teamname=ORG_OWNERS), + admin_code=400).set_method('DELETE'), + TestSpec(url_for('delete_team_permissions', repository=ORG_REPO, + teamname=ORG_READERS), + admin_code=204).set_method('DELETE'), + TestSpec(url_for('delete_team_permissions', repository=PRIVATE_REPO, + teamname=ORG_OWNERS), + admin_code=400).set_method('DELETE'), + TestSpec(url_for('delete_team_permissions', repository=PRIVATE_REPO, + teamname=ORG_READERS), + admin_code=400).set_method('DELETE'), + + TestSpec(url_for('list_repo_tokens', repository=PUBLIC_REPO), + admin_code=403), + TestSpec(url_for('list_repo_tokens', repository=ORG_REPO)), + TestSpec(url_for('list_repo_tokens', repository=PRIVATE_REPO)), + + TestSpec(url_for('get_tokens', repository=PUBLIC_REPO, code=FAKE_TOKEN), + admin_code=403), + TestSpec(url_for('get_tokens', repository=ORG_REPO, code=FAKE_TOKEN), + admin_code=400), + TestSpec(url_for('get_tokens', repository=PRIVATE_REPO, code=FAKE_TOKEN), + admin_code=400), + + TestSpec(url_for('create_token', repository=PUBLIC_REPO), + admin_code=403).set_method('POST'), + (TestSpec(url_for('create_token', repository=ORG_REPO), + admin_code=201).set_method('POST') + .set_data_from_obj(CREATE_TOKEN_DETAILS)), + (TestSpec(url_for('create_token', repository=PRIVATE_REPO), + admin_code=201).set_method('POST') + .set_data_from_obj(CREATE_TOKEN_DETAILS)), + + TestSpec(url_for('change_token', repository=PUBLIC_REPO, code=FAKE_TOKEN), + admin_code=403).set_method('PUT'), + TestSpec(url_for('change_token', repository=ORG_REPO, code=FAKE_TOKEN), + admin_code=400).set_method('PUT'), + TestSpec(url_for('change_token', repository=PRIVATE_REPO, + code=FAKE_TOKEN), admin_code=400).set_method('PUT'), + + TestSpec(url_for('delete_token', repository=PUBLIC_REPO, code=FAKE_TOKEN), + admin_code=403).set_method('DELETE'), + TestSpec(url_for('delete_token', repository=ORG_REPO, code=FAKE_TOKEN), + admin_code=400).set_method('DELETE'), + TestSpec(url_for('delete_token', repository=PRIVATE_REPO, + code=FAKE_TOKEN), admin_code=400).set_method('DELETE'), + + TestSpec(url_for('subscribe_api'), 401, 400, 400, 400).set_method('PUT'), + + TestSpec(url_for('subscribe_org_api', orgname=ORG), + 401, 403, 403, 400).set_method('PUT'), + + TestSpec(url_for('get_subscription'), 401, 200, 200, 200), + + TestSpec(url_for('get_org_subscription', orgname=ORG)), + ] diff --git a/test/test_api_security.py b/test/test_api_security.py index f63d92c5c..6a8ffd743 100644 --- a/test/test_api_security.py +++ b/test/test_api_security.py @@ -6,10 +6,12 @@ import endpoints.api from app import app from data import model from initdb import wipe_database, initialize_database, populate_database -from specs import build_anon_spec, build_no_access_spec +from specs import build_specs NO_ACCESS_USER = 'freshuser' +READ_ACCESS_USER = 'reader' +ADMIN_ACCESS_USER = 'devtable' class ApiTestCase(unittest.TestCase): @@ -19,31 +21,78 @@ class ApiTestCase(unittest.TestCase): populate_database() +class SpecTestBuilder(type): + @staticmethod + def _test_generator(url, expected_status, open_kwargs, auth_username=None): + def test(self): + with app.test_client() as c: + if auth_username: + # Temporarily remove the teardown functions + teardown_funcs = app.teardown_request_funcs[None] + app.teardown_request_funcs[None] = [] + + with c.session_transaction() as sess: + sess['user_id'] = auth_username + sess['identity.id'] = auth_username + sess['identity.auth_type'] = 'username' + + # Restore the teardown functions + app.teardown_request_funcs[None] = teardown_funcs + + rv = c.open(url, **open_kwargs) + msg = '%s %s: %s expected: %s' % (open_kwargs['method'], url, + rv.status_code, expected_status) + if rv.status_code != expected_status: + print msg + self.assertEqual(rv.status_code, expected_status, msg) + return test + + + def __new__(cls, name, bases, attrs): + with app.test_request_context() as ctx: + specs = attrs['spec_func']() + for test_spec in specs: + url, open_kwargs = test_spec.get_client_args() + expected_status = getattr(test_spec, attrs['result_attr']) + test = SpecTestBuilder._test_generator(url, expected_status, + open_kwargs, + attrs['auth_username']) + + test_name_url = url.replace('/', '_').replace('-', '_') + test_name = 'test_%s_%s' % (open_kwargs['method'].lower(), + test_name_url) + attrs[test_name] = test + + return type(name, bases, attrs) + + class TestAnonymousAccess(ApiTestCase): - def __runspec(self, client, spec): - for url, (expected_status, open_kwargs) in spec.items(): - rv = client.open(url, **open_kwargs) - msg = '%s %s: %s expected: %s' % (open_kwargs['method'], url, - rv.status_code, expected_status) - self.assertEqual(rv.status_code, expected_status, msg) + __metaclass__ = SpecTestBuilder + spec_func = build_specs + result_attr = 'anon_code' + auth_username = None - def test_anonymous_public_access(self): - with app.test_request_context() as ctx: - spec = build_anon_spec() - with app.test_client() as c: - self.__runspec(c, spec) +class TestNoAccess(ApiTestCase): + __metaclass__ = SpecTestBuilder + spec_func = build_specs + result_attr = 'no_access_code' + auth_username = NO_ACCESS_USER - def test_authenticated_but_not_authorized(self): - with app.test_request_context() as ctx: - spec = build_no_access_spec() - with app.test_client() as c: - with c.session_transaction() as sess: - sess['user_id'] = NO_ACCESS_USER +class TestReadAccess(ApiTestCase): + __metaclass__ = SpecTestBuilder + spec_func = build_specs + result_attr = 'read_code' + auth_username = READ_ACCESS_USER - self.__runspec(c, spec) + +class TestAdminAccess(ApiTestCase): + __metaclass__ = SpecTestBuilder + spec_func = build_specs + result_attr = 'admin_code' + auth_username = ADMIN_ACCESS_USER if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/test/teststorage.py b/test/teststorage.py new file mode 100644 index 000000000..d597bfa0e --- /dev/null +++ b/test/teststorage.py @@ -0,0 +1,37 @@ +from uuid import uuid4 + +from storage.basestorage import Storage + + +class FakeStorage(Storage): + def _init_path(self, path=None, create=False): + return path + + def get_content(self, path): + raise IOError('Fake files are fake!') + + def put_content(self, path, content): + return path + + def stream_read(self, path): + yield '' + + def stream_write(self, path, fp): + pass + + def remove(self, path): + pass + + def exists(self, path): + return True + + +class FakeUserfiles(object): + def prepare_for_drop(self, mime_type): + return ('http://fake/url', uuid4()) + + def store_file(self, flask_file): + raise NotImplementedError() + + def get_file_url(self, file_id, expires_in=300): + return ('http://fake/url') diff --git a/workers/dockerfilebuild.py b/workers/dockerfilebuild.py index 4cdeb0e70..b78cb8b0c 100644 --- a/workers/dockerfilebuild.py +++ b/workers/dockerfilebuild.py @@ -13,7 +13,6 @@ from base64 import b64encode from requests.exceptions import ConnectionError from data.queue import dockerfile_build_queue -from data.userfiles import UserRequestFiles from data import model from data.database import db as db_connection from app import app @@ -151,9 +150,7 @@ def babysit_builder(request): ssh_client.exec_command(remove_auth_cmd) # Prepare the signed resource url the build node can fetch the job from - user_files = UserRequestFiles(app.config['AWS_ACCESS_KEY'], - app.config['AWS_SECRET_KEY'], - app.config['REGISTRY_S3_BUCKET']) + user_files = app.config['USERFILES'] resource_url = user_files.get_file_url(repository_build.resource_key) # Start the build server