diff --git a/endpoints/api/__init__.py b/endpoints/api/__init__.py index 2f5e2045e..16a2c99e9 100644 --- a/endpoints/api/__init__.py +++ b/endpoints/api/__init__.py @@ -1,6 +1,7 @@ import logging import json import datetime +import hashlib from flask import Blueprint, request, make_response, jsonify, session from flask.ext.restful import Resource, abort, Api, reqparse @@ -344,6 +345,12 @@ def log_action(kind, user_or_orgname, metadata=None, repo=None): metadata=metadata, repository=repo) +def calculate_internal_id(dbid): + """ Returns an 'internal id' we can send to the frontend that represents the + given database ID, but without leaking its actual value. + """ + return hashlib.sha1("internal-db-" + str(dbid)).hexdigest() + import endpoints.api.billing import endpoints.api.build import endpoints.api.discovery diff --git a/endpoints/api/image.py b/endpoints/api/image.py index 3060053ad..74cb1183f 100644 --- a/endpoints/api/image.py +++ b/endpoints/api/image.py @@ -4,7 +4,7 @@ from collections import defaultdict from app import storage as store from endpoints.api import (resource, nickname, require_repo_read, RepositoryParamResource, - format_date, NotFound) + format_date, NotFound, calculate_internal_id) from data import model from util.cache import cache_control_flask_restful @@ -15,16 +15,31 @@ def image_view(image): extended_props = image.storage command = extended_props.command + + def internal_id(aid): + if aid == '': + return '' + + return calculate_internal_id(aid) + + # Calculate the ancestors string, with the DBID's replaced with the + # hashed 'internal' IDs. + ancestors = [internal_id(a) for a in image.ancestors.split('/')] + ancestors_string = '/'.join(ancestors) + return { 'id': image.docker_image_id, 'created': format_date(extended_props.created), 'comment': extended_props.comment, 'command': json.loads(command) if command else None, - 'ancestors': image.ancestors, - 'dbid': image.id, 'size': extended_props.image_size, 'locations': list(image.storage.locations), 'uploading': image.storage.uploading, + + 'ancestors': ancestors_string, + + 'internal_id': calculate_internal_id(image.id), + 'sort_index': len(image.ancestors) } diff --git a/endpoints/api/repository.py b/endpoints/api/repository.py index 17a35fea1..6de830b84 100644 --- a/endpoints/api/repository.py +++ b/endpoints/api/repository.py @@ -7,7 +7,8 @@ from data import model from endpoints.api import (truthy_bool, format_date, nickname, log_action, validate_json_request, require_repo_read, require_repo_write, require_repo_admin, RepositoryParamResource, resource, query_param, parse_args, ApiResource, - request_error, require_scope, Unauthorized, NotFound, InvalidRequest) + request_error, require_scope, Unauthorized, NotFound, InvalidRequest, + calculate_internal_id) from auth.permissions import (ModifyRepositoryPermission, AdministerRepositoryPermission, CreateRepositoryPermission, ReadRepositoryPermission) from auth.auth_context import get_authenticated_user @@ -169,7 +170,7 @@ class Repository(RepositoryParamResource): return { 'name': tag.name, 'image_id': tag.image.docker_image_id, - 'dbid': tag.image.id + 'internal_id': calculate_internal_id(tag.image.id) } organization = None diff --git a/static/js/app.js b/static/js/app.js index 5457b46d2..8af5e66c4 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -6058,7 +6058,7 @@ quayApp.directive('tagSpecificImagesView', function () { } var currentTag = $scope.repository.tags[$scope.tag]; - if (image.dbid == currentTag.dbid) { + if (image.internal_id == currentTag.internal_id) { classes += 'tag-image '; } @@ -6068,15 +6068,15 @@ quayApp.directive('tagSpecificImagesView', function () { var forAllTagImages = function(tag, callback, opt_cutoff) { if (!tag) { return; } - if (!$scope.imageByDBID) { - $scope.imageByDBID = []; + if (!$scope.imageByInternalId) { + $scope.imageByInternalId = []; for (var i = 0; i < $scope.images.length; ++i) { var currentImage = $scope.images[i]; - $scope.imageByDBID[currentImage.dbid] = currentImage; + $scope.imageByInternalId[currentImage.internal_id] = currentImage; } } - var tag_image = $scope.imageByDBID[tag.dbid]; + var tag_image = $scope.imageByInternalId[tag.internal_id]; if (!tag_image) { return; } @@ -6085,7 +6085,7 @@ quayApp.directive('tagSpecificImagesView', function () { var ancestors = tag_image.ancestors.split('/').reverse(); for (var i = 0; i < ancestors.length; ++i) { - var image = $scope.imageByDBID[ancestors[i]]; + var image = $scope.imageByInternalId[ancestors[i]]; if (image) { if (image == opt_cutoff) { return; @@ -6111,7 +6111,7 @@ quayApp.directive('tagSpecificImagesView', function () { var getIdsForTag = function(currentTag) { var ids = {}; forAllTagImages(currentTag, function(image) { - ids[image.dbid] = true; + ids[image.internal_id] = true; }, $scope.imageCutoff); return ids; }; @@ -6121,8 +6121,8 @@ quayApp.directive('tagSpecificImagesView', function () { for (var currentTagName in $scope.repository.tags) { var currentTag = $scope.repository.tags[currentTagName]; if (currentTag != tag) { - for (var dbid in getIdsForTag(currentTag)) { - delete toDelete[dbid]; + for (var internal_id in getIdsForTag(currentTag)) { + delete toDelete[internal_id]; } } } @@ -6131,7 +6131,7 @@ quayApp.directive('tagSpecificImagesView', function () { var images = []; for (var i = 0; i < $scope.images.length; ++i) { var image = $scope.images[i]; - if (toDelete[image.dbid]) { + if (toDelete[image.internal_id]) { images.push(image); } } @@ -6142,7 +6142,7 @@ quayApp.directive('tagSpecificImagesView', function () { return result; } - return b.dbid - a.dbid; + return b.sort_index - a.sort_index; }); $scope.tagSpecificImages = images; diff --git a/static/js/controllers.js b/static/js/controllers.js index a4849e342..57e900c56 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -497,7 +497,7 @@ function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiServi }; $scope.findImageForTag = function(tag) { - return tag && $scope.imageByDBID && $scope.imageByDBID[tag.dbid]; + return tag && $scope.imageByInternalId && $scope.imageByInternalId[tag.internal_id]; }; $scope.createOrMoveTag = function(image, tagName, opt_invalid) { @@ -685,9 +685,9 @@ function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiServi }; var forAllTagImages = function(tag, callback) { - if (!tag || !$scope.imageByDBID) { return; } + if (!tag || !$scope.imageByInternalId) { return; } - var tag_image = $scope.imageByDBID[tag.dbid]; + var tag_image = $scope.imageByInternalId[tag.internal_id]; if (!tag_image) { return; } // Callback the tag's image itself. @@ -697,7 +697,7 @@ function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiServi if (!tag_image.ancestors) { return; } var ancestors = tag_image.ancestors.split('/'); for (var i = 0; i < ancestors.length; ++i) { - var image = $scope.imageByDBID[ancestors[i]]; + var image = $scope.imageByInternalId[ancestors[i]]; if (image) { callback(image); } @@ -786,10 +786,10 @@ function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiServi $scope.specificImages = []; // Build various images for quick lookup of images. - $scope.imageByDBID = {}; + $scope.imageByInternalId = {}; for (var i = 0; i < $scope.images.length; ++i) { var currentImage = $scope.images[i]; - $scope.imageByDBID[currentImage.dbid] = currentImage; + $scope.imageByInternalId[currentImage.internal_id] = currentImage; } // Dispose of any existing tree. diff --git a/static/js/graphing.js b/static/js/graphing.js index b18e5ddbe..74bd6aab5 100644 --- a/static/js/graphing.js +++ b/static/js/graphing.js @@ -307,8 +307,8 @@ ImageHistoryTree.prototype.setHighlightedPath_ = function(image) { this.markPath_(this.currentNode_, false); } - var imageByDBID = this.imageByDBID_; - var currentNode = imageByDBID[image.dbid]; + var imageByInternalId = this.imageByInternalId_; + var currentNode = imageByInternalId[image.internal_id]; if (currentNode) { this.markPath_(currentNode, true); this.currentNode_ = currentNode; @@ -386,7 +386,7 @@ ImageHistoryTree.prototype.buildRoot_ = function() { var formatted = {"name": "No images found"}; // Build a node for each image. - var imageByDBID = {}; + var imageByInternalId = {}; for (var i = 0; i < this.images_.length; ++i) { var image = this.images_[i]; var imageNode = { @@ -395,9 +395,9 @@ ImageHistoryTree.prototype.buildRoot_ = function() { "image": image, "tags": image.tags }; - imageByDBID[image.dbid] = imageNode; + imageByInternalId[image.internal_id] = imageNode; } - this.imageByDBID_ = imageByDBID; + this.imageByInternalId_ = imageByInternalId; // For each node, attach it to its immediate parent. If there is no immediate parent, // then the node is the root. @@ -408,10 +408,10 @@ ImageHistoryTree.prototype.buildRoot_ = function() { // Skip images that are currently uploading. if (image.uploading) { continue; } - var imageNode = imageByDBID[image.dbid]; + var imageNode = imageByInternalId[image.internal_id]; var ancestors = this.getAncestors_(image); - var immediateParent = ancestors[ancestors.length - 1] * 1; - var parent = imageByDBID[immediateParent]; + var immediateParent = ancestors[ancestors.length - 1]; + var parent = imageByInternalId[immediateParent]; if (parent) { // Add a reference to the parent. This makes walking the tree later easier. imageNode.parent = parent; @@ -442,7 +442,7 @@ ImageHistoryTree.prototype.buildRoot_ = function() { // Skip images that are currently uploading. if (image.uploading) { continue; } - var imageNode = imageByDBID[image.dbid]; + var imageNode = imageByInternalId[image.internal_id]; maxChildCount = Math.max(maxChildCount, this.determineMaximumChildCount_(imageNode)); } @@ -573,7 +573,7 @@ ImageHistoryTree.prototype.setTag_ = function(tagName) { return; } - var imageByDBID = this.imageByDBID_; + var imageByInternalId = this.imageByInternalId_; // Save the current tag. var previousTagName = this.currentTag_; @@ -596,10 +596,10 @@ ImageHistoryTree.prototype.setTag_ = function(tagName) { // Skip images that are currently uploading. if (image.uploading) { continue; } - var imageNode = this.imageByDBID_[image.dbid]; + var imageNode = this.imageByInternalId_[image.internal_id]; var ancestors = this.getAncestors_(image); - var immediateParent = ancestors[ancestors.length - 1] * 1; - var parent = imageByDBID[immediateParent]; + var immediateParent = ancestors[ancestors.length - 1]; + var parent = imageByInternalId[immediateParent]; if (parent && imageNode.highlighted) { var arr = parent.children; if (parent._children) { diff --git a/test/test_api_usage.py b/test/test_api_usage.py index c71b6f335..3bbc19098 100644 --- a/test/test_api_usage.py +++ b/test/test_api_usage.py @@ -1436,7 +1436,7 @@ class TestListAndGetImage(ApiTestCase): assert 'comment' in image assert 'command' in image assert 'ancestors' in image - assert 'dbid' in image + assert 'internal_id' in image assert 'size' in image ijson = self.getJsonResponse(RepositoryImage,