def _syncItems(image, cli_dict, user): folderModel = Folder() itemModel = Item() children = folderModel.childItems( image.tagFolder, filters={'meta.slicerCLIType': 'task'}) existingItems = {item['name']: item for item in children} for cli, desc in six.iteritems(cli_dict): item = itemModel.createItem( cli, user, image.tagFolder, 'Slicer CLI generated CLI command item', reuseExisting=True) meta_data = dict(slicerCLIType='task', type=desc.get('type', 'Unknown')) # copy some things from the image to be independent meta_data['image'] = image.name meta_data[ 'digest'] = image.digest if image.name != image.digest else None desc_type = desc.get('desc-type', 'xml') if desc_type == 'xml': meta_data.update(parse_xml_desc(item, desc, user)) elif desc_type == 'yaml': meta_data.update(parse_yaml_desc(item, desc, user)) elif desc_type == 'json': meta_data.update(parse_json_desc(item, desc, user)) itemModel.setMetadata(item, meta_data) if cli in existingItems: del existingItems[cli] # delete superfluous items for item in six.itervalues(existingItems): itemModel.remove(item)
class Item(Resource): def __init__(self): super(Item, self).__init__() self.resourceName = 'item' self._model = ItemModel() self.route('DELETE', (':id',), self.deleteItem) self.route('GET', (), self.find) self.route('GET', (':id',), self.getItem) self.route('GET', (':id', 'files'), self.getFiles) self.route('GET', (':id', 'download'), self.download) self.route('GET', (':id', 'rootpath'), self.rootpath) self.route('POST', (), self.createItem) self.route('PUT', (':id',), self.updateItem) self.route('POST', (':id', 'copy'), self.copyItem) self.route('PUT', (':id', 'metadata'), self.setMetadata) self.route('DELETE', (':id', 'metadata'), self.deleteMetadata) @access.public(scope=TokenScope.DATA_READ) @filtermodel(model=ItemModel) @autoDescribeRoute( Description('List or search for items.') .notes('You must pass either a "itemId" or "text" field ' 'to specify how you are searching for items. ' 'If you omit one of these parameters the request will fail and respond : ' '"Invalid search mode."') .responseClass('Item', array=True) .param('folderId', 'Pass this to list all items in a folder.', required=False) .param('text', 'Pass this to perform a full text search for items.', required=False) .param('name', 'Pass to lookup an item by exact name match. Must ' 'pass folderId as well when using this.', required=False) .pagingParams(defaultSort='lowerName') .errorResponse() .errorResponse('Read access was denied on the parent folder.', 403) ) def find(self, folderId, text, name, limit, offset, sort): """ Get a list of items with given search parameters. Currently accepted search modes are: 1. Searching by folderId, with optional additional filtering by the name field (exact match) or using full text search within a single parent folder. Pass a "name" parameter or "text" parameter to invoke these additional filters. 2. Searching with full text search across all items in the system. Simply pass a "text" parameter for this mode. """ user = self.getCurrentUser() if folderId: folder = Folder().load( id=folderId, user=user, level=AccessType.READ, exc=True) filters = {} if text: filters['$text'] = { '$search': text } if name: filters['name'] = name return Folder().childItems( folder=folder, limit=limit, offset=offset, sort=sort, filters=filters) elif text is not None: return self._model.textSearch( text, user=user, limit=limit, offset=offset, sort=sort) else: raise RestException('Invalid search mode.') @access.public(scope=TokenScope.DATA_READ) @filtermodel(model=ItemModel) @autoDescribeRoute( Description('Get an item by ID.') .responseClass('Item') .modelParam('id', model=ItemModel, level=AccessType.READ) .errorResponse('ID was invalid.') .errorResponse('Read access was denied for the item.', 403) ) def getItem(self, item): return item @access.user(scope=TokenScope.DATA_WRITE) @filtermodel(model=ItemModel) @autoDescribeRoute( Description('Create a new item.') .responseClass('Item') .modelParam('folderId', 'The ID of the parent folder.', model=Folder, level=AccessType.WRITE, paramType='query') .param('name', 'Name for the item.', strip=True) .param('description', 'Description for the item.', required=False, default='', strip=True) .param('reuseExisting', 'Return existing item (by name) if it exists.', required=False, dataType='boolean', default=False) .jsonParam('metadata', 'A JSON object containing the metadata keys to add', paramType='form', requireObject=True, required=False) .errorResponse() .errorResponse('Write access was denied on the parent folder.', 403) ) def createItem(self, folder, name, description, reuseExisting, metadata): newItem = self._model.createItem( folder=folder, name=name, creator=self.getCurrentUser(), description=description, reuseExisting=reuseExisting) if metadata: newItem = self._model.setMetadata(newItem, metadata) return newItem @access.user(scope=TokenScope.DATA_WRITE) @filtermodel(model=ItemModel) @autoDescribeRoute( Description('Edit an item or move it to another folder.') .responseClass('Item') .modelParam('id', model=ItemModel, level=AccessType.WRITE) .param('name', 'Name for the item.', required=False, strip=True) .param('description', 'Description for the item.', required=False) .modelParam('folderId', 'Pass this to move the item to a new folder.', model=Folder, required=False, paramType='query', level=AccessType.WRITE) .jsonParam('metadata', 'A JSON object containing the metadata keys to add', paramType='form', requireObject=True, required=False) .errorResponse('ID was invalid.') .errorResponse('Write access was denied for the item or folder.', 403) ) def updateItem(self, item, name, description, folder, metadata): if name is not None: item['name'] = name if description is not None: item['description'] = description self._model.updateItem(item) if folder and folder['_id'] != item['folderId']: self._model.move(item, folder) if metadata: item = self._model.setMetadata(item, metadata) return item @access.user(scope=TokenScope.DATA_WRITE) @filtermodel(model=ItemModel) @autoDescribeRoute( Description('Set metadata fields on an item.') .responseClass('Item') .notes('Set metadata fields to null in order to delete them.') .modelParam('id', model=ItemModel, level=AccessType.WRITE) .jsonParam('metadata', 'A JSON object containing the metadata keys to add', paramType='body', requireObject=True) .param('allowNull', 'Whether "null" is allowed as a metadata value.', required=False, dataType='boolean', default=False) .errorResponse(('ID was invalid.', 'Invalid JSON passed in request body.', 'Metadata key name was invalid.')) .errorResponse('Write access was denied for the item.', 403) ) def setMetadata(self, item, metadata, allowNull): return self._model.setMetadata(item, metadata, allowNull=allowNull) @access.user(scope=TokenScope.DATA_WRITE) @filtermodel(ItemModel) @autoDescribeRoute( Description('Delete metadata fields on an item.') .responseClass('Item') .modelParam('id', model=ItemModel, level=AccessType.WRITE) .jsonParam( 'fields', 'A JSON list containing the metadata fields to delete', paramType='body', schema={ 'type': 'array', 'items': { 'type': 'string' } } ) .errorResponse(('ID was invalid.', 'Invalid JSON passed in request body.', 'Metadata key name was invalid.')) .errorResponse('Write access was denied for the item.', 403) ) def deleteMetadata(self, item, fields): return self._model.deleteMetadata(item, fields) def _downloadMultifileItem(self, item, user): setResponseHeader('Content-Type', 'application/zip') setContentDisposition(item['name'] + '.zip') def stream(): zip = ziputil.ZipGenerator(item['name']) for (path, file) in self._model.fileList(item, subpath=False): for data in zip.addFile(file, path): yield data yield zip.footer() return stream @access.public(scope=TokenScope.DATA_READ) @filtermodel(model=File) @autoDescribeRoute( Description('Get the files within an item.') .responseClass('File', array=True) .modelParam('id', model=ItemModel, level=AccessType.READ) .pagingParams(defaultSort='name') .errorResponse('ID was invalid.') .errorResponse('Read access was denied for the item.', 403) ) def getFiles(self, item, limit, offset, sort): return self._model.childFiles(item=item, limit=limit, offset=offset, sort=sort) @access.cookie @access.public(scope=TokenScope.DATA_READ) @autoDescribeRoute( Description('Download the contents of an item.') .modelParam('id', model=ItemModel, level=AccessType.READ) .param('offset', 'Byte offset into the file.', dataType='int', required=False, default=0) .param('format', 'If unspecified, items with one file are downloaded ' 'as that file, and other items are downloaded as a zip ' 'archive. If \'zip\', a zip archive is always sent.', required=False) .param('contentDisposition', 'Specify the Content-Disposition response ' 'header disposition-type value, only applied for single file ' 'items.', required=False, enum=['inline', 'attachment'], default='attachment') .param('extraParameters', 'Arbitrary data to send along with the ' 'download request, only applied for single file ' 'items.', required=False) # single file items could produce other types, too. .produces(['application/zip', 'application/octet-stream']) .errorResponse('ID was invalid.') .errorResponse('Read access was denied for the item.', 403) ) def download(self, item, offset, format, contentDisposition, extraParameters): user = self.getCurrentUser() files = list(self._model.childFiles(item=item, limit=2)) if format not in (None, '', 'zip'): raise RestException('Unsupported format: %s.' % format) if len(files) == 1 and format != 'zip': if contentDisposition not in {None, 'inline', 'attachment'}: raise RestException('Unallowed contentDisposition type "%s".' % contentDisposition) return File().download( files[0], offset, contentDisposition=contentDisposition, extraParameters=extraParameters) else: return self._downloadMultifileItem(item, user) @access.user(scope=TokenScope.DATA_WRITE) @autoDescribeRoute( Description('Delete an item by ID.') .modelParam('id', model=ItemModel, level=AccessType.WRITE) .errorResponse('ID was invalid.') .errorResponse('Write access was denied for the item.', 403) ) def deleteItem(self, item): self._model.remove(item) return {'message': 'Deleted item %s.' % item['name']} @access.public(scope=TokenScope.DATA_READ) @autoDescribeRoute( Description('Get the path to the root of the item\'s hierarchy.') .modelParam('id', model=ItemModel, level=AccessType.READ) .errorResponse('ID was invalid.') .errorResponse('Read access was denied for the item.', 403) ) def rootpath(self, item): return self._model.parentsToRoot(item, self.getCurrentUser()) @access.user(scope=TokenScope.DATA_WRITE) @filtermodel(model=ItemModel) @autoDescribeRoute( Description('Copy an item.') .notes('If no folderId parameter is specified, creates a copy of the item in ' 'its current containing folder.') .responseClass('Item') .modelParam('id', 'The ID of the original item.', model=ItemModel, level=AccessType.READ) .modelParam('folderId', 'The ID of the parent folder.', required=False, model=Folder, level=AccessType.WRITE, paramType='query') .param('name', 'Name for the new item.', required=False, strip=True) .param('description', 'Description for the new item.', required=False, strip=True) .errorResponse(('A parameter was invalid.', 'ID was invalid.')) .errorResponse('Read access was denied on the original item.\n\n' 'Write access was denied on the parent folder.', 403) ) def copyItem(self, item, folder, name, description): user = self.getCurrentUser() if folder is None: folder = Folder().load( id=item['folderId'], user=user, level=AccessType.WRITE, exc=True) return self._model.copyItem( item, creator=user, name=name, folder=folder, description=description)
def testAccessFlags(self): resp = self.request('/system/access_flag') self.assertStatusOk(resp) self.assertEqual(resp.json, {}) registerAccessFlag('my_key', name='hello', description='a custom flag') resp = self.request('/system/access_flag') self.assertStatusOk(resp) self.assertEqual( resp.json, { 'my_key': { 'name': 'hello', 'description': 'a custom flag', 'admin': False } }) self.users[1] = User().load(self.users[1]['_id'], force=True) user = self.users[1] # Manage custom access flags on an access controlled resource self.assertFalse(User().hasAccessFlags(user, user, flags=['my_key'])) # Admin should always have permission self.assertTrue(User().hasAccessFlags(user, self.users[0], flags=['my_key'])) # Test the requireAccessFlags method with self.assertRaises(AccessException): User().requireAccessFlags(user, user=user, flags='my_key') User().requireAccessFlags(user, user=self.users[0], flags='my_key') acl = User().getFullAccessList(user) self.assertEqual(acl['users'][0]['flags'], []) # Test loadmodel requiredFlags argument via REST endpoint resp = self.request('/test_endpoints/loadmodel_with_flags/%s' % user['_id'], user=self.users[1]) self.assertStatus(resp, 403) user = User().setAccessList( self.users[0], access={ 'users': [{ 'id': self.users[1]['_id'], 'level': AccessType.ADMIN, 'flags': ['my_key', 'not a registered flag'] }], 'groups': [{ 'id': self.group['_id'], 'level': AccessType.ADMIN, 'flags': ['my_key'] }] }, save=True) resp = self.request('/test_endpoints/loadmodel_with_flags/%s' % user['_id'], user=self.users[1]) self.assertStatusOk(resp) self.assertEqual(resp.json, 'success') # Only registered flags should be stored acl = User().getFullAccessList(user) self.assertEqual(acl['users'][0]['flags'], ['my_key']) self.assertTrue(User().hasAccessFlags(user, user, flags=['my_key'])) # Create an admin-only access flag registerAccessFlag('admin_flag', name='admin flag', admin=True) # Non-admin shouldn't be able to set it user = User().setAccessList(self.users[0], access={ 'users': [{ 'id': self.users[1]['_id'], 'level': AccessType.ADMIN, 'flags': ['admin_flag'] }], 'groups': [] }, save=True, user=self.users[1]) acl = User().getFullAccessList(user) self.assertEqual(acl['users'][0]['flags'], []) # Admin user should be able to set it user = User().setAccessList(self.users[1], access={ 'users': [{ 'id': self.users[1]['_id'], 'level': AccessType.ADMIN, 'flags': ['admin_flag'] }], 'groups': [{ 'id': self.group['_id'], 'level': AccessType.ADMIN, 'flags': ['admin_flag'] }] }, save=True, user=self.users[0]) acl = User().getFullAccessList(user) self.assertEqual(acl['users'][0]['flags'], ['admin_flag']) # An already-enabled admin-only flag should stay enabled for non-admin user user = User().setAccessList(self.users[1], access={ 'users': [{ 'id': self.users[1]['_id'], 'level': AccessType.ADMIN, 'flags': ['my_key', 'admin_flag'] }], 'groups': [{ 'id': self.group['_id'], 'level': AccessType.ADMIN, 'flags': ['admin_flag'] }] }, save=True, user=self.users[1]) acl = User().getFullAccessList(user) self.assertEqual(set(acl['users'][0]['flags']), {'my_key', 'admin_flag'}) self.assertEqual(acl['groups'][0]['flags'], ['admin_flag']) # Test setting public flags on a collection and folder collectionModel = Collection() folderModel = Folder() itemModel = Item() collection = collectionModel.createCollection('coll', creator=self.users[0], public=True) folder = folderModel.createFolder(collection, 'folder', parentType='collection', creator=self.users[0]) # Add an item to the folder so we can test AclMixin flag behavior item = itemModel.createItem(folder=folder, name='test', creator=self.users[0]) folder = folderModel.setUserAccess(folder, self.users[1], level=AccessType.ADMIN, save=True, currentUser=self.users[0]) with self.assertRaises(AccessException): collectionModel.requireAccessFlags(collection, user=None, flags='my_key') # Test AclMixin flag behavior with self.assertRaises(AccessException): itemModel.requireAccessFlags(item, user=None, flags='my_key') self.assertFalse( itemModel.hasAccessFlags(item, user=None, flags='my_key')) collection = collectionModel.setAccessList(collection, access=collection['access'], save=True, recurse=True, user=self.users[0], publicFlags=['my_key']) collectionModel.requireAccessFlags(collection, user=None, flags='my_key') # Make sure recursive setting of public flags worked folder = folderModel.load(folder['_id'], force=True) self.assertEqual(folder['publicFlags'], ['my_key']) itemModel.requireAccessFlags(item, user=None, flags='my_key') # Non-admin shouldn't be able to set admin-only public flags folder = folderModel.setPublicFlags(folder, flags=['admin_flag'], user=self.users[1], save=True) self.assertEqual(folder['publicFlags'], []) # Admin users should be able to set admin-only public flags folder = folderModel.setPublicFlags(folder, flags=['admin_flag'], user=self.users[0], save=True, append=True) self.assertEqual(folder['publicFlags'], ['admin_flag']) # Non-admin users can set admin-only public flags if they are already enabled folder = folderModel.setPublicFlags(folder, flags=['admin_flag', 'my_key'], user=self.users[1], save=True) self.assertEqual(set(folder['publicFlags']), {'admin_flag', 'my_key'}) # Test "force" options folder = folderModel.setPublicFlags(folder, flags='admin_flag', force=True, save=True) self.assertEqual(folder['publicFlags'], ['admin_flag']) folder = folderModel.setAccessList(folder, access={ 'users': [{ 'id': self.users[1]['_id'], 'level': AccessType.ADMIN, 'flags': ['my_key', 'admin_flag'] }], 'groups': [] }, save=True, force=True) folderModel.requireAccessFlags(folder, user=self.users[1], flags='my_key') folder = folderModel.setUserAccess(folder, self.users[1], level=AccessType.READ, save=True, force=True, flags=[]) self.assertFalse( folderModel.hasAccessFlags(folder, self.users[1], flags='my_key')) folder = folderModel.setGroupAccess(folder, self.group, level=AccessType.READ, save=True, force=True, flags='my_key') folderModel.requireAccessFlags(folder, user=self.users[1], flags='my_key') # Testing with flags=None should give sensible behavior folderModel.requireAccessFlags(folder, user=None, flags=None) # Test filtering results by access flags (both ACModel and AclMixin) for model, doc in ((folderModel, folder), (itemModel, item)): cursor = model.find({}) self.assertGreater(len(list(cursor)), 0) cursor = model.find({}) filtered = list( model.filterResultsByPermission(cursor, user=None, level=AccessType.READ, flags='my_key')) self.assertEqual(len(filtered), 0) cursor = model.find({}) filtered = list( model.filterResultsByPermission(cursor, user=self.users[1], level=AccessType.READ, flags=('my_key', 'admin_flag'))) self.assertEqual(len(filtered), 1) self.assertEqual(filtered[0]['_id'], doc['_id'])
class LabelResource(Resource): def __init__(self): super().__init__() self.resourceName = 'label' self.ann_file_name = "annotation.json" self.coll_m = Collection() self.file_m = File() self.folder_m = Folder() self.item_m = Item() self.upload_m = Upload() self.asset_m = Assetstore() self.setupRoutes() def setupRoutes(self): self.route('GET', (), handler=self.getLabelList) self.route('GET', (':label_id',), self.getLabel) self.route('GET', ('meta',), self.getLabelMeta) self.route('GET', ('create',), self.createLabelFile) self.route('GET', ('by_name',), self.getLabelByName) self.route('POST', (), self.postLabel) # ############# PUBLIC METHODS ################## @access.public @autoDescribeRoute( Description('Get label list').param('assign_id', 'assignment folder id')) @rest.rawResponse @trace def getLabelList(self, assign_id): files = self.folder_m.fileList(assign_id, user=self.getCurrentUser(), data=False, includeMetadata=True, mimeFilter=['application/json']) files = list(files) cherrypy.response.headers["Content-Type"] = "application/json" return dumps(files) @access.public @autoDescribeRoute( Description('Create a new label file inside the label folder if it doesnt exist') .param('assign_id', 'the parent folder id') .param('name', 'image name for which we are creating this label file')) @rest.rawResponse @trace def createLabelFile(self, assign_id, name): p_folder = self.folder_m.load(id=assign_id, user=self.getCurrentUser(), level=AccessType.WRITE) label_folder = find_folder(p_folder=p_folder, name=name, user=self.getCurrentUser(), desc="Label Folder", create=True) file = find_file(p_folder=label_folder, name=self.ann_file_name, user=self.getCurrentUser(), assetstore=self.asset_m.getCurrent(), create=False) if not file: file = create_new_file(p_folder=label_folder, name=self.ann_file_name, user=self.getCurrentUser(), assetstore=self.asset_m.getCurrent()) config_file = self.__find_config(assign_id) if not config_file: printFail("No config file found") return errorMessage("No config file found") else: res = copy_file(src_file=config_file, dest_file=file, user=self.getCurrentUser()) return dumps({ "label_id": res['fileId'] }) return dumps({ "label_id": file['_id'] }) @access.public @autoDescribeRoute( Description('Get labels by file_name') .param('name', 'label file name') .param('assign_id', 'the assignment id')) @rest.rawResponse @trace def getLabelByName(self, name, assign_id): p_folder = self.folder_m.load(assign_id, user=self.getCurrentUser(), level=AccessType.READ) label_folder = find_folder(p_folder=p_folder, name=name, user=self.getCurrentUser(), desc="Label Folder", create=True) # this file is created in <assign_folder>/<label_folder>/assignment.json file = find_file(p_folder=label_folder, name=self.ann_file_name, user=self.getCurrentUser(), assetstore=self.asset_m.getCurrent(), create=False) cherrypy.response.headers["Content-Type"] = "application/json" if file: return self.file_m.download(file) else: return dumps({}) @access.public @autoDescribeRoute( Description('Get label file by id') .param('label_id', 'label file id')) @rest.rawResponse @trace def getLabel(self, label_id): file = self.file_m.load(label_id, level=AccessType.READ, user=self.getCurrentUser()) printOk2(file) cherrypy.response.headers["Content-Type"] = "application/json" return self.file_m.download(file) @access.public @autoDescribeRoute( Description('Get label meta data by id') .param('label_id', 'label file id')) @trace def getLabelMeta(self, label_id): file = self.file_m.load(label_id, level=AccessType.READ, user=self.getCurrentUser()) cherrypy.response.headers["Content-Type"] = "application/json" return dumps(file) @access.public @autoDescribeRoute( Description('Post to label file by id') .param('label_id', 'label file id') .param('labels', 'labels to be updated')) @rest.rawResponse @trace def postLabel(self, label_id, labels): file = self.file_m.load(label_id, level=AccessType.WRITE, user=self.getCurrentUser()) cherrypy.response.headers["Content-Type"] = "application/json" params = {'labels': json.loads(labels)} data = json.dumps(params, indent=2, sort_keys=True) upload = writeData(self.getCurrentUser(), file, data) printOk2(file) printOk(upload) return dumps(upload) # ############# PRIVATE METHODS ################## def __create_new_file(self, folder, file_name): item = self.item_m.createItem(file_name, creator=self.getCurrentUser(), folder=folder, description='label file', reuseExisting=False) file = self.file_m.createFile(size=0, item=item, name=file_name, creator=self.getCurrentUser(), assetstore=self.asset_m.getCurrent(), mimeType="application/json") return file @staticmethod def __get_owner_id(folder): aclList = Folder().getFullAccessList(folder) for acl in aclList['users']: if acl['level'] == AccessType.ADMIN: return str(acl['id']) return None def __get_config_folder(self, label_folder_id): label_folder = Folder().load(label_folder_id, user=self.getCurrentUser(), level=AccessType.READ) ownerId = self.__get_owner_id(label_folder) config_folder = self.folder_m.load(label_folder['meta'][ownerId], level=AccessType.READ, user=self.getCurrentUser()) return config_folder def __find_config(self, folder_id): folder = self.__get_config_folder(folder_id) printOk2("Config folder {}".format(folder)) files = self.folder_m.fileList(folder, self.getCurrentUser(), data=False) for file_path, file in files: printOk(file) if file['name'] == "config.json": return file def __findFolder(self, p_folder, name, desc="", create=False): """ Find folder by name. If not found create the folder :param p_folder: parent folder :param name: name of the folder you want to find inside the parent folder :return: folder doc """ folder = list(self.folder_m.find({'folderId': p_folder['_id'], 'name': name}).limit(1)) if not folder: # check if you are allowed to create, else return nothing if create: folder = self.folder_m.createFolder(parent=p_folder, name=name, creator=self.getCurrentUser(), description=desc, reuseExisting=True) else: return None return folder def __findFile(self, folder, file_name): item = list(self.item_m.find({'folderId': folder['_id'], 'name': file_name}).limit(1)) if not item: return None item = item[0] file = list(self.file_m.find({'itemId': item['_id']}).limit(1)) if not file: return None return file[0]
class Item(Resource): def __init__(self): super(Item, self).__init__() self.resourceName = 'item' self._model = ItemModel() self.route('DELETE', (':id',), self.deleteItem) self.route('GET', (), self.find) self.route('GET', (':id',), self.getItem) self.route('GET', (':id', 'files'), self.getFiles) self.route('GET', (':id', 'download'), self.download) self.route('GET', (':id', 'rootpath'), self.rootpath) self.route('POST', (), self.createItem) self.route('PUT', (':id',), self.updateItem) self.route('POST', (':id', 'copy'), self.copyItem) self.route('PUT', (':id', 'metadata'), self.setMetadata) self.route('DELETE', (':id', 'metadata'), self.deleteMetadata) @access.public(scope=TokenScope.DATA_READ) @filtermodel(model=ItemModel) @autoDescribeRoute( Description('List or search for items.') .notes('You must pass either a "itemId" or "text" field ' 'to specify how you are searching for items. ' 'If you omit one of these parameters the request will fail and respond : ' '"Invalid search mode."') .responseClass('Item', array=True) .param('folderId', 'Pass this to list all items in a folder.', required=False) .param('text', 'Pass this to perform a full text search for items.', required=False) .param('name', 'Pass to lookup an item by exact name match. Must ' 'pass folderId as well when using this.', required=False) .pagingParams(defaultSort='lowerName') .errorResponse() .errorResponse('Read access was denied on the parent folder.', 403) ) def find(self, folderId, text, name, limit, offset, sort): """ Get a list of items with given search parameters. Currently accepted search modes are: 1. Searching by folderId, with optional additional filtering by the name field (exact match) or using full text search within a single parent folder. Pass a "name" parameter or "text" parameter to invoke these additional filters. 2. Searching with full text search across all items in the system. Simply pass a "text" parameter for this mode. """ user = self.getCurrentUser() if folderId: folder = Folder().load( id=folderId, user=user, level=AccessType.READ, exc=True) filters = {} if text: filters['$text'] = { '$search': text } if name: filters['name'] = name return Folder().childItems( folder=folder, limit=limit, offset=offset, sort=sort, filters=filters) elif text is not None: return self._model.textSearch( text, user=user, limit=limit, offset=offset, sort=sort) else: raise RestException('Invalid search mode.') @access.public(scope=TokenScope.DATA_READ) @filtermodel(model=ItemModel) @autoDescribeRoute( Description('Get an item by ID.') .responseClass('Item') .modelParam('id', model=ItemModel, level=AccessType.READ) .errorResponse('ID was invalid.') .errorResponse('Read access was denied for the item.', 403) ) def getItem(self, item): return item @access.user(scope=TokenScope.DATA_WRITE) @filtermodel(model=ItemModel) @autoDescribeRoute( Description('Create a new item.') .responseClass('Item') .modelParam('folderId', 'The ID of the parent folder.', model=Folder, level=AccessType.WRITE, paramType='query') .param('name', 'Name for the item.', strip=True) .param('description', 'Description for the item.', required=False, default='', strip=True) .param('reuseExisting', 'Return existing item (by name) if it exists.', required=False, dataType='boolean', default=False) .jsonParam('metadata', 'A JSON object containing the metadata keys to add', paramType='form', requireObject=True, required=False) .errorResponse() .errorResponse('Write access was denied on the parent folder.', 403) ) def createItem(self, folder, name, description, reuseExisting, metadata): newItem = self._model.createItem( folder=folder, name=name, creator=self.getCurrentUser(), description=description, reuseExisting=reuseExisting) if metadata: newItem = self._model.setMetadata(newItem, metadata) return newItem @access.user(scope=TokenScope.DATA_WRITE) @filtermodel(model=ItemModel) @autoDescribeRoute( Description('Edit an item or move it to another folder.') .responseClass('Item') .modelParam('id', model=ItemModel, level=AccessType.WRITE) .param('name', 'Name for the item.', required=False, strip=True) .param('description', 'Description for the item.', required=False) .modelParam('folderId', 'Pass this to move the item to a new folder.', model=Folder, required=False, paramType='query', level=AccessType.WRITE) .jsonParam('metadata', 'A JSON object containing the metadata keys to add', paramType='form', requireObject=True, required=False) .errorResponse('ID was invalid.') .errorResponse('Write access was denied for the item or folder.', 403) ) def updateItem(self, item, name, description, folder, metadata): if name is not None: item['name'] = name if description is not None: item['description'] = description self._model.updateItem(item) if folder and folder['_id'] != item['folderId']: self._model.move(item, folder) if metadata: item = self._model.setMetadata(item, metadata) return item @access.user(scope=TokenScope.DATA_WRITE) @filtermodel(model=ItemModel) @autoDescribeRoute( Description('Set metadata fields on an item.') .responseClass('Item') .notes('Set metadata fields to null in order to delete them.') .modelParam('id', model=ItemModel, level=AccessType.WRITE) .jsonParam('metadata', 'A JSON object containing the metadata keys to add', paramType='body', requireObject=True) .param('allowNull', 'Whether "null" is allowed as a metadata value.', required=False, dataType='boolean', default=False) .errorResponse(('ID was invalid.', 'Invalid JSON passed in request body.', 'Metadata key name was invalid.')) .errorResponse('Write access was denied for the item.', 403) ) def setMetadata(self, item, metadata, allowNull): return self._model.setMetadata(item, metadata, allowNull=allowNull) @access.user(scope=TokenScope.DATA_WRITE) @filtermodel(ItemModel) @autoDescribeRoute( Description('Delete metadata fields on an item.') .responseClass('Item') .modelParam('id', model=ItemModel, level=AccessType.WRITE) .jsonParam( 'fields', 'A JSON list containing the metadata fields to delete', paramType='body', schema={ 'type': 'array', 'items': { 'type': 'string' } } ) .errorResponse(('ID was invalid.', 'Invalid JSON passed in request body.', 'Metadata key name was invalid.')) .errorResponse('Write access was denied for the item.', 403) ) def deleteMetadata(self, item, fields): return self._model.deleteMetadata(item, fields) def _downloadMultifileItem(self, item, user): setResponseHeader('Content-Type', 'application/zip') setContentDisposition(item['name'] + '.zip') def stream(): zip = ziputil.ZipGenerator(item['name']) for (path, file) in self._model.fileList(item, subpath=False): for data in zip.addFile(file, path): yield data yield zip.footer() return stream @access.public(scope=TokenScope.DATA_READ) @filtermodel(model=File) @autoDescribeRoute( Description('Get the files within an item.') .responseClass('File', array=True) .modelParam('id', model=ItemModel, level=AccessType.READ) .pagingParams(defaultSort='name') .errorResponse('ID was invalid.') .errorResponse('Read access was denied for the item.', 403) ) def getFiles(self, item, limit, offset, sort): return self._model.childFiles(item=item, limit=limit, offset=offset, sort=sort) @access.cookie @access.public(scope=TokenScope.DATA_READ) @autoDescribeRoute( Description('Download the contents of an item.') .modelParam('id', model=ItemModel, level=AccessType.READ) .param('offset', 'Byte offset into the file.', dataType='int', required=False, default=0) .param('format', 'If unspecified, items with one file are downloaded ' 'as that file, and other items are downloaded as a zip ' 'archive. If \'zip\', a zip archive is always sent.', required=False) .param('contentDisposition', 'Specify the Content-Disposition response ' 'header disposition-type value, only applied for single file ' 'items.', required=False, enum=['inline', 'attachment'], default='attachment') .param('extraParameters', 'Arbitrary data to send along with the ' 'download request, only applied for single file ' 'items.', required=False) # single file items could produce other types, too. .produces(['application/zip', 'application/octet-stream']) .errorResponse('ID was invalid.') .errorResponse('Read access was denied for the item.', 403) ) def download(self, item, offset, format, contentDisposition, extraParameters): user = self.getCurrentUser() files = list(self._model.childFiles(item=item, limit=2)) if format not in (None, '', 'zip'): raise RestException('Unsupported format: %s.' % format) if len(files) == 1 and format != 'zip': if contentDisposition not in {None, 'inline', 'attachment'}: raise RestException('Unallowed contentDisposition type "%s".' % contentDisposition) return File().download( files[0], offset, contentDisposition=contentDisposition, extraParameters=extraParameters) else: return self._downloadMultifileItem(item, user) @access.user(scope=TokenScope.DATA_WRITE) @autoDescribeRoute( Description('Delete an item by ID.') .modelParam('id', model=ItemModel, level=AccessType.WRITE) .errorResponse('ID was invalid.') .errorResponse('Write access was denied for the item.', 403) ) def deleteItem(self, item): self._model.remove(item) return {'message': 'Deleted item %s.' % item['name']} @access.public(scope=TokenScope.DATA_READ) @autoDescribeRoute( Description('Get the path to the root of the item\'s hierarchy.') .modelParam('id', model=ItemModel, level=AccessType.READ) .errorResponse('ID was invalid.') .errorResponse('Read access was denied for the item.', 403) ) def rootpath(self, item): return self._model.parentsToRoot(item, self.getCurrentUser()) @access.user(scope=TokenScope.DATA_WRITE) @filtermodel(model=ItemModel) @autoDescribeRoute( Description('Copy an item.') .notes('If no folderId parameter is specified, creates a copy of the item in ' 'its current containing folder.') .responseClass('Item') .modelParam('id', 'The ID of the original item.', model=ItemModel, level=AccessType.READ) .modelParam('folderId', 'The ID of the parent folder.', required=False, model=Folder, level=AccessType.WRITE) .param('name', 'Name for the new item.', required=False, strip=True) .param('description', 'Description for the new item.', required=False, strip=True) .errorResponse(('A parameter was invalid.', 'ID was invalid.')) .errorResponse('Read access was denied on the original item.\n\n' 'Write access was denied on the parent folder.', 403) ) def copyItem(self, item, folder, name, description): user = self.getCurrentUser() if folder is None: folder = Folder().load( id=item['folderId'], user=user, level=AccessType.WRITE, exc=True) return self._model.copyItem( item, creator=user, name=name, folder=folder, description=description)
def testAccessFlags(self): resp = self.request('/system/access_flag') self.assertStatusOk(resp) self.assertEqual(resp.json, {}) registerAccessFlag('my_key', name='hello', description='a custom flag') resp = self.request('/system/access_flag') self.assertStatusOk(resp) self.assertEqual(resp.json, { 'my_key': { 'name': 'hello', 'description': 'a custom flag', 'admin': False } }) self.users[1] = User().load(self.users[1]['_id'], force=True) user = self.users[1] # Manage custom access flags on an access controlled resource self.assertFalse(User().hasAccessFlags(user, user, flags=['my_key'])) # Admin should always have permission self.assertTrue(User().hasAccessFlags(user, self.users[0], flags=['my_key'])) # Test the requireAccessFlags method with self.assertRaises(AccessException): User().requireAccessFlags(user, user=user, flags='my_key') User().requireAccessFlags(user, user=self.users[0], flags='my_key') acl = User().getFullAccessList(user) self.assertEqual(acl['users'][0]['flags'], []) # Test loadmodel requiredFlags argument via REST endpoint resp = self.request( '/test_endpoints/loadmodel_with_flags/%s' % user['_id'], user=self.users[1]) self.assertStatus(resp, 403) user = User().setAccessList(self.users[0], access={ 'users': [{ 'id': self.users[1]['_id'], 'level': AccessType.ADMIN, 'flags': ['my_key', 'not a registered flag'] }], 'groups': [{ 'id': self.group['_id'], 'level': AccessType.ADMIN, 'flags': ['my_key'] }] }, save=True) resp = self.request( '/test_endpoints/loadmodel_with_flags/%s' % user['_id'], user=self.users[1]) self.assertStatusOk(resp) self.assertEqual(resp.json, 'success') # Only registered flags should be stored acl = User().getFullAccessList(user) self.assertEqual(acl['users'][0]['flags'], ['my_key']) self.assertTrue(User().hasAccessFlags(user, user, flags=['my_key'])) # Create an admin-only access flag registerAccessFlag('admin_flag', name='admin flag', admin=True) # Non-admin shouldn't be able to set it user = User().setAccessList(self.users[0], access={ 'users': [{ 'id': self.users[1]['_id'], 'level': AccessType.ADMIN, 'flags': ['admin_flag'] }], 'groups': [] }, save=True, user=self.users[1]) acl = User().getFullAccessList(user) self.assertEqual(acl['users'][0]['flags'], []) # Admin user should be able to set it user = User().setAccessList(self.users[1], access={ 'users': [{ 'id': self.users[1]['_id'], 'level': AccessType.ADMIN, 'flags': ['admin_flag'] }], 'groups': [{ 'id': self.group['_id'], 'level': AccessType.ADMIN, 'flags': ['admin_flag'] }] }, save=True, user=self.users[0]) acl = User().getFullAccessList(user) self.assertEqual(acl['users'][0]['flags'], ['admin_flag']) # An already-enabled admin-only flag should stay enabled for non-admin user user = User().setAccessList(self.users[1], access={ 'users': [{ 'id': self.users[1]['_id'], 'level': AccessType.ADMIN, 'flags': ['my_key', 'admin_flag'] }], 'groups': [{ 'id': self.group['_id'], 'level': AccessType.ADMIN, 'flags': ['admin_flag'] }] }, save=True, user=self.users[1]) acl = User().getFullAccessList(user) self.assertEqual(set(acl['users'][0]['flags']), {'my_key', 'admin_flag'}) self.assertEqual(acl['groups'][0]['flags'], ['admin_flag']) # Test setting public flags on a collection and folder collectionModel = Collection() folderModel = Folder() itemModel = Item() collection = collectionModel.createCollection('coll', creator=self.users[0], public=True) folder = folderModel.createFolder( collection, 'folder', parentType='collection', creator=self.users[0]) # Add an item to the folder so we can test AclMixin flag behavior item = itemModel.createItem(folder=folder, name='test', creator=self.users[0]) folder = folderModel.setUserAccess( folder, self.users[1], level=AccessType.ADMIN, save=True, currentUser=self.users[0]) with self.assertRaises(AccessException): collectionModel.requireAccessFlags(collection, user=None, flags='my_key') # Test AclMixin flag behavior with self.assertRaises(AccessException): itemModel.requireAccessFlags(item, user=None, flags='my_key') self.assertFalse(itemModel.hasAccessFlags(item, user=None, flags='my_key')) collection = collectionModel.setAccessList( collection, access=collection['access'], save=True, recurse=True, user=self.users[0], publicFlags=['my_key']) collectionModel.requireAccessFlags(collection, user=None, flags='my_key') # Make sure recursive setting of public flags worked folder = folderModel.load(folder['_id'], force=True) self.assertEqual(folder['publicFlags'], ['my_key']) itemModel.requireAccessFlags(item, user=None, flags='my_key') # Non-admin shouldn't be able to set admin-only public flags folder = folderModel.setPublicFlags( folder, flags=['admin_flag'], user=self.users[1], save=True) self.assertEqual(folder['publicFlags'], []) # Admin users should be able to set admin-only public flags folder = folderModel.setPublicFlags( folder, flags=['admin_flag'], user=self.users[0], save=True, append=True) self.assertEqual(folder['publicFlags'], ['admin_flag']) # Non-admin users can set admin-only public flags if they are already enabled folder = folderModel.setPublicFlags( folder, flags=['admin_flag', 'my_key'], user=self.users[1], save=True) self.assertEqual(set(folder['publicFlags']), {'admin_flag', 'my_key'}) # Test "force" options folder = folderModel.setPublicFlags(folder, flags='admin_flag', force=True, save=True) self.assertEqual(folder['publicFlags'], ['admin_flag']) folder = folderModel.setAccessList(folder, access={ 'users': [{ 'id': self.users[1]['_id'], 'level': AccessType.ADMIN, 'flags': ['my_key', 'admin_flag'] }], 'groups': [] }, save=True, force=True) folderModel.requireAccessFlags(folder, user=self.users[1], flags='my_key') folder = folderModel.setUserAccess( folder, self.users[1], level=AccessType.READ, save=True, force=True, flags=[]) self.assertFalse(folderModel.hasAccessFlags(folder, self.users[1], flags='my_key')) folder = folderModel.setGroupAccess( folder, self.group, level=AccessType.READ, save=True, force=True, flags='my_key') folderModel.requireAccessFlags(folder, user=self.users[1], flags='my_key') # Testing with flags=None should give sensible behavior folderModel.requireAccessFlags(folder, user=None, flags=None) # Test filtering results by access flags (both ACModel and AclMixin) for model, doc in ((folderModel, folder), (itemModel, item)): cursor = model.find({}) self.assertGreater(len(list(cursor)), 0) cursor = model.find({}) filtered = list(model.filterResultsByPermission( cursor, user=None, level=AccessType.READ, flags='my_key')) self.assertEqual(len(filtered), 0) cursor = model.find({}) filtered = list(model.filterResultsByPermission( cursor, user=self.users[1], level=AccessType.READ, flags=('my_key', 'admin_flag'))) self.assertEqual(len(filtered), 1) self.assertEqual(filtered[0]['_id'], doc['_id'])
class HdfsAssetstoreResource(Resource): def __init__(self): super(HdfsAssetstoreResource, self).__init__() self.resourceName = 'hdfs_assetstore' self.route('PUT', (':id', 'import'), self.importData) self.folderModel = Folder() # Save to avoid many lookups self.itemModel = Item() self.fileModel = File() def _importFile(self, parent, name, user, assetstore, node): item = self.itemModel.findOne({ 'folderId': parent['_id'], 'name': name }) if item is None: item = self.itemModel.createItem( name=name, creator=user, folder=parent) file = self.fileModel.findOne({ 'name': name, 'itemId': item['_id'] }) if file is None: file = self.fileModel.createFile( creator=user, item=item, name=name, size=node['length'], assetstore=assetstore, mimeType=None, saveFile=False) file['hdfs'] = { 'imported': True, 'path': node['path'] } self.fileModel.save(file) def _importData(self, parentType, parent, assetstore, client, path, ctx, user): for node in client.ls([path]): ctx.update(message='Importing ' + node['path']) name = posixpath.basename(node['path']) if node['file_type'] == 'd': folder = self.folderModel.findOne({ 'parentId': parent['_id'], 'name': name, 'parentCollection': parentType }) if folder is None: folder = self.folderModel.createFolder( parent, name, parentType=parentType, creator=user) self._importData('folder', folder, assetstore, client, node['path'], ctx, user) elif node['file_type'] == 'f' and parentType == 'folder': self._importFile(parent, name, user, assetstore, node) @access.admin @loadmodel(model='assetstore') @describeRoute( Description('Import a data hierarchy from an HDFS instance.') .notes('Only site administrators may use this endpoint.') .param('id', 'The ID of the assetstore representing the HDFS instance.', paramType='path') .param('parentId', 'The ID of the parent object in the Girder data ' 'hierarchy under which to import the files.') .param('parentType', 'The type of the parent object to import into.', enum=('folder', 'user', 'collection'), required=False) .param('path', 'Root of the directory structure (relative to the root ' 'of the HDFS) to import.') .param('progress', 'Whether to record progress on this operation (' 'default=False)', required=False, dataType='boolean') .errorResponse() .errorResponse('You are not an administrator.', 403) ) def importData(self, assetstore, params): self.requireParams(('parentId', 'path'), params) user = self.getCurrentUser() parentType = params.get('parentType', 'folder') if parentType not in ('user', 'collection', 'folder'): raise RestException('Invalid parentType.') parent = self.model(parentType).load(params['parentId'], force=True, exc=True) progress = self.boolParam('progress', params, default=False) client = HdfsClient( host=assetstore['hdfs']['host'], port=assetstore['hdfs']['port'], use_trash=False) path = params['path'] with ProgressContext(progress, user=user, title='Importing data from HDFS') as ctx: try: self._importData(parentType, parent, assetstore, client, path, ctx, user) except FileNotFoundException: raise RestException('File not found: %s.' % path)
class Item(Resource): def __init__(self): super().__init__() self.resourceName = 'item' self._model = ItemModel() self.route('DELETE', (':id',), self.deleteItem) self.route('GET', (), self.find) self.route('GET', (':id',), self.getItem) self.route('GET', (':id', 'files'), self.getFiles) self.route('GET', (':id', 'download'), self.download) self.route('GET', (':id', 'rootpath'), self.rootpath) self.route('GET', (':id', 'position'), self.findPosition) self.route('POST', (), self.createItem) self.route('PUT', (':id',), self.updateItem) self.route('POST', (':id', 'copy'), self.copyItem) self.route('PUT', (':id', 'metadata'), self.setMetadata) self.route('DELETE', (':id', 'metadata'), self.deleteMetadata) #added by Shubhang on 23rd nov self.route('PUT', ('moveItems',), self.moveItems) @access.public(scope=TokenScope.DATA_READ) @filtermodel(model=ItemModel) @autoDescribeRoute( Description('List or search for items.') .notes('You must pass either a "folderId" or "text" field ' 'to specify how you are searching for items. ' 'If you omit one of these parameters the request will fail and respond : ' '"Invalid search mode."') .responseClass('Item', array=True) .param('folderId', 'Pass this to list all items in a folder.', required=False) .param('text', 'Pass this to perform a full text search for items.', required=False) .param('name', 'Pass to lookup an item by exact name match. Must ' 'pass folderId as well when using this.', required=False) .pagingParams(defaultSort='lowerName') .errorResponse() .errorResponse('Read access was denied on the parent folder.', 403) ) def find(self, folderId, text, name, limit, offset, sort): """ Get a list of items with given search parameters. Currently accepted search modes are: 1. Searching by folderId, with optional additional filtering by the name field (exact match) or using full text search within a single parent folder. Pass a "name" parameter or "text" parameter to invoke these additional filters. 2. Searching with full text search across all items in the system. Simply pass a "text" parameter for this mode. """ return self._find(folderId, text, name, limit, offset, sort) def _find(self, folderId, text, name, limit, offset, sort, filters=None): user = self.getCurrentUser() filters = (filters.copy() if filters else {}) if folderId: folder = Folder().load( id=folderId, user=user, level=AccessType.READ, exc=True) if text: filters['$text'] = { '$search': text } if name: filters['name'] = name return Folder().childItems( folder=folder, limit=limit, offset=offset, sort=sort, filters=filters) elif text is not None: return self._model.textSearch( text, user=user, limit=limit, offset=offset, sort=sort, filters=filters) else: raise RestException('Invalid search mode.') @access.public(scope=TokenScope.DATA_READ) @autoDescribeRoute( Description('Report the offset of an item in a list or search.') .notes('You must pass either a "folderId" or "text" field ' 'to specify how you are searching for items. ' 'If you omit one of these parameters the request will fail and respond : ' '"Invalid search mode."') .modelParam('id', model=ItemModel, level=AccessType.READ) .param('folderId', 'Pass this to list all items in a folder.', required=False) .param('text', 'Pass this to perform a full text search for items.', required=False) .param('name', 'Pass to lookup an item by exact name match. Must ' 'pass folderId as well when using this.', required=False) .param('sort', 'Field to sort the result set by.', default='lowerName', required=False, strip=True) .param('sortdir', 'Sort order: 1 for ascending, -1 for descending.', required=False, dataType='integer', enum=[SortDir.ASCENDING, SortDir.DESCENDING], default=SortDir.ASCENDING) .errorResponse() .errorResponse('Read access was denied on the parent folder.', 403) ) def findPosition(self, item, folderId, text, name, params): limit, offset, sort = self.getPagingParameters(params, 'lowerName') if len(sort) != 1 or sort[0][0] not in item: raise RestException('Invalid sort mode.') sortField, sortDir = sort[0] dir = '$lt' if sortDir == SortDir.ASCENDING else '$gt' filters = {'$or': [ {sortField: {dir: item.get(sortField)}}, # For items that have the same sort value, sort by _id. # Mongo may fall back to this as the final sort, but this isn't # documented. {sortField: item.get(sortField), '_id': {dir: item['_id']}} ]} # limit and offset don't affect the results. cursor = self._find(folderId, text, name, 1, 0, sort, filters) return cursor.count() @access.public(scope=TokenScope.DATA_READ) @filtermodel(model=ItemModel) @autoDescribeRoute( Description('Get an item by ID.') .responseClass('Item') .modelParam('id', model=ItemModel, level=AccessType.READ) .errorResponse('ID was invalid.') .errorResponse('Read access was denied for the item.', 403) ) def getItem(self, item): return item @access.user(scope=TokenScope.DATA_WRITE) @filtermodel(model=ItemModel) @autoDescribeRoute( Description('Create a new item.') .responseClass('Item') .modelParam('folderId', 'The ID of the parent folder.', model=Folder, level=AccessType.WRITE, paramType='query') .param('name', 'Name for the item.', strip=True) .param('description', 'Description for the item.', required=False, default='', strip=True) .param('reuseExisting', 'Return existing item (by name) if it exists.', required=False, dataType='boolean', default=False) .jsonParam('metadata', 'A JSON object containing the metadata keys to add', paramType='form', requireObject=True, required=False) .errorResponse() .errorResponse('Write access was denied on the parent folder.', 403) ) def createItem(self, folder, name, description, reuseExisting, metadata): newItem = self._model.createItem( folder=folder, name=name, creator=self.getCurrentUser(), description=description, reuseExisting=reuseExisting) if metadata: newItem = self._model.setMetadata(newItem, metadata) return newItem @access.user(scope=TokenScope.DATA_WRITE) @filtermodel(model=ItemModel) @autoDescribeRoute( Description('Edit an item or move it to another folder.') .responseClass('Item') .modelParam('id', model=ItemModel, level=AccessType.WRITE) .param('name', 'Name for the item.', required=False, strip=True) .param('description', 'Description for the item.', required=False) .modelParam('folderId', 'Pass this to move the item to a new folder.', model=Folder, required=False, paramType='query', level=AccessType.WRITE) .jsonParam('metadata', 'A JSON object containing the metadata keys to add', paramType='form', requireObject=True, required=False) .errorResponse('ID was invalid.') .errorResponse('Write access was denied for the item or folder.', 403) ) def updateItem(self, item, name, description, folder, metadata): #logging.error(Description.modelParam(self,name='5fb637c698dc0d7acfe89190', model=ItemModel, level=AccessType.WRITE)) # logging.error(item) if name is not None: item['name'] = name if description is not None: item['description'] = description self._model.updateItem(item) if folder and folder['_id'] != item['folderId']: self._model.move(item, folder) if metadata: item = self._model.setMetadata(item, metadata) return item @access.user(scope=TokenScope.DATA_WRITE) @filtermodel(model=ItemModel) @autoDescribeRoute( Description('Set metadata fields on an item.') .responseClass('Item') .notes('Set metadata fields to null in order to delete them.') .modelParam('id', model=ItemModel, level=AccessType.WRITE) .jsonParam('metadata', 'A JSON object containing the metadata keys to add', paramType='body', requireObject=True) .param('allowNull', 'Whether "null" is allowed as a metadata value.', required=False, dataType='boolean', default=False) .errorResponse(('ID was invalid.', 'Invalid JSON passed in request body.', 'Metadata key name was invalid.')) .errorResponse('Write access was denied for the item.', 403) ) def setMetadata(self, item, metadata, allowNull): return self._model.setMetadata(item, metadata, allowNull=allowNull) @access.user(scope=TokenScope.DATA_WRITE) @filtermodel(ItemModel) @autoDescribeRoute( Description('Delete metadata fields on an item.') .responseClass('Item') .modelParam('id', model=ItemModel, level=AccessType.WRITE) .jsonParam( 'fields', 'A JSON list containing the metadata fields to delete', paramType='body', schema={ 'type': 'array', 'items': { 'type': 'string' } } ) .errorResponse(('ID was invalid.', 'Invalid JSON passed in request body.', 'Metadata key name was invalid.')) .errorResponse('Write access was denied for the item.', 403) ) def deleteMetadata(self, item, fields): return self._model.deleteMetadata(item, fields) def _downloadMultifileItem(self, item, user): setResponseHeader('Content-Type', 'application/zip') setContentDisposition(item['name'] + '.zip') def stream(): zip = ziputil.ZipGenerator(item['name']) for (path, file) in self._model.fileList(item, subpath=False): for data in zip.addFile(file, path): yield data yield zip.footer() return stream @access.public(scope=TokenScope.DATA_READ) @filtermodel(model=File) @autoDescribeRoute( Description('Get the files within an item.') .responseClass('File', array=True) .modelParam('id', model=ItemModel, level=AccessType.READ) .pagingParams(defaultSort='name') .errorResponse('ID was invalid.') .errorResponse('Read access was denied for the item.', 403) ) def getFiles(self, item, limit, offset, sort): return self._model.childFiles(item=item, limit=limit, offset=offset, sort=sort) @access.public(scope=TokenScope.DATA_READ, cookie=True) @autoDescribeRoute( Description('Download the contents of an item.') .modelParam('id', model=ItemModel, level=AccessType.READ) .param('offset', 'Byte offset into the file.', dataType='int', required=False, default=0) .param('format', 'If unspecified, items with one file are downloaded ' 'as that file, and other items are downloaded as a zip ' "archive. If 'zip', a zip archive is always sent.", required=False) .param('contentDisposition', 'Specify the Content-Disposition response ' 'header disposition-type value, only applied for single file ' 'items.', required=False, enum=['inline', 'attachment'], default='attachment') .param('extraParameters', 'Arbitrary data to send along with the ' 'download request, only applied for single file ' 'items.', required=False) # single file items could produce other types, too. .produces(['application/zip', 'application/octet-stream']) .errorResponse('ID was invalid.') .errorResponse('Read access was denied for the item.', 403) ) def download(self, item, offset, format, contentDisposition, extraParameters): user = self.getCurrentUser() files = list(self._model.childFiles(item=item, limit=2)) if format not in (None, '', 'zip'): raise RestException('Unsupported format: %s.' % format) if len(files) == 1 and format != 'zip': if contentDisposition not in {None, 'inline', 'attachment'}: raise RestException('Unallowed contentDisposition type "%s".' % contentDisposition) return File().download( files[0], offset, contentDisposition=contentDisposition, extraParameters=extraParameters) else: return self._downloadMultifileItem(item, user) @access.user(scope=TokenScope.DATA_WRITE) @autoDescribeRoute( Description('Delete an item by ID.') .modelParam('id', model=ItemModel, level=AccessType.WRITE) .errorResponse('ID was invalid.') .errorResponse('Write access was denied for the item.', 403) ) def deleteItem(self, item): self._model.remove(item) return {'message': 'Deleted item %s.' % item['name']} @access.public(scope=TokenScope.DATA_READ) @autoDescribeRoute( Description("Get the path to the root of the item's hierarchy.") .modelParam('id', model=ItemModel, level=AccessType.READ) .errorResponse('ID was invalid.') .errorResponse('Read access was denied for the item.', 403) ) def rootpath(self, item): return self._model.parentsToRoot(item, self.getCurrentUser()) @access.user(scope=TokenScope.DATA_WRITE) @filtermodel(model=ItemModel) @autoDescribeRoute( Description('Copy an item.') .notes('If no folderId parameter is specified, creates a copy of the item in ' 'its current containing folder.') .responseClass('Item') .modelParam('id', 'The ID of the original item.', model=ItemModel, level=AccessType.READ) .modelParam('folderId', 'The ID of the parent folder.', required=False, model=Folder, level=AccessType.WRITE, paramType='query') .param('name', 'Name for the new item.', required=False, strip=True) .param('description', 'Description for the new item.', required=False, strip=True) .errorResponse(('A parameter was invalid.', 'ID was invalid.')) .errorResponse('Read access was denied on the original item.\n\n' 'Write access was denied on the parent folder.', 403) ) def copyItem(self, item, folder, name, description): user = self.getCurrentUser() if folder is None: folder = Folder().load( id=item['folderId'], user=user, level=AccessType.WRITE, exc=True) return self._model.copyItem( item, creator=user, name=name, folder=folder, description=description) # For moving multiple images. @access.user(scope=TokenScope.DATA_WRITE) @filtermodel(model=ItemModel) @autoDescribeRoute( Description('Edit an item or move it to another folder.') .param('folder','enter id', paramType='formData') .param('itemArray', "Ids of item array.", paramType='formData') .errorResponse('Write access was denied for the item or folder.', 403) ) def moveItems(self, folder, itemArray): # for item1 in itemarray: # logging.error(item1) # # self.updateItem(item1,None,None,folder,None) # logging.error(folder) item_dict = json.loads(itemArray) folder_dict = json.loads(folder) x = folder_dict['_id'] folder_dict['_id'] = ObjectId(x) x = folder_dict['baseParentId'] folder_dict['baseParentId'] = ObjectId(x) x = folder_dict['parentId'] folder_dict['parentId'] = ObjectId(x) x = folder_dict['creatorId'] folder_dict['creatorId'] = ObjectId(x) for oneItem in item_dict: # logging.error('---------INDIVIDUAL ITEM-----------') # logging.error(oneItem) x = oneItem['_id'] oneItem['_id'] = ObjectId(x) x = oneItem['folderId'] oneItem['folderId'] = ObjectId(x) x = oneItem['creatorId'] oneItem['creatorId'] = ObjectId(x) x = oneItem['baseParentId'] oneItem['baseParentId'] = ObjectId(x) self._model.move(oneItem, folder_dict) return json.loads(itemArray)
class LabelResource(Resource): def __init__(self): super().__init__() self.resourceName = 'label' self.coll_m = Collection() self.file_m = File() self.folder_m = Folder() self.item_m = Item() self.upload_m = Upload() self.asset_m = Assetstore() self.setupRoutes() def setupRoutes(self): self.route('GET', (), handler=self.getLabelList) self.route('GET', (':label_id',), self.getLabel) self.route('GET', ('meta',), self.getLabelMeta) self.route('GET', ('create',), self.createLabelFile) self.route('GET', ('by_name',), self.getLabelByName) self.route('POST', (), self.postLabel) def createNewFile(self, folder, file_name): item = self.item_m.createItem(file_name, creator=self.getCurrentUser(), folder=folder, description='label file', reuseExisting=False) file = self.file_m.createFile(size=0, item=item, name=file_name, creator=self.getCurrentUser(), assetstore=self.asset_m.getCurrent(), mimeType="application/json") return file def copy(self, srcFile, destFile): upload = self.upload_m.createUploadToFile(destFile, self.getCurrentUser(), srcFile['size']) self.upload_m.handleChunk(upload=upload, chunk=RequestBodyStream(self.file_m.open(srcFile), size=destFile['size']), user=self.getCurrentUser()) return upload @access.public @autoDescribeRoute( Description('Get label list')) @rest.rawResponse def getLabelList(self): printOk('getLabelsList() was called!') try: collection = list(self.coll_m.list(user=self.getCurrentUser(), offset=0, limit=1))[0] files = self.coll_m.fileList(collection, user=self.getCurrentUser(), data=False, includeMetadata=True, mimeFilter=['application/json']) files = list(files) cherrypy.response.headers["Content-Type"] = "application/json" return dumps(files) except: printFail(traceback.print_exc) @staticmethod def getOwnerId(folder): aclList = Folder().getFullAccessList(folder) for acl in aclList['users']: if acl['level'] == AccessType.ADMIN: return str(acl['id']) return None def getConfigFolder(self, label_folder_id): label_folder = Folder().load(label_folder_id, user=self.getCurrentUser(), level=AccessType.READ) ownerId = self.getOwnerId(label_folder) config_folder = self.folder_m.load(label_folder['meta'][ownerId], level=AccessType.READ, user=self.getCurrentUser()) return config_folder def findConfig(self, folder_id): folder = self.getConfigFolder(folder_id) printOk2("Config folder {}".format(folder)) files = self.folder_m.fileList(folder, self.getCurrentUser(), data=False) for file_path, file in files: printOk(file) if file['name'] == "config.json": return file def __findFile(self, folder, file_name): item = list(self.item_m.find({'folderId': folder['_id'], 'name': file_name}).limit(1)) if not item: return None item = item[0] file = list(self.file_m.find({'itemId': item['_id']}).limit(1)) if not file: return None return file[0] @access.public @autoDescribeRoute( Description('Create a new label file if it doesnt exist') .param('file_name', 'label file name').param('folder_id', 'the parent folder id')) @rest.rawResponse def createLabelFile(self, file_name, folder_id): try: folder = self.folder_m.load(folder_id, user=self.getCurrentUser(), level=AccessType.WRITE) file = self.__findFile(folder, file_name) if not file: file = self.createNewFile(folder, file_name) config_file = self.findConfig(folder_id) if not config_file: printFail("No config file found") return errorMessage("No config file found") else: res = self.copy(config_file, file) return dumps({ "label_id": res['fileId'] }) return dumps({ "label_id": file['_id'] }) except: printFail(traceback.print_exc) cherrypy.response.status = 500 @access.public @autoDescribeRoute( Description('Get labels by file_name') .param('file_name', 'label file name').param('folder_id', 'the parent folder id')) @rest.rawResponse def getLabelByName(self, file_name, folder_id): try: folder = self.folder_m.load(folder_id, user=self.getCurrentUser(), level=AccessType.READ) file = self.__findFile(folder, file_name) cherrypy.response.headers["Content-Type"] = "application/json" if file: return self.file_m.download(file) else: return dumps({}) except: printFail(traceback.print_exc) cherrypy.response.status = 500 @access.public @autoDescribeRoute( Description('Get label by id') .param('label_id', 'label file id')) @rest.rawResponse def getLabel(self, label_id): try: file = self.file_m.load(label_id, level=AccessType.READ, user=self.getCurrentUser()) printOk2(file) cherrypy.response.headers["Content-Type"] = "application/json" return self.file_m.download(file) except: # Unknown slug printFail(traceback.print_exc) cherrypy.response.status = 404 @access.public @autoDescribeRoute( Description('Get label by id') .param('label_id', 'label file id')) def getLabelMeta(self, label_id): try: file = self.file_m.load(label_id, level=AccessType.READ, user=self.getCurrentUser()) cherrypy.response.headers["Content-Type"] = "application/json" return dumps(file) except: # Unknown slug printFail(traceback.print_exc) cherrypy.response.status = 404 @access.public @autoDescribeRoute( Description('Post label by id') .param('label_id', 'label file id')) @rest.rawResponse def postLabel(self, label_id, params): try: file = self.file_m.load(label_id, level=AccessType.WRITE, user=self.getCurrentUser()) cherrypy.response.headers["Content-Type"] = "application/json" params['labels'] = json.loads(params['labels']) data = json.dumps(params, indent=2, sort_keys=True) upload = writeData(self.getCurrentUser(), file, data) printOk2(file) printOk(upload) return dumps(upload) except: # Unknown slug printFail(traceback.print_exc) cherrypy.response.status = 404 @access.public @autoDescribeRoute( Description('Post label by id') .param('label_id', 'label file id')) @rest.rawResponse def strokeToOutline(self, strokes): pass