From 2006917e03750ff2834de3ce064f30331e53e0aa Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Thu, 27 Mar 2014 18:33:13 -0400 Subject: [PATCH 1/6] Add support for pull credentials on builds and build triggers --- data/database.py | 1 + data/model/legacy.py | 29 ++++++- endpoints/api/build.py | 37 +++++++- endpoints/api/trigger.py | 44 +++++++++- endpoints/common.py | 4 +- endpoints/webhooks.py | 4 +- initdb.py | 8 +- static/css/quay.css | 34 ++++++++ static/directives/trigger-description.html | 2 +- static/js/app.js | 16 +++- static/js/controllers.js | 20 ++++- static/partials/repo-admin.html | 46 +++++++++- test/data/test.db | Bin 194560 -> 540672 bytes test/test_api_security.py | 4 +- test/test_api_usage.py | 94 ++++++++++++++++++++- util/names.py | 3 + workers/dockerfilebuild.py | 46 ++++++---- 17 files changed, 355 insertions(+), 37 deletions(-) diff --git a/data/database.py b/data/database.py index d99f56c77..b0cb60bb6 100644 --- a/data/database.py +++ b/data/database.py @@ -176,6 +176,7 @@ class RepositoryBuildTrigger(BaseModel): auth_token = CharField() config = TextField(default='{}') write_token = ForeignKeyField(AccessToken, null=True) + pull_user = ForeignKeyField(User, null=True, related_name='pulluser') class EmailConfirmation(BaseModel): diff --git a/data/model/legacy.py b/data/model/legacy.py index 00d9c9b26..116a39a96 100644 --- a/data/model/legacy.py +++ b/data/model/legacy.py @@ -154,6 +154,16 @@ def create_robot(robot_shortname, parent): raise DataModelException(ex.message) +def lookup_robot(robot_username): + joined = User.select().join(FederatedLogin).join(LoginService) + found = list(joined.where(LoginService.name == 'quayrobot', + User.username == robot_username)) + if not found or len(found) < 1 or not found[0].robot: + return None + + return found[0] + + def verify_robot(robot_username, password): joined = User.select().join(FederatedLogin).join(LoginService) found = list(joined.where(FederatedLogin.service_ident == password, @@ -1449,6 +1459,20 @@ def create_repository_build(repo, access_token, job_config_obj, dockerfile_id, display_name=display_name, trigger=trigger, resource_key=dockerfile_id) +def get_pull_credentials(trigger): + if not trigger.pull_user: + return None + + try: + login_info = FederatedLogin.get(user=trigger.pull_user) + except FederatedLogin.DoesNotExist: + return None + + return { + 'username': trigger.pull_user.username, + 'password': login_info.service_ident, + 'registry': 'quay.io' # TODO: Is there a better way to do this? + } def create_webhook(repo, params_obj): return Webhook.create(repository=repo, parameters=json.dumps(params_obj)) @@ -1506,11 +1530,12 @@ def log_action(kind_name, user_or_organization_name, performer=None, metadata_json=json.dumps(metadata), datetime=timestamp) -def create_build_trigger(repo, service_name, auth_token, user): +def create_build_trigger(repo, service_name, auth_token, user, pull_user=None): service = BuildTriggerService.get(name=service_name) trigger = RepositoryBuildTrigger.create(repository=repo, service=service, auth_token=auth_token, - connected_user=user) + connected_user=user, + pull_user=None) return trigger diff --git a/endpoints/api/build.py b/endpoints/api/build.py index f14d097bb..c08ee37ec 100644 --- a/endpoints/api/build.py +++ b/endpoints/api/build.py @@ -33,6 +33,13 @@ def get_job_config(build_obj): def trigger_view(trigger): + def user_view(user): + return { + 'name': user.username, + 'kind': 'user', + 'is_robot': user.robot, + } + if trigger and trigger.uuid: config_dict = get_trigger_config(trigger) build_trigger = BuildTrigger.get_trigger_for_service(trigger.service.name) @@ -41,7 +48,8 @@ def trigger_view(trigger): 'config': config_dict, 'id': trigger.uuid, 'connected_user': trigger.connected_user.username, - 'is_active': build_trigger.is_active(config_dict) + 'is_active': build_trigger.is_active(config_dict), + 'pull_user': user_view(trigger.pull_user) if trigger.pull_user else None } return None @@ -88,6 +96,29 @@ class RepositoryBuildList(RepositoryParamResource): 'type': 'string', 'description': 'Subdirectory in which the Dockerfile can be found', }, + 'pull_credentials': { + 'type': 'object', + 'description': 'Credentials used by the builder when pulling images', + 'required': [ + 'username', + 'password', + 'registry' + ], + 'properties': { + 'username': { + 'type': 'string', + 'description': 'The username for the pull' + }, + 'password': { + 'type': 'string', + 'description': 'The password for the pull' + }, + 'registry': { + 'type': 'string', + 'description': 'The registry hostname for the pull' + }, + } + } }, }, } @@ -116,6 +147,7 @@ class RepositoryBuildList(RepositoryParamResource): dockerfile_id = request_json['file_id'] subdir = request_json['subdirectory'] if 'subdirectory' in request_json else '' + pull_credentials = request_json.get('pull_credentials', None) # Check if the dockerfile resource has already been used. If so, then it # can only be reused if the user has access to the repository for which it @@ -130,7 +162,8 @@ class RepositoryBuildList(RepositoryParamResource): repo = model.get_repository(namespace, repository) display_name = user_files.get_file_checksum(dockerfile_id) - build_request = start_build(repo, dockerfile_id, ['latest'], display_name, subdir, True) + build_request = start_build(repo, dockerfile_id, ['latest'], display_name, subdir, True, + pull_credentials=pull_credentials) resp = build_status_view(build_request, True) repo_string = '%s/%s' % (namespace, repository) diff --git a/endpoints/api/trigger.py b/endpoints/api/trigger.py index 1eb7cd169..0d846d889 100644 --- a/endpoints/api/trigger.py +++ b/endpoints/api/trigger.py @@ -15,7 +15,8 @@ from endpoints.common import start_build from endpoints.trigger import (BuildTrigger as BuildTriggerBase, TriggerDeactivationException, TriggerActivationException, EmptyRepositoryException) from data import model -from auth.permissions import UserAdminPermission +from auth.permissions import UserAdminPermission, AdministerOrganizationPermission +from util.names import parse_robot_username logger = logging.getLogger(__name__) @@ -133,7 +134,19 @@ class BuildTriggerActivate(RepositoryParamResource): 'BuildTriggerActivateRequest': { 'id': 'BuildTriggerActivateRequest', 'type': 'object', - 'description': 'Arbitrary json.', + 'required': [ + 'config' + ], + 'properties': { + 'config': { + 'type': 'object', + 'description': 'Arbitrary json.', + }, + 'pull_robot': { + 'type': 'string', + 'description': 'The name of the robot that will be used to pull images.' + } + } }, } @@ -154,7 +167,27 @@ class BuildTriggerActivate(RepositoryParamResource): user_permission = UserAdminPermission(trigger.connected_user.username) if user_permission.can(): - new_config_dict = request.get_json() + # Update the pull robot (if any). + pull_robot_name = request.get_json().get('pull_robot', None) + if pull_robot_name: + pull_robot = model.lookup_robot(pull_robot_name) + if not pull_robot: + raise NotFound() + + # Make sure the user has administer permissions for the robot's namespace. + (robot_namespace, shortname) = parse_robot_username(pull_robot_name) + if not AdministerOrganizationPermission(robot_namespace).can(): + raise Unauthorized() + + # Make sure the namespace matches that of the trigger. + if robot_namespace != namespace: + raise Unauthorized() + + # Set the pull robot. + trigger.pull_user = pull_robot + + # Update the config. + new_config_dict = request.get_json()['config'] token_name = 'Build Trigger: %s' % trigger.service.name token = model.create_delegate_token(namespace, repository, token_name, @@ -185,6 +218,7 @@ class BuildTriggerActivate(RepositoryParamResource): log_action('setup_repo_trigger', namespace, {'repo': repository, 'namespace': namespace, 'trigger_id': trigger.uuid, 'service': trigger.service.name, + 'pull_user': trigger.pull_user.username if trigger.pull_user else None, 'config': final_config}, repo=repo) return trigger_view(trigger) @@ -214,8 +248,10 @@ class ActivateBuildTrigger(RepositoryParamResource): dockerfile_id, tags, name, subdir = specs repo = model.get_repository(namespace, repository) + pull_credentials = model.get_pull_credentials(trigger) - build_request = start_build(repo, dockerfile_id, tags, name, subdir, True) + build_request = start_build(repo, dockerfile_id, tags, name, subdir, True, + pull_credentials=pull_credentials) resp = build_status_view(build_request, True) repo_string = '%s/%s' % (namespace, repository) diff --git a/endpoints/common.py b/endpoints/common.py index 4832aaaf0..99ca4ef1a 100644 --- a/endpoints/common.py +++ b/endpoints/common.py @@ -100,7 +100,7 @@ def check_repository_usage(user_or_org, plan_found): def start_build(repository, dockerfile_id, tags, build_name, subdir, manual, - trigger=None): + trigger=None, pull_credentials=None): host = urlparse.urlparse(request.url).netloc repo_path = '%s/%s/%s' % (host, repository.namespace, repository.name) @@ -112,7 +112,9 @@ def start_build(repository, dockerfile_id, tags, build_name, subdir, manual, 'docker_tags': tags, 'repository': repo_path, 'build_subdir': subdir, + 'pull_credentials': pull_credentials, } + build_request = model.create_repository_build(repository, token, job_config, dockerfile_id, build_name, trigger) diff --git a/endpoints/webhooks.py b/endpoints/webhooks.py index d92e7095e..69ba97b58 100644 --- a/endpoints/webhooks.py +++ b/endpoints/webhooks.py @@ -73,8 +73,10 @@ def build_trigger_webhook(namespace, repository, trigger_uuid): # This was just a validation request, we don't need to build anything return make_response('Okay') + pull_credentials = model.get_pull_credentials(trigger) repo = model.get_repository(namespace, repository) - start_build(repo, dockerfile_id, tags, name, subdir, False, trigger) + start_build(repo, dockerfile_id, tags, name, subdir, False, trigger, + pull_credentials=pull_credentials) return make_response('Okay') diff --git a/initdb.py b/initdb.py index a4b1709f0..fb55ca8fd 100644 --- a/initdb.py +++ b/initdb.py @@ -257,7 +257,7 @@ def populate_database(): new_user_1.stripe_id = TEST_STRIPE_ID new_user_1.save() - model.create_robot('dtrobot', new_user_1) + dtrobot = model.create_robot('dtrobot', new_user_1) new_user_2 = model.create_user('public', 'password', 'jacob.moshenko@gmail.com') @@ -268,6 +268,8 @@ def populate_database(): new_user_3.verified = True new_user_3.save() + model.create_robot('anotherrobot', new_user_3) + new_user_4 = model.create_user('randomuser', 'password', 'no4@thanks.com') new_user_4.verified = True new_user_4.save() @@ -330,7 +332,7 @@ def populate_database(): token = model.create_access_token(building, 'write') trigger = model.create_build_trigger(building, 'github', '123authtoken', - new_user_1) + new_user_1, pull_user = dtrobot) trigger.config = json.dumps({ 'build_source': 'jakedt/testconnect', 'subdir': '', @@ -354,6 +356,8 @@ def populate_database(): org.stripe_id = TEST_STRIPE_ID org.save() + model.create_robot('coolrobot', org) + oauth.create_application(org, 'Some Test App', 'http://localhost:8000', 'http://localhost:8000/o2c.html', client_id='deadbeef') diff --git a/static/css/quay.css b/static/css/quay.css index ac7c01e66..919c0ddc9 100644 --- a/static/css/quay.css +++ b/static/css/quay.css @@ -3589,4 +3589,38 @@ pre.command:before { .auth-info .scope { cursor: pointer; margin-right: 4px; +} + +.trigger-pull-credentials { + margin-top: 4px; + padding-left: 26px; + font-size: 12px; +} + +.trigger-pull-credentials .context-tooltip { + color: gray; + margin-right: 4px; +} + +.trigger-description .trigger-description-subtitle { + display: inline-block; + margin-right: 34px; +} + +.trigger-option-section:not(:last-child) { + border-bottom: 1px solid #eee; + padding-bottom: 16px; + margin-bottom: 16px; +} + +.trigger-option-section .entity-search-element .twitter-typeahead { + width: 370px; +} + +.trigger-option-section .entity-search-element input { + width: 100%; +} + +.trigger-option-section table td { + padding: 6px; } \ No newline at end of file diff --git a/static/directives/trigger-description.html b/static/directives/trigger-description.html index 2a081aa69..1ce32ec32 100644 --- a/static/directives/trigger-description.html +++ b/static/directives/trigger-description.html @@ -10,7 +10,7 @@
- Dockerfile: + Dockerfile: //Dockerfile diff --git a/static/js/app.js b/static/js/app.js index 87f73c724..00dc325ac 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -2504,7 +2504,8 @@ quayApp.directive('entitySearch', function () { 'isOrganization': '=isOrganization', 'isPersistent': '=isPersistent', 'currentEntity': '=currentEntity', - 'clearNow': '=clearNow' + 'clearNow': '=clearNow', + 'filter': '=filter', }, controller: function($scope, $element, Restangular, UserService, ApiService) { $scope.lazyLoading = true; @@ -2628,6 +2629,19 @@ quayApp.directive('entitySearch', function () { var datums = []; for (var i = 0; i < data.results.length; ++i) { var entity = data.results[i]; + if ($scope.filter) { + var allowed = $scope.filter; + var found = 'user'; + if (entity.kind == 'user') { + found = entity.is_robot ? 'robot' : 'user'; + } else if (entity.kind == 'team') { + found = 'team'; + } + if (allowed.indexOf(found)) { + continue; + } + } + datums.push({ 'value': entity.name, 'tokens': [entity.name], diff --git a/static/js/controllers.js b/static/js/controllers.js index b74071928..2daf27c30 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -1158,7 +1158,7 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope fetchRepository(); } -function RepoAdminCtrl($scope, Restangular, ApiService, KeyService, $routeParams, $rootScope, $location) { +function RepoAdminCtrl($scope, Restangular, ApiService, KeyService, $routeParams, $rootScope, $location, UserService) { var namespace = $routeParams.namespace; var name = $routeParams.name; @@ -1462,6 +1462,10 @@ function RepoAdminCtrl($scope, Restangular, ApiService, KeyService, $routeParams $scope.setupTrigger = function(trigger) { $scope.triggerSetupReady = false; $scope.currentSetupTrigger = trigger; + + trigger['_pullEntity'] = null; + trigger['_publicPull'] = true; + $('#setupTriggerModal').modal({}); $('#setupTriggerModal').on('hidden.bs.modal', function () { $scope.$apply(function() { @@ -1470,6 +1474,10 @@ function RepoAdminCtrl($scope, Restangular, ApiService, KeyService, $routeParams }); }; + $scope.isNamespaceAdmin = function(namespace) { + return UserService.isNamespaceAdmin(namespace); + }; + $scope.finishSetupTrigger = function(trigger) { $('#setupTriggerModal').modal('hide'); $scope.currentSetupTrigger = null; @@ -1479,7 +1487,15 @@ function RepoAdminCtrl($scope, Restangular, ApiService, KeyService, $routeParams 'trigger_uuid': trigger.id }; - ApiService.activateBuildTrigger(trigger['config'], params).then(function(resp) { + var data = { + 'config': trigger['config'] + }; + + if (trigger['_pullEntity']) { + data['pull_robot'] = trigger['_pullEntity']['name']; + } + + ApiService.activateBuildTrigger(data, params).then(function(resp) { trigger['is_active'] = true; }, function(resp) { $scope.triggers.splice($scope.triggers.indexOf(trigger), 1); diff --git a/static/partials/repo-admin.html b/static/partials/repo-admin.html index 6d2fe9665..80d45768e 100644 --- a/static/partials/repo-admin.html +++ b/static/partials/repo-admin.html @@ -270,6 +270,12 @@ Setting up trigger
+
+ + Pull Credentials: + + +
From 2a72e91bdb166564b7c644f61fd861e2f1e9a95e Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Tue, 1 Apr 2014 19:33:11 -0400 Subject: [PATCH 5/6] Prevent the entity search typeahead "no users found" message from being displayed when the entity is set from code --- static/js/app.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/static/js/app.js b/static/js/app.js index 7ac0219f2..311b531fb 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -2728,7 +2728,7 @@ quayApp.directive('entitySearch', function () { entity['is_org_member'] = true; } - $scope.setEntityInternal(entity); + $scope.setEntityInternal(entity, false); }; $scope.clearEntityInternal = function() { @@ -2738,8 +2738,12 @@ quayApp.directive('entitySearch', function () { } }; - $scope.setEntityInternal = function(entity) { - $(input).typeahead('val', $scope.isPersistent ? entity.name : ''); + $scope.setEntityInternal = function(entity, updateTypeahead) { + if (updateTypeahead) { + $(input).typeahead('val', $scope.isPersistent ? entity.name : ''); + } else { + $(input).val($scope.isPersistent ? entity.name : ''); + } if ($scope.isPersistent) { $scope.currentEntity = entity; @@ -2854,7 +2858,7 @@ quayApp.directive('entitySearch', function () { $(input).on('typeahead:selected', function(e, datum) { $scope.$apply(function() { - $scope.setEntityInternal(datum.entity); + $scope.setEntityInternal(datum.entity, true); }); }); From 9a79d1562ad2aaf8452910cec82a5db33e19b940 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Tue, 1 Apr 2014 21:49:06 -0400 Subject: [PATCH 6/6] Change to store the pull robot on the repository build and only add the credentials to the queue item. This prevents the credentials from being exposed to the end user. Also fixes the restart build option --- data/database.py | 3 +- data/model/legacy.py | 31 ++++++++++++---- endpoints/api/build.py | 64 ++++++++++++++++---------------- endpoints/api/trigger.py | 8 ++-- endpoints/common.py | 8 ++-- endpoints/webhooks.py | 4 +- initdb.py | 2 +- static/js/controllers.js | 9 ++++- static/partials/repo-admin.html | 4 +- test/data/test.db | Bin 198656 -> 544768 bytes test/test_api_usage.py | 39 +++++++++++++------ util/names.py | 3 ++ workers/dockerfilebuild.py | 3 +- 13 files changed, 110 insertions(+), 68 deletions(-) diff --git a/data/database.py b/data/database.py index b0cb60bb6..d6a67bd80 100644 --- a/data/database.py +++ b/data/database.py @@ -176,7 +176,7 @@ class RepositoryBuildTrigger(BaseModel): auth_token = CharField() config = TextField(default='{}') write_token = ForeignKeyField(AccessToken, null=True) - pull_user = ForeignKeyField(User, null=True, related_name='pulluser') + pull_robot = ForeignKeyField(User, null=True, related_name='triggerpullrobot') class EmailConfirmation(BaseModel): @@ -245,6 +245,7 @@ class RepositoryBuild(BaseModel): started = DateTimeField(default=datetime.now) display_name = CharField() trigger = ForeignKeyField(RepositoryBuildTrigger, null=True, index=True) + pull_robot = ForeignKeyField(User, null=True, related_name='buildpullrobot') class QueueItem(BaseModel): diff --git a/data/model/legacy.py b/data/model/legacy.py index a2cac1d26..3cfa6c27f 100644 --- a/data/model/legacy.py +++ b/data/model/legacy.py @@ -1453,27 +1453,42 @@ def get_recent_repository_build(namespace_name, repository_name): def create_repository_build(repo, access_token, job_config_obj, dockerfile_id, - display_name, trigger=None): + display_name, trigger=None, pull_robot_name=None): + pull_robot = None + if pull_robot_name: + pull_robot = lookup_robot(pull_robot_name) + return RepositoryBuild.create(repository=repo, access_token=access_token, job_config=json.dumps(job_config_obj), display_name=display_name, trigger=trigger, - resource_key=dockerfile_id) + resource_key=dockerfile_id, + pull_robot=pull_robot) -def get_pull_credentials(trigger): - if not trigger.pull_user: + +def get_pull_robot_name(trigger): + if not trigger.pull_robot: return None + return trigger.pull_robot.username + + +def get_pull_credentials(robotname): + robot = lookup_robot(robotname) + if not robot: + return None + try: - login_info = FederatedLogin.get(user=trigger.pull_user) + login_info = FederatedLogin.get(user=robot) except FederatedLogin.DoesNotExist: return None return { - 'username': trigger.pull_user.username, + 'username': robot.username, 'password': login_info.service_ident, 'registry': '%s://%s/v1/' % (app.config['URL_SCHEME'], app.config['URL_HOST']), } + def create_webhook(repo, params_obj): return Webhook.create(repository=repo, parameters=json.dumps(params_obj)) @@ -1530,12 +1545,12 @@ def log_action(kind_name, user_or_organization_name, performer=None, metadata_json=json.dumps(metadata), datetime=timestamp) -def create_build_trigger(repo, service_name, auth_token, user, pull_user=None): +def create_build_trigger(repo, service_name, auth_token, user, pull_robot=None): service = BuildTriggerService.get(name=service_name) trigger = RepositoryBuildTrigger.create(repository=repo, service=service, auth_token=auth_token, connected_user=user, - pull_user=None) + pull_robot=pull_robot) return trigger diff --git a/endpoints/api/build.py b/endpoints/api/build.py index 3ff6dfd9d..9fa130054 100644 --- a/endpoints/api/build.py +++ b/endpoints/api/build.py @@ -10,8 +10,10 @@ from endpoints.api import (RepositoryParamResource, parse_args, query_param, nic from endpoints.common import start_build from endpoints.trigger import BuildTrigger from data import model -from auth.permissions import ModifyRepositoryPermission +from auth.auth_context import get_authenticated_user +from auth.permissions import ModifyRepositoryPermission, AdministerOrganizationPermission from data.buildlogs import BuildStatusRetrievalError +from util.names import parse_robot_username logger = logging.getLogger(__name__) @@ -33,13 +35,14 @@ def get_job_config(build_obj): return None +def user_view(user): + return { + 'name': user.username, + 'kind': 'user', + 'is_robot': user.robot, + } + def trigger_view(trigger): - def user_view(user): - return { - 'name': user.username, - 'kind': 'user', - 'is_robot': user.robot, - } if trigger and trigger.uuid: config_dict = get_trigger_config(trigger) @@ -50,7 +53,7 @@ def trigger_view(trigger): 'id': trigger.uuid, 'connected_user': trigger.connected_user.username, 'is_active': build_trigger.is_active(config_dict), - 'pull_user': user_view(trigger.pull_user) if trigger.pull_user else None + 'pull_robot': user_view(trigger.pull_robot) if trigger.pull_robot else None } return None @@ -75,6 +78,7 @@ def build_status_view(build_obj, can_write=False): 'is_writer': can_write, 'trigger': trigger_view(build_obj.trigger), 'resource_key': build_obj.resource_key, + 'pull_robot': user_view(build_obj.pull_robot) if build_obj.pull_robot else None, } if can_write: @@ -103,28 +107,9 @@ class RepositoryBuildList(RepositoryParamResource): 'type': 'string', 'description': 'Subdirectory in which the Dockerfile can be found', }, - 'pull_credentials': { - 'type': 'object', - 'description': 'Credentials used by the builder when pulling images', - 'required': [ - 'username', - 'password', - 'registry' - ], - 'properties': { - 'username': { - 'type': 'string', - 'description': 'The username for the pull' - }, - 'password': { - 'type': 'string', - 'description': 'The password for the pull' - }, - 'registry': { - 'type': 'string', - 'description': 'The registry hostname for the pull' - }, - } + 'pull_robot': { + 'type': 'string', + 'description': 'Username of a Quay robot account to use as pull credentials', } }, }, @@ -154,7 +139,22 @@ class RepositoryBuildList(RepositoryParamResource): dockerfile_id = request_json['file_id'] subdir = request_json['subdirectory'] if 'subdirectory' in request_json else '' - pull_credentials = request_json.get('pull_credentials', None) + pull_robot_name = request_json.get('pull_robot', None) + + # Verify the security behind the pull robot. + if pull_robot_name: + result = parse_robot_username(pull_robot_name) + if result: + pull_robot = model.lookup_robot(pull_robot_name) + if not pull_robot: + raise NotFound() + + # Make sure the user has administer permissions for the robot's namespace. + (robot_namespace, shortname) = result + if not AdministerOrganizationPermission(robot_namespace).can(): + raise Unauthorized() + else: + raise Unauthorized() # Check if the dockerfile resource has already been used. If so, then it # can only be reused if the user has access to the repository for which it @@ -170,7 +170,7 @@ class RepositoryBuildList(RepositoryParamResource): display_name = user_files.get_file_checksum(dockerfile_id) build_request = start_build(repo, dockerfile_id, ['latest'], display_name, subdir, True, - pull_credentials=pull_credentials) + pull_robot_name=pull_robot_name) resp = build_status_view(build_request, True) repo_string = '%s/%s' % (namespace, repository) diff --git a/endpoints/api/trigger.py b/endpoints/api/trigger.py index fc734d3db..c62367f52 100644 --- a/endpoints/api/trigger.py +++ b/endpoints/api/trigger.py @@ -190,7 +190,7 @@ class BuildTriggerActivate(RepositoryParamResource): raise Unauthorized() # Set the pull robot. - trigger.pull_user = pull_robot + trigger.pull_robot = pull_robot # Update the config. new_config_dict = request.get_json()['config'] @@ -224,7 +224,7 @@ class BuildTriggerActivate(RepositoryParamResource): log_action('setup_repo_trigger', namespace, {'repo': repository, 'namespace': namespace, 'trigger_id': trigger.uuid, 'service': trigger.service.name, - 'pull_user': trigger.pull_user.username if trigger.pull_user else None, + 'pull_robot': trigger.pull_robot.username if trigger.pull_robot else None, 'config': final_config}, repo=repo) return trigger_view(trigger) @@ -254,10 +254,10 @@ class ActivateBuildTrigger(RepositoryParamResource): dockerfile_id, tags, name, subdir = specs repo = model.get_repository(namespace, repository) - pull_credentials = model.get_pull_credentials(trigger) + pull_robot_name = model.get_pull_robot_name(trigger) build_request = start_build(repo, dockerfile_id, tags, name, subdir, True, - pull_credentials=pull_credentials) + pull_robot_name=pull_robot_name) resp = build_status_view(build_request, True) repo_string = '%s/%s' % (namespace, repository) diff --git a/endpoints/common.py b/endpoints/common.py index e22df6226..e25d7c797 100644 --- a/endpoints/common.py +++ b/endpoints/common.py @@ -107,7 +107,7 @@ def check_repository_usage(user_or_org, plan_found): def start_build(repository, dockerfile_id, tags, build_name, subdir, manual, - trigger=None, pull_credentials=None): + trigger=None, pull_robot_name=None): host = urlparse.urlparse(request.url).netloc repo_path = '%s/%s/%s' % (host, repository.namespace, repository.name) @@ -118,18 +118,18 @@ def start_build(repository, dockerfile_id, tags, build_name, subdir, manual, job_config = { 'docker_tags': tags, 'repository': repo_path, - 'build_subdir': subdir, - 'pull_credentials': pull_credentials, + 'build_subdir': subdir } build_request = model.create_repository_build(repository, token, job_config, dockerfile_id, build_name, - trigger) + trigger, pull_robot_name = pull_robot_name) dockerfile_build_queue.put(json.dumps({ 'build_uuid': build_request.uuid, 'namespace': repository.namespace, 'repository': repository.name, + 'pull_credentials': model.get_pull_credentials(pull_robot_name) if pull_robot_name else None }), retries_remaining=1) metadata = { diff --git a/endpoints/webhooks.py b/endpoints/webhooks.py index 69ba97b58..93d5e413c 100644 --- a/endpoints/webhooks.py +++ b/endpoints/webhooks.py @@ -73,10 +73,10 @@ def build_trigger_webhook(namespace, repository, trigger_uuid): # This was just a validation request, we don't need to build anything return make_response('Okay') - pull_credentials = model.get_pull_credentials(trigger) + pull_robot_name = model.get_pull_robot_name(trigger) repo = model.get_repository(namespace, repository) start_build(repo, dockerfile_id, tags, name, subdir, False, trigger, - pull_credentials=pull_credentials) + pull_robot_name=pull_robot_name) return make_response('Okay') diff --git a/initdb.py b/initdb.py index edad2f2b1..854f83f8a 100644 --- a/initdb.py +++ b/initdb.py @@ -332,7 +332,7 @@ def populate_database(): token = model.create_access_token(building, 'write') trigger = model.create_build_trigger(building, 'github', '123authtoken', - new_user_1, pull_user = dtrobot) + new_user_1, pull_robot=dtrobot[0]) trigger.config = json.dumps({ 'build_source': 'jakedt/testconnect', 'subdir': '', diff --git a/static/js/controllers.js b/static/js/controllers.js index e907398eb..08bcca561 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -962,9 +962,13 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope var data = { 'file_id': build['resource_key'], - 'subdirectory': subdirectory + 'subdirectory': subdirectory, }; + if (build['pull_robot']) { + data['pull_robot'] = build['pull_robot']['name']; + } + var params = { 'repository': namespace + '/' + name }; @@ -1486,7 +1490,8 @@ function RepoAdminCtrl($scope, Restangular, ApiService, KeyService, $routeParams } ApiService.activateBuildTrigger(data, params).then(function(resp) { - trigger['is_active'] = true; + trigger['is_active'] = true; + trigger['pull_robot'] = resp['pull_robot']; }, function(resp) { $scope.triggers.splice($scope.triggers.indexOf(trigger), 1); bootbox.dialog({ diff --git a/static/partials/repo-admin.html b/static/partials/repo-admin.html index 172b6d9f8..f1ee224f5 100644 --- a/static/partials/repo-admin.html +++ b/static/partials/repo-admin.html @@ -270,11 +270,11 @@ Setting up trigger
-
+
Pull Credentials: - +
diff --git a/test/data/test.db b/test/data/test.db index b52ec7fe126611a7256b4d1fc9673b523ab1a3d5..a2d06594804813bac40b14a63510edfab6653493 100644 GIT binary patch delta 18485 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 0 - def test_requestrepobuild_with_credentials(self): + def test_requestrepobuild_with_robot(self): self.login(ADMIN_ACCESS_USER) - # Ensure where not yet building. + # Ensure we are not yet building. json = self.getJsonResponse(RepositoryBuildList, params=dict(repository=ADMIN_ACCESS_USER + '/simple')) assert len(json['builds']) == 0 # Request a (fake) build. - pull_creds = { - 'username': 'foo', - 'password': 'bar', - 'registry': 'baz' - } + pull_robot = ADMIN_ACCESS_USER + '+dtrobot' self.postResponse(RepositoryBuildList, params=dict(repository=ADMIN_ACCESS_USER + '/simple'), - data=dict(file_id='foobarbaz', pull_credentials=pull_creds), + data=dict(file_id='foobarbaz', pull_robot=pull_robot), expected_code=201) # Check for the build. @@ -1007,7 +1003,28 @@ class TestRequestRepoBuild(ApiTestCase): params=dict(repository=ADMIN_ACCESS_USER + '/building')) assert len(json['builds']) > 0 - + + + def test_requestrepobuild_with_invalid_robot(self): + self.login(ADMIN_ACCESS_USER) + + # Request a (fake) build. + pull_robot = ADMIN_ACCESS_USER + '+invalidrobot' + self.postResponse(RepositoryBuildList, + params=dict(repository=ADMIN_ACCESS_USER + '/simple'), + data=dict(file_id='foobarbaz', pull_robot=pull_robot), + expected_code=404) + + def test_requestrepobuild_with_unauthorized_robot(self): + self.login(ADMIN_ACCESS_USER) + + # Request a (fake) build. + pull_robot = 'freshuser+anotherrobot' + self.postResponse(RepositoryBuildList, + params=dict(repository=ADMIN_ACCESS_USER + '/simple'), + data=dict(file_id='foobarbaz', pull_robot=pull_robot), + expected_code=403) + class TestWebhooks(ApiTestCase): @@ -1746,7 +1763,7 @@ class TestBuildTriggers(ApiTestCase): # Verify that the robot was saved. self.assertEquals(True, activate_json['is_active']) - self.assertEquals(ADMIN_ACCESS_USER + '+dtrobot', activate_json['pull_user']['name']) + self.assertEquals(ADMIN_ACCESS_USER + '+dtrobot', activate_json['pull_robot']['name']) # Start a manual build. start_json = self.postJsonResponse(ActivateBuildTrigger, diff --git a/util/names.py b/util/names.py index b705bec36..57fafdd10 100644 --- a/util/names.py +++ b/util/names.py @@ -26,4 +26,7 @@ def format_robot_username(parent_username, robot_shortname): return '%s+%s' % (parent_username, robot_shortname) def parse_robot_username(robot_username): + if not '+' in robot_username: + return None + return robot_username.split('+', 2) diff --git a/workers/dockerfilebuild.py b/workers/dockerfilebuild.py index 6e8be1f4d..9d552a4ae 100644 --- a/workers/dockerfilebuild.py +++ b/workers/dockerfilebuild.py @@ -352,13 +352,14 @@ class DockerfileBuildWorker(Worker): job_details['repository'], job_details['build_uuid']) + pull_credentials = job_details.get('pull_credentials', None) + job_config = json.loads(repository_build.job_config) resource_url = user_files.get_file_url(repository_build.resource_key) tag_names = job_config['docker_tags'] build_subdir = job_config['build_subdir'] repo = job_config['repository'] - pull_credentials = job_config.get('pull_credentials', None) access_token = repository_build.access_token.code