Example #1
0
def createNotebooks(event):
    user = event.info
    folder_model = Folder()

    result = lookUpPath('user/%s/Private' % user['login'], force=True)
    private_folder = result['document']

    oc_folder = folder_model.createFolder(private_folder, 'oc',
                                          parentType='folder',
                                          creator=user,
                                          public=True,
                                          reuseExisting=True)

    notebook_folder = folder_model.createFolder(oc_folder, 'notebooks',
                                                parentType='folder',
                                                creator=user,
                                                public=True,
                                                reuseExisting=True)

    notebooks_dir = os.path.join(os.path.dirname(__file__), 'notebooks')

    upload_model = Upload()
    for file in glob.glob('%s/*.ipynb' % notebooks_dir):
        size =  os.path.getsize(file)
        name = os.path.basename(file)
        with open(file, 'rb') as fp:
            upload_model.uploadFromFile(
                fp, size=size, name=name, parentType='folder',
                parent={'_id': ObjectId(notebook_folder['_id'])}, user=user,
                mimeType='application/x-ipynb+json')
Example #2
0
    def _create(name, docker_image, user, baseFolder):
        folderModel = Folder()
        fileModel = File()

        imageName, tagName = _split(name)

        image = folderModel.createFolder(
            baseFolder,
            imageName,
            'Slicer CLI generated docker image folder',
            creator=user,
            reuseExisting=True)
        folderModel.setMetadata(image, dict(slicerCLIType='image'))

        fileModel.createLinkFile('Docker Hub',
                                 image,
                                 'folder',
                                 'https://hub.docker.com/r/%s' % imageName,
                                 user,
                                 reuseExisting=True)

        tag = folderModel.createFolder(
            image,
            tagName,
            'Slicer CLI generated docker image tag folder',
            creator=user,
            reuseExisting=True)

        # add docker image labels as meta data
        labels = docker_image.labels.copy()
        if 'description' in labels:
            tag['description'] = labels['description']
            del labels['description']
        labels = {k.replace('.', '_'): v for k, v in six.iteritems(labels)}
        if 'Author' in docker_image.attrs:
            labels['author'] = docker_image.attrs['Author']

        digest = None
        if docker_image.attrs.get('RepoDigests', []):
            digest = docker_image.attrs['RepoDigests'][0]

        labels['digest'] = digest
        folderModel.setMetadata(tag, labels)

        folderModel.setMetadata(tag, dict(slicerCLIType='tag'))

        return DockerImageItem(image, tag, user)
Example #3
0
def createNotebooks(event):

    # If there is no current asset store, just return
    try:
        Assetstore().getCurrent()
    except GirderException:
        print(
            TerminalColor.warning('WARNING: no current asset store. '
                                  'Notebook will not be created.'))
        return

    user = event.info
    folder_model = Folder()

    result = lookUpPath('user/%s/Private' % user['login'], force=True)
    private_folder = result['document']

    oc_folder = folder_model.createFolder(private_folder,
                                          'oc',
                                          parentType='folder',
                                          creator=user,
                                          public=True,
                                          reuseExisting=True)

    notebook_folder = folder_model.createFolder(oc_folder,
                                                'notebooks',
                                                parentType='folder',
                                                creator=user,
                                                public=True,
                                                reuseExisting=True)

    notebooks_dir = os.path.join(os.path.dirname(__file__), 'notebooks')

    upload_model = Upload()
    for file in glob.glob('%s/*.ipynb' % notebooks_dir):
        size = os.path.getsize(file)
        name = os.path.basename(file)
        with open(file, 'rb') as fp:
            upload_model.uploadFromFile(
                fp,
                size=size,
                name=name,
                parentType='folder',
                parent={'_id': ObjectId(notebook_folder['_id'])},
                user=user,
                mimeType='application/x-ipynb+json')
Example #4
0
    existinig_collections = [each['name'] for each in girderCollection.list()]

    if len(existinig_collections
           ) == 0 or not GIRDER_COLLECTION in existinig_collections:
        girderCollection.createCollection(GIRDER_COLLECTION, currentUser)
        existinig_collections = [
            each['name'] for each in girderCollection.list()
        ]
    else:
        print "ERROR: Cannot create Collection. User not registered or logged in."

    try:
        for collection in girderCollection.list():
            if collection['name'] == GIRDER_COLLECTION:
                girderFolder.createFolder(collection,
                                          'uploaded_files',
                                          description="uploaded files",
                                          parentType='collection')
    except:
        print "WARNING: Uploaded_Files folder already exists."

    main_conf = {
        '/': {
            'tools.sessions.on': True,
            'tools.staticdir.root': CURRENT_DIR
        },
        '/static': {
            'tools.staticdir.on': True,
            'tools.staticdir.dir': STATIC_FOLDER
        },
        '/favicon.ico': {
            'tools.staticfile.on': True,
Example #5
0
    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'])
Example #6
0
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]
Example #7
0
class Folder(Resource):
    """API Endpoint for folders."""
    def __init__(self):
        super(Folder, self).__init__()
        self.resourceName = 'folder'
        self._model = FolderModel()
        self.route('DELETE', (':id', ), self.deleteFolder)
        self.route('DELETE', (':id', 'contents'), self.deleteContents)
        self.route('GET', (), self.find)
        self.route('GET', (':id', ), self.getFolder)
        self.route('GET', (':id', 'details'), self.getFolderDetails)
        self.route('GET', (':id', 'access'), self.getFolderAccess)
        self.route('GET', (':id', 'download'), self.downloadFolder)
        self.route('GET', (':id', 'rootpath'), self.rootpath)
        self.route('GET', (':id', 'position'), self.findPosition)
        self.route('POST', (), self.createFolder)
        self.route('PUT', (':id', ), self.updateFolder)
        self.route('PUT', (':id', 'access'), self.updateFolderAccess)
        self.route('POST', (':id', 'copy'), self.copyFolder)
        self.route('PUT', (':id', 'metadata'), self.setMetadata)
        self.route('DELETE', (':id', 'metadata'), self.deleteMetadata)

    @access.public(scope=TokenScope.DATA_READ)
    @filtermodel(model=FolderModel)
    @autoDescribeRoute(
        Description('Search for folders by certain properties.').notes(
            'You must pass either a "folderId" or "text" field '
            'to specify how you are searching for folders.  '
            'If you omit one of these parameters the request will fail and respond : '
            '"Invalid search mode."').responseClass(
                'Folder', array=True).param(
                    'parentType',
                    "Type of the folder's parent",
                    required=False,
                    enum=['folder', 'user', 'collection']).param(
                        'parentId',
                        "The ID of the folder's parent.",
                        required=False).param('text',
                                              'Pass to perform a text search.',
                                              required=False).
        param('name', 'Pass to lookup a folder by exact name match. Must '
              'pass parentType and parentId as well when using this.',
              required=False).pagingParams(
                  defaultSort='lowerName').errorResponse().errorResponse(
                      'Read access was denied on the parent resource.', 403))
    def find(self, parentType, parentId, text, name, limit, offset, sort):
        """
        Get a list of folders with given search parameters. Currently accepted
        search modes are:

        1. Searching by parentId and parentType, 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 folders in the system.
           Simply pass a "text" parameter for this mode.
        """
        return self._find(parentType, parentId, text, name, limit, offset,
                          sort)

    def _find(self,
              parentType,
              parentId,
              text,
              name,
              limit,
              offset,
              sort,
              filters=None):
        user = self.getCurrentUser()

        filters = (filters.copy() if filters else {})
        if parentType and parentId:
            parent = ModelImporter.model(parentType).load(
                parentId, user=user, level=AccessType.READ, exc=True)

            if text:
                filters['$text'] = {'$search': text}
            if name:
                filters['name'] = name

            return self._model.childFolders(parentType=parentType,
                                            parent=parent,
                                            user=user,
                                            offset=offset,
                                            limit=limit,
                                            sort=sort,
                                            filters=filters)
        elif text:
            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 a folder in a list or search.').
        notes(
            'You must pass either a "folderId" or "text" field '
            'to specify how you are searching for folders.  '
            'If you omit one of these parameters the request will fail and respond : '
            '"Invalid search mode."').modelParam(
                'id', model=FolderModel, level=AccessType.READ).param(
                    'parentType',
                    "Type of the folder's parent",
                    required=False,
                    enum=['folder', 'user', 'collection']).param(
                        'parentId',
                        "The ID of the folder's parent.",
                        required=False).param('text',
                                              'Pass to perform a text search.',
                                              required=False).
        param('name', 'Pass to lookup a folder by exact name match. Must '
              'pass parentType and parentId 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 resource.',
                          403))
    def findPosition(self, folder, parentType, parentId, text, name, params):
        limit, offset, sort = self.getPagingParameters(params, 'lowerName')
        if len(sort) != 1 or sort[0][0] not in folder:
            raise RestException('Invalid sort mode.')
        sortField, sortDir = sort[0]
        dir = '$lt' if sortDir == SortDir.ASCENDING else '$gt'
        filters = {
            '$or': [
                {
                    sortField: {
                        dir: folder.get(sortField)
                    }
                },
                # For folders 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: folder.get(sortField),
                    '_id': {
                        dir: folder['_id']
                    }
                }
            ]
        }
        # limit and offset don't affect the results.
        cursor = self._find(parentType, parentId, text, name, 1, 0, sort,
                            filters)
        return cursor.count()

    @access.public(scope=TokenScope.DATA_READ)
    @autoDescribeRoute(
        Description('Get detailed information about a folder.').modelParam(
            'id', model=FolderModel,
            level=AccessType.READ).errorResponse().errorResponse(
                'Read access was denied on the folder.', 403))
    def getFolderDetails(self, folder):
        return {
            'nItems':
            self._model.countItems(folder),
            'nFolders':
            self._model.countFolders(folder,
                                     user=self.getCurrentUser(),
                                     level=AccessType.READ)
        }

    @access.public(scope=TokenScope.DATA_READ, cookie=True)
    @autoDescribeRoute(
        Description('Download an entire folder as a zip archive.').modelParam(
            'id', model=FolderModel, level=AccessType.READ).jsonParam(
                'mimeFilter',
                'JSON list of MIME types to include.',
                required=False,
                requireArray=True).produces('application/zip').errorResponse(
                    'ID was invalid.').errorResponse(
                        'Read access was denied for the folder.', 403))
    def downloadFolder(self, folder, mimeFilter):
        """
        Returns a generator function that will be used to stream out a zip
        file containing this folder's contents, filtered by permissions.
        """
        setResponseHeader('Content-Type', 'application/zip')
        setContentDisposition(folder['name'] + '.zip')
        user = self.getCurrentUser()

        def stream():
            zip = ziputil.ZipGenerator(folder['name'])
            for (path, file) in self._model.fileList(folder,
                                                     user=user,
                                                     subpath=False,
                                                     mimeFilter=mimeFilter):
                for data in zip.addFile(file, path):
                    yield data
            yield zip.footer()

        return stream

    @access.user(scope=TokenScope.DATA_WRITE)
    @filtermodel(model=FolderModel)
    @autoDescribeRoute(
        Description('Update a folder or move it into a new parent.'
                    ).responseClass('Folder').modelParam(
                        'id', model=FolderModel, level=AccessType.WRITE).param(
                            'name',
                            'Name of the folder.',
                            required=False,
                            strip=True).param('description',
                                              'Description for the folder.',
                                              required=False,
                                              strip=True).
        param('parentType',
              "Type of the folder's parent",
              required=False,
              enum=['folder', 'user', 'collection'],
              strip=True).param(
                  'parentId',
                  'Parent ID for the new parent of this folder.',
                  required=False).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 folder or its new parent object.',
            403))
    def updateFolder(self, folder, name, description, parentType, parentId,
                     metadata):
        user = self.getCurrentUser()
        if name is not None:
            folder['name'] = name
        if description is not None:
            folder['description'] = description

        folder = self._model.updateFolder(folder)
        if metadata:
            folder = self._model.setMetadata(folder, metadata)

        if parentType and parentId:
            parent = ModelImporter.model(parentType).load(
                parentId, level=AccessType.WRITE, user=user, exc=True)
            if (parentType, parent['_id']) != (folder['parentCollection'],
                                               folder['parentId']):
                folder = self._model.move(folder, parent, parentType)

        return folder

    @access.user(scope=TokenScope.DATA_OWN)
    @filtermodel(model=FolderModel, addFields={'access'})
    @autoDescribeRoute(
        Description('Update the access control list for a folder.').modelParam(
            'id', model=FolderModel, level=AccessType.ADMIN).jsonParam(
                'access',
                'The JSON-encoded access control list.',
                requireObject=True).jsonParam(
                    'publicFlags',
                    'JSON list of public access flags.',
                    requireArray=True,
                    required=False).param(
                        'public',
                        'Whether the folder should be publicly visible.',
                        dataType='boolean',
                        required=False).param(
                            'recurse',
                            'Whether the policies should be applied to all '
                            'subfolders under this folder as well.',
                            dataType='boolean',
                            default=False,
                            required=False).
        param('progress', 'If recurse is set to True, this controls whether '
              'progress notifications will be sent.',
              dataType='boolean',
              default=False,
              required=False).errorResponse('ID was invalid.').errorResponse(
                  'Admin access was denied for the folder.', 403))
    def updateFolderAccess(self, folder, access, publicFlags, public, recurse,
                           progress):
        user = self.getCurrentUser()
        progress = progress and recurse  # Only enable progress in recursive case
        with ProgressContext(progress,
                             user=user,
                             title='Updating permissions',
                             message='Calculating progress...') as ctx:
            if progress:
                ctx.update(
                    total=self._model.subtreeCount(folder,
                                                   includeItems=False,
                                                   user=user,
                                                   level=AccessType.ADMIN))
            return self._model.setAccessList(folder,
                                             access,
                                             save=True,
                                             recurse=recurse,
                                             user=user,
                                             progress=ctx,
                                             setPublic=public,
                                             publicFlags=publicFlags)

    @access.user(scope=TokenScope.DATA_WRITE)
    @filtermodel(model=FolderModel)
    @autoDescribeRoute(
        Description('Create a new folder.').responseClass('Folder').param(
            'parentType',
            "Type of the folder's parent",
            required=False,
            enum=['folder', 'user', 'collection'],
            default='folder').param('parentId',
                                    "The ID of the folder's parent.").param(
                                        'name',
                                        'Name of the folder.',
                                        strip=True).param(
                                            'description',
                                            'Description for the folder.',
                                            required=False,
                                            default='',
                                            strip=True).
        param('reuseExisting',
              'Return existing folder if it exists rather than '
              'creating a new one.',
              required=False,
              dataType='boolean',
              default=False).param(
                  'public',
                  'Whether the folder should be publicly visible. By '
                  'default, inherits the value from parent folder, or in the '
                  'case of user or collection parentType, defaults to False.',
                  required=False,
                  dataType='boolean').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', 403))
    def createFolder(self, public, parentType, parentId, name, description,
                     reuseExisting, metadata):
        user = self.getCurrentUser()
        parent = ModelImporter.model(parentType).load(id=parentId,
                                                      user=user,
                                                      level=AccessType.WRITE,
                                                      exc=True)

        newFolder = self._model.createFolder(parent=parent,
                                             name=name,
                                             parentType=parentType,
                                             creator=user,
                                             description=description,
                                             public=public,
                                             reuseExisting=reuseExisting)
        if metadata:
            newFolder = self._model.setMetadata(newFolder, metadata)
        return newFolder

    @access.public(scope=TokenScope.DATA_READ)
    @filtermodel(model=FolderModel)
    @autoDescribeRoute(
        Description('Get a folder by ID.').responseClass('Folder').modelParam(
            'id', model=FolderModel, level=AccessType.READ).errorResponse(
                'ID was invalid.').errorResponse(
                    'Read access was denied for the folder.', 403))
    def getFolder(self, folder):
        return folder

    @access.user(scope=TokenScope.DATA_OWN)
    @autoDescribeRoute(
        Description('Get the access control list for a folder.').responseClass(
            'Folder').modelParam(
                'id', model=FolderModel, level=AccessType.ADMIN).errorResponse(
                    'ID was invalid.').errorResponse(
                        'Admin access was denied for the folder.', 403))
    def getFolderAccess(self, folder):
        return self._model.getFullAccessList(folder)

    @access.user(scope=TokenScope.DATA_OWN)
    @autoDescribeRoute(
        Description('Delete a folder by ID.').modelParam(
            'id', model=FolderModel, level=AccessType.ADMIN).param(
                'progress',
                'Whether to record progress on this task.',
                required=False,
                dataType='boolean',
                default=False).errorResponse('ID was invalid.').errorResponse(
                    'Admin access was denied for the folder.', 403))
    def deleteFolder(self, folder, progress):
        with ProgressContext(progress,
                             user=self.getCurrentUser(),
                             title='Deleting folder %s' % folder['name'],
                             message='Calculating folder size...') as ctx:
            # Don't do the subtree count if we weren't asked for progress
            if progress:
                ctx.update(total=self._model.subtreeCount(folder))
            self._model.remove(folder, progress=ctx)
        return {'message': 'Deleted folder %s.' % folder['name']}

    @access.user(scope=TokenScope.DATA_WRITE)
    @filtermodel(model=FolderModel)
    @autoDescribeRoute(
        Description('Set metadata fields on an folder.').
        responseClass('Folder').notes(
            'Set metadata fields to null in order to delete them.').modelParam(
                'id', model=FolderModel, 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 folder.',
                                 403))
    def setMetadata(self, folder, metadata, allowNull):
        return self._model.setMetadata(folder, metadata, allowNull=allowNull)

    @access.user(scope=TokenScope.DATA_WRITE)
    @filtermodel(model=FolderModel)
    @autoDescribeRoute(
        Description('Copy a folder.').responseClass('Folder').modelParam(
            'id',
            'The ID of the original folder.',
            model=FolderModel,
            level=AccessType.READ).param(
                'parentType',
                "Type of the new folder's parent",
                required=False,
                enum=['folder', 'user', 'collection'
                      ]).param('parentId',
                               'The ID of the parent document.',
                               required=False).param(
                                   'name',
                                   'Name for the new folder.',
                                   required=False).param(
                                       'description',
                                       'Description for the new folder.',
                                       required=False).
        param('public', 'Whether the folder should be publicly visible. By '
              'default, inherits the value from parent folder, or in the case '
              'of user or collection parentType, defaults to False. If '
              "'original', use the value of the original folder.",
              required=False,
              enum=['true', 'false', 'original']).param(
                  'progress',
                  'Whether to record progress on this task.',
                  required=False,
                  dataType='boolean',
                  default=False).errorResponse(
                      ('A parameter was invalid.',
                       'ID was invalid.')).errorResponse(
                           'Read access was denied on the original folder.\n\n'
                           'Write access was denied on the parent.', 403))
    def copyFolder(self, folder, parentType, parentId, name, description,
                   public, progress):
        user = self.getCurrentUser()
        parentType = parentType or folder['parentCollection']
        if parentId:
            parent = ModelImporter.model(parentType).load(
                id=parentId, user=user, level=AccessType.WRITE, exc=True)
        else:
            parent = None

        with ProgressContext(progress,
                             user=self.getCurrentUser(),
                             title='Copying folder %s' % folder['name'],
                             message='Calculating folder size...') as ctx:
            # Don't do the subtree count if we weren't asked for progress
            if progress:
                ctx.update(total=self._model.subtreeCount(folder))
            return self._model.copyFolder(folder,
                                          creator=user,
                                          name=name,
                                          parentType=parentType,
                                          parent=parent,
                                          description=description,
                                          public=public,
                                          progress=ctx)

    @access.user(scope=TokenScope.DATA_WRITE)
    @autoDescribeRoute(
        Description('Remove all contents from a folder.').notes(
            'Cleans out all the items and subfolders from under a folder, '
            'but does not remove the folder itself.').modelParam(
                'id',
                'The ID of the folder to clean.',
                model=FolderModel,
                level=AccessType.WRITE).param(
                    'progress',
                    'Whether to record progress on this task.',
                    required=False,
                    dataType='boolean',
                    default=False).errorResponse(
                        'ID was invalid.').errorResponse(
                            'Write access was denied on the folder.', 403))
    def deleteContents(self, folder, progress):
        with ProgressContext(progress,
                             user=self.getCurrentUser(),
                             title='Clearing folder %s' % folder['name'],
                             message='Calculating folder size...') as ctx:
            # Don't do the subtree count if we weren't asked for progress
            if progress:
                ctx.update(total=self._model.subtreeCount(folder) - 1)
            self._model.clean(folder, progress=ctx)
        return {'message': 'Cleaned folder %s.' % folder['name']}

    @access.user(scope=TokenScope.DATA_WRITE)
    @filtermodel(FolderModel)
    @autoDescribeRoute(
        Description('Delete metadata fields on a folder.').responseClass(
            'Folder').modelParam(
                'id', model=FolderModel, 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 folder.', 403))
    def deleteMetadata(self, folder, fields):
        return self._model.deleteMetadata(folder, fields)

    @access.public(scope=TokenScope.DATA_READ)
    @autoDescribeRoute(
        Description(
            "Get the path to the root of the folder's hierarchy.").modelParam(
                'id', model=FolderModel, level=AccessType.READ).errorResponse(
                    'ID was invalid.').errorResponse(
                        'Read access was denied for the folder.', 403))
    def rootpath(self, folder, params):
        return self._model.parentsToRoot(folder, user=self.getCurrentUser())
Example #8
0
class Folder(Resource):
    """API Endpoint for folders."""

    def __init__(self):
        super(Folder, self).__init__()
        self.resourceName = 'folder'
        self._model = FolderModel()
        self.route('DELETE', (':id',), self.deleteFolder)
        self.route('DELETE', (':id', 'contents'), self.deleteContents)
        self.route('GET', (), self.find)
        self.route('GET', (':id',), self.getFolder)
        self.route('GET', (':id', 'details'), self.getFolderDetails)
        self.route('GET', (':id', 'access'), self.getFolderAccess)
        self.route('GET', (':id', 'download'), self.downloadFolder)
        self.route('GET', (':id', 'rootpath'), self.rootpath)
        self.route('POST', (), self.createFolder)
        self.route('PUT', (':id',), self.updateFolder)
        self.route('PUT', (':id', 'access'), self.updateFolderAccess)
        self.route('POST', (':id', 'copy'), self.copyFolder)
        self.route('PUT', (':id', 'metadata'), self.setMetadata)
        self.route('DELETE', (':id', 'metadata'), self.deleteMetadata)

    @access.public(scope=TokenScope.DATA_READ)
    @filtermodel(model=FolderModel)
    @autoDescribeRoute(
        Description('Search for folders by certain properties.')
        .notes('You must pass either a "folderId" or "text" field '
               'to specify how you are searching for folders.  '
               'If you omit one of these parameters the request will fail and respond : '
               '"Invalid search mode."')
        .responseClass('Folder', array=True)
        .param('parentType', "Type of the folder's parent", required=False,
               enum=['folder', 'user', 'collection'])
        .param('parentId', "The ID of the folder's parent.", required=False)
        .param('text', 'Pass to perform a text search.', required=False)
        .param('name', 'Pass to lookup a folder by exact name match. Must '
               'pass parentType and parentId as well when using this.', required=False)
        .pagingParams(defaultSort='lowerName')
        .errorResponse()
        .errorResponse('Read access was denied on the parent resource.', 403)
    )
    def find(self, parentType, parentId, text, name, limit, offset, sort):
        """
        Get a list of folders with given search parameters. Currently accepted
        search modes are:

        1. Searching by parentId and parentType, 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 folders in the system.
           Simply pass a "text" parameter for this mode.
        """
        user = self.getCurrentUser()

        if parentType and parentId:
            parent = ModelImporter.model(parentType).load(
                parentId, user=user, level=AccessType.READ, exc=True)

            filters = {}
            if text:
                filters['$text'] = {
                    '$search': text
                }
            if name:
                filters['name'] = name

            return self._model.childFolders(
                parentType=parentType, parent=parent, user=user,
                offset=offset, limit=limit, sort=sort, filters=filters)
        elif text:
            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)
    @autoDescribeRoute(
        Description('Get detailed information about a folder.')
        .modelParam('id', model=FolderModel, level=AccessType.READ)
        .errorResponse()
        .errorResponse('Read access was denied on the folder.', 403)
    )
    def getFolderDetails(self, folder):
        return {
            'nItems': self._model.countItems(folder),
            'nFolders': self._model.countFolders(
                folder, user=self.getCurrentUser(), level=AccessType.READ)
        }

    @access.public(scope=TokenScope.DATA_READ, cookie=True)
    @autoDescribeRoute(
        Description('Download an entire folder as a zip archive.')
        .modelParam('id', model=FolderModel, level=AccessType.READ)
        .jsonParam('mimeFilter', 'JSON list of MIME types to include.', required=False,
                   requireArray=True)
        .produces('application/zip')
        .errorResponse('ID was invalid.')
        .errorResponse('Read access was denied for the folder.', 403)
    )
    def downloadFolder(self, folder, mimeFilter):
        """
        Returns a generator function that will be used to stream out a zip
        file containing this folder's contents, filtered by permissions.
        """
        setResponseHeader('Content-Type', 'application/zip')
        setContentDisposition(folder['name'] + '.zip')
        user = self.getCurrentUser()

        def stream():
            zip = ziputil.ZipGenerator(folder['name'])
            for (path, file) in self._model.fileList(
                    folder, user=user, subpath=False, mimeFilter=mimeFilter):
                for data in zip.addFile(file, path):
                    yield data
            yield zip.footer()
        return stream

    @access.user(scope=TokenScope.DATA_WRITE)
    @filtermodel(model=FolderModel)
    @autoDescribeRoute(
        Description('Update a folder or move it into a new parent.')
        .responseClass('Folder')
        .modelParam('id', model=FolderModel, level=AccessType.WRITE)
        .param('name', 'Name of the folder.', required=False, strip=True)
        .param('description', 'Description for the folder.', required=False, strip=True)
        .param('parentType', "Type of the folder's parent", required=False,
               enum=['folder', 'user', 'collection'], strip=True)
        .param('parentId', 'Parent ID for the new parent of this folder.', required=False)
        .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 folder or its new parent object.', 403)
    )
    def updateFolder(self, folder, name, description, parentType, parentId, metadata):
        user = self.getCurrentUser()
        if name is not None:
            folder['name'] = name
        if description is not None:
            folder['description'] = description

        folder = self._model.updateFolder(folder)
        if metadata:
            folder = self._model.setMetadata(folder, metadata)

        if parentType and parentId:
            parent = ModelImporter.model(parentType).load(
                parentId, level=AccessType.WRITE, user=user, exc=True)
            if (parentType, parent['_id']) != (folder['parentCollection'], folder['parentId']):
                folder = self._model.move(folder, parent, parentType)

        return folder

    @access.user(scope=TokenScope.DATA_OWN)
    @filtermodel(model=FolderModel, addFields={'access'})
    @autoDescribeRoute(
        Description('Update the access control list for a folder.')
        .modelParam('id', model=FolderModel, level=AccessType.ADMIN)
        .jsonParam('access', 'The JSON-encoded access control list.', requireObject=True)
        .jsonParam('publicFlags', 'JSON list of public access flags.', requireArray=True,
                   required=False)
        .param('public', 'Whether the folder should be publicly visible.',
               dataType='boolean', required=False)
        .param('recurse', 'Whether the policies should be applied to all '
               'subfolders under this folder as well.', dataType='boolean',
               default=False, required=False)
        .param('progress', 'If recurse is set to True, this controls whether '
               'progress notifications will be sent.', dataType='boolean',
               default=False, required=False)
        .errorResponse('ID was invalid.')
        .errorResponse('Admin access was denied for the folder.', 403)
    )
    def updateFolderAccess(self, folder, access, publicFlags, public, recurse, progress):
        user = self.getCurrentUser()
        progress = progress and recurse  # Only enable progress in recursive case
        with ProgressContext(progress, user=user, title='Updating permissions',
                             message='Calculating progress...') as ctx:
            if progress:
                ctx.update(total=self._model.subtreeCount(
                    folder, includeItems=False, user=user, level=AccessType.ADMIN))
            return self._model.setAccessList(
                folder, access, save=True, recurse=recurse, user=user,
                progress=ctx, setPublic=public, publicFlags=publicFlags)

    @access.user(scope=TokenScope.DATA_WRITE)
    @filtermodel(model=FolderModel)
    @autoDescribeRoute(
        Description('Create a new folder.')
        .responseClass('Folder')
        .param('parentType', "Type of the folder's parent", required=False,
               enum=['folder', 'user', 'collection'], default='folder')
        .param('parentId', "The ID of the folder's parent.")
        .param('name', 'Name of the folder.', strip=True)
        .param('description', 'Description for the folder.', required=False,
               default='', strip=True)
        .param('reuseExisting', 'Return existing folder if it exists rather than '
               'creating a new one.', required=False,
               dataType='boolean', default=False)
        .param('public', 'Whether the folder should be publicly visible. By '
               'default, inherits the value from parent folder, or in the '
               'case of user or collection parentType, defaults to False.',
               required=False, dataType='boolean')
        .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', 403)
    )
    def createFolder(self, public, parentType, parentId, name, description,
                     reuseExisting, metadata):
        user = self.getCurrentUser()
        parent = ModelImporter.model(parentType).load(
            id=parentId, user=user, level=AccessType.WRITE, exc=True)

        newFolder = self._model.createFolder(
            parent=parent, name=name, parentType=parentType, creator=user,
            description=description, public=public, reuseExisting=reuseExisting)
        if metadata:
            newFolder = self._model.setMetadata(newFolder, metadata)
        return newFolder

    @access.public(scope=TokenScope.DATA_READ)
    @filtermodel(model=FolderModel)
    @autoDescribeRoute(
        Description('Get a folder by ID.')
        .responseClass('Folder')
        .modelParam('id', model=FolderModel, level=AccessType.READ)
        .errorResponse('ID was invalid.')
        .errorResponse('Read access was denied for the folder.', 403)
    )
    def getFolder(self, folder):
        return folder

    @access.user(scope=TokenScope.DATA_OWN)
    @autoDescribeRoute(
        Description('Get the access control list for a folder.')
        .responseClass('Folder')
        .modelParam('id', model=FolderModel, level=AccessType.ADMIN)
        .errorResponse('ID was invalid.')
        .errorResponse('Admin access was denied for the folder.', 403)
    )
    def getFolderAccess(self, folder):
        return self._model.getFullAccessList(folder)

    @access.user(scope=TokenScope.DATA_OWN)
    @autoDescribeRoute(
        Description('Delete a folder by ID.')
        .modelParam('id', model=FolderModel, level=AccessType.ADMIN)
        .param('progress', 'Whether to record progress on this task.',
               required=False, dataType='boolean', default=False)
        .errorResponse('ID was invalid.')
        .errorResponse('Admin access was denied for the folder.', 403)
    )
    def deleteFolder(self, folder, progress):
        with ProgressContext(progress, user=self.getCurrentUser(),
                             title='Deleting folder %s' % folder['name'],
                             message='Calculating folder size...') as ctx:
            # Don't do the subtree count if we weren't asked for progress
            if progress:
                ctx.update(total=self._model.subtreeCount(folder))
            self._model.remove(folder, progress=ctx)
        return {'message': 'Deleted folder %s.' % folder['name']}

    @access.user(scope=TokenScope.DATA_WRITE)
    @filtermodel(model=FolderModel)
    @autoDescribeRoute(
        Description('Set metadata fields on an folder.')
        .responseClass('Folder')
        .notes('Set metadata fields to null in order to delete them.')
        .modelParam('id', model=FolderModel, 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 folder.', 403)
    )
    def setMetadata(self, folder, metadata, allowNull):
        return self._model.setMetadata(folder, metadata, allowNull=allowNull)

    @access.user(scope=TokenScope.DATA_WRITE)
    @filtermodel(model=FolderModel)
    @autoDescribeRoute(
        Description('Copy a folder.')
        .responseClass('Folder')
        .modelParam('id', 'The ID of the original folder.', model=FolderModel,
                    level=AccessType.READ)
        .param('parentType', "Type of the new folder's parent", required=False,
               enum=['folder', 'user', 'collection'])
        .param('parentId', 'The ID of the parent document.', required=False)
        .param('name', 'Name for the new folder.', required=False)
        .param('description', "Description for the new folder.", required=False)
        .param('public', "Whether the folder should be publicly visible. By "
               "default, inherits the value from parent folder, or in the case "
               "of user or collection parentType, defaults to False. If "
               "'original', use the value of the original folder.",
               required=False, enum=['true', 'false', 'original'])
        .param('progress', 'Whether to record progress on this task.',
               required=False, dataType='boolean', default=False)
        .errorResponse(('A parameter was invalid.',
                        'ID was invalid.'))
        .errorResponse('Read access was denied on the original folder.\n\n'
                       'Write access was denied on the parent.', 403)
    )
    def copyFolder(self, folder, parentType, parentId, name, description, public, progress):
        user = self.getCurrentUser()
        parentType = parentType or folder['parentCollection']
        if parentId:
            parent = ModelImporter.model(parentType).load(
                id=parentId, user=user, level=AccessType.WRITE, exc=True)
        else:
            parent = None

        with ProgressContext(progress, user=self.getCurrentUser(),
                             title='Copying folder %s' % folder['name'],
                             message='Calculating folder size...') as ctx:
            # Don't do the subtree count if we weren't asked for progress
            if progress:
                ctx.update(total=self._model.subtreeCount(folder))
            return self._model.copyFolder(
                folder, creator=user, name=name, parentType=parentType,
                parent=parent, description=description, public=public, progress=ctx)

    @access.user(scope=TokenScope.DATA_WRITE)
    @autoDescribeRoute(
        Description('Remove all contents from a folder.')
        .notes('Cleans out all the items and subfolders from under a folder, '
               'but does not remove the folder itself.')
        .modelParam('id', 'The ID of the folder to clean.', model=FolderModel,
                    level=AccessType.WRITE)
        .param('progress', 'Whether to record progress on this task.',
               required=False, dataType='boolean', default=False)
        .errorResponse('ID was invalid.')
        .errorResponse('Write access was denied on the folder.', 403)
    )
    def deleteContents(self, folder, progress):
        with ProgressContext(progress, user=self.getCurrentUser(),
                             title='Clearing folder %s' % folder['name'],
                             message='Calculating folder size...') as ctx:
            # Don't do the subtree count if we weren't asked for progress
            if progress:
                ctx.update(total=self._model.subtreeCount(folder) - 1)
            self._model.clean(folder, progress=ctx)
        return {'message': 'Cleaned folder %s.' % folder['name']}

    @access.user(scope=TokenScope.DATA_WRITE)
    @filtermodel(FolderModel)
    @autoDescribeRoute(
        Description('Delete metadata fields on a folder.')
        .responseClass('Folder')
        .modelParam('id', model=FolderModel, 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 folder.', 403)
    )
    def deleteMetadata(self, folder, fields):
        return self._model.deleteMetadata(folder, fields)

    @access.public(scope=TokenScope.DATA_READ)
    @autoDescribeRoute(
        Description('Get the path to the root of the folder\'s hierarchy.')
        .modelParam('id', model=FolderModel, level=AccessType.READ)
        .errorResponse('ID was invalid.')
        .errorResponse('Read access was denied for the folder.', 403)
    )
    def rootpath(self, folder, params):
        return self._model.parentsToRoot(folder, user=self.getCurrentUser())
Example #9
0
    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'])
Example #10
0
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)
Example #11
0
    try:
        currentUser = girderUser.getAdmins()[0]
    except:
        print "ERROR: No user registered or logged in."
    existinig_collections = [each['name'] for each in girderCollection.list()]

    if len(existinig_collections) == 0 or not GIRDER_COLLECTION in existinig_collections:
        girderCollection.createCollection(GIRDER_COLLECTION, currentUser)
        existinig_collections = [each['name'] for each in girderCollection.list()]
    else:
        print "ERROR: Cannot create Collection. User not registered or logged in."

    try:
        for collection in girderCollection.list():
            if collection['name'] == GIRDER_COLLECTION:
                girderFolder.createFolder(collection, 'uploaded_files', description="uploaded files", parentType='collection')
    except:
        print "WARNING: Uploaded_Files folder already exists."

    main_conf = {
            '/': {
                'tools.sessions.on': True,
                'tools.staticdir.root': CURRENT_DIR
            },
            '/static': {
                 'tools.staticdir.on': True,
                 'tools.staticdir.dir': STATIC_FOLDER
            },
            '/favicon.ico':
            {
                'tools.staticfile.on': True,
Example #12
0
class App(Resource):
    def __init__(self):
        super(App, self).__init__()
        self.resourceName = 'app'
        self._model = Folder()

        self.route('POST', (), self.initApp)
        self.route('GET', (), self.listApp)
        self.route('DELETE', (':app_id', ), self.deleteApp)
        self.route('POST', (':app_id', 'release'), self.createNewRelease)
        self.route('GET', (':app_id', 'release'), self.getAllReleases)
        self.route('GET', (':app_id', 'release', ':release_id_or_name'),
                   self.getReleaseByIdOrName)
        self.route('DELETE', (':app_id', 'release', ':release_id_or_name'),
                   self.deleteReleaseByIdOrName)
        self.route('GET', (':app_id', 'extension'), self.getExtensions)
        self.route('GET', (':app_id', 'extension', ':extension_name'),
                   self.getExtensionByName)
        self.route('POST', (':app_id', 'extension'),
                   self.createOrUpdateExtension)
        self.route('DELETE', (':app_id', 'extension', ':ext_id'),
                   self.deleteExtension)

    @autoDescribeRoute(
        Description('Create a new application.').responseClass('Folder').notes(
            'If collectionId is missing or collectionName does not match an existing '
            'collection, a fresh new collection will be created with the "collection_name" '
            'given in parameters. '
            'By default the name "Applications" will be given to the collection.'
        ).param('name', 'The name of the application.').param(
            'app_description', 'Application description.',
            required=False).param(
                'collection_id',
                'The ID of the collection which contain the application',
                required=False).param(
                    'collection_name',
                    'The Name of the collection which be created to contain'
                    ' the application',
                    required=False).param('collection_description',
                                          'Collection description.',
                                          required=False).
        param('public',
              'Whether the collection should be publicly visible.',
              required=False,
              dataType='boolean',
              default=True).errorResponse(
                  'Write permission denied on the application.', 403))
    @access.user(scope=TokenScope.DATA_WRITE)
    def initApp(self, name, app_description, collection_id, collection_name,
                collection_description, public):
        """
        Create the directory for start a new application. By default, without specifying
        a ``collection_id``, it will create a new collection name either ``collection_name``
        if provided, or **'Applications'**. If the collection 'Applications already exist it will
        get it.
        Return the new application (as a folder) that always contain a default
        sub-folder named 'nightly'.

        :param name: Name of the new application
        :param app_description: Description of the new application
        :param collection_id: Id of the collection within create the application
        :param collection_name: Name of the collection which will be created
        :param collection_description: Description of the new collection
        :param public: Whether the new collection should be publicly visible
        :return: The new application folder
        """
        creator = self.getCurrentUser()
        # Load or create the collection that contain the application
        if collection_id:
            collection = Collection().load(collection_id, force=True)
        elif collection_name:
            collection = list(Collection().find({'name': collection_name},
                                                user=creator))
            if not collection:
                collection = Collection().createCollection(
                    name=collection_name,
                    description=collection_description,
                    public=public,
                    creator=creator)
            else:
                collection = collection[0]
        else:
            collection = list(Collection().find({'name': 'Applications'},
                                                user=creator))
            if not collection:
                collection = Collection().createCollection(
                    name='Applications',
                    description=collection_description,
                    public=public,
                    creator=creator)
            else:
                collection = collection[0]
        # Create the application
        if not app_description:
            app_description = ''
        app = self._model.createFolder(parent=collection,
                                       name=name,
                                       description=app_description,
                                       parentType='Collection',
                                       public=public,
                                       creator=creator)
        # Create the 'nightly' release which will be the default folder when uploading an extension
        self._model.createFolder(
            parent=app,
            name=constants.NIGHTLY_RELEASE_NAME,
            description='Uploaded each night, always up-to-date',
            parentType='Folder',
            public=public,
            creator=creator)
        # Set a default template name for extensions in the application,
        # this can be changed in anytime.
        return self._model.setMetadata(
            app, {
                'extensionNameTemplate':
                '{app_revision}_{os}_{arch}_{baseName}_{revision}'
            })

    @autoDescribeRoute(
        Description('List existing application.').responseClass(
            'Folder', array=True).param('app_id',
                                        'The ID of the application.',
                                        required=False).param(
                                            'collection_id',
                                            'The ID of the collection.',
                                            required=False).param(
                                                'name',
                                                'The name of the application.',
                                                required=False).
        param('text',
              'Provide text search of the application.',
              required=False).pagingParams(
                  defaultSort='name').errorResponse().errorResponse(
                      'Read permission denied on the application.', 403))
    @access.user(scope=TokenScope.DATA_READ)
    def listApp(self, app_id, collection_id, name, text, limit, offset, sort):
        """
        List existing applications base on some optional parameters:

        :param app_id: Application ID
        :param collection_id: Collection ID
        :param name: Name of the application
        :param text: Provide text search on the name of the application
        :return: List of applications
        """
        user = self.getCurrentUser()

        if ObjectId.is_valid(app_id):
            return self._model.load(app_id, user=user)
        else:
            if collection_id:
                parent = Collection().load(collection_id,
                                           user=user,
                                           level=AccessType.READ,
                                           exc=True)
            else:
                parent = Collection().findOne(query={'name': 'Applications'},
                                              user=user,
                                              offset=offset)
            if parent:
                filters = {}
                if text:
                    filters['$text'] = {'$search': text}
                if name:
                    filters['name'] = name

                return list(
                    self._model.childFolders(parentType='collection',
                                             parent=parent,
                                             user=user,
                                             offset=offset,
                                             limit=limit,
                                             sort=sort,
                                             filters=filters))
            return []

    @access.user(scope=TokenScope.DATA_WRITE)
    @autoDescribeRoute(
        Description('Delete an Application by ID.').modelParam(
            'app_id', model=Folder, level=AccessType.ADMIN).param(
                'progress',
                'Whether to record progress on this task.',
                required=False,
                dataType='boolean',
                default=False).errorResponse('ID was invalid.').errorResponse(
                    'Admin access was denied for the application.', 403))
    def deleteApp(self, folder, progress):
        """
        Delete the application by ID.

        :param id: Id of the application
        :return: Confirmation message with the deleted application name
        """
        return _deleteFolder(folder, progress, self.getCurrentUser())

    @autoDescribeRoute(
        Description('Create a new release.').responseClass('Folder').notes(
            'The application\'s revision is stored as metadata of the new release.'
        ).param('name', 'The release\'s name.').param(
            'app_id', 'The application\'s ID which contain the release').param(
                'app_revision',
                'The application\'s revision which correspond to the release'
            ).param('description',
                    'The application\'s description.',
                    required=False).param(
                        'public',
                        'Whether the release should be publicly visible.',
                        required=False,
                        dataType='boolean',
                        default=True).errorResponse())
    @access.user(scope=TokenScope.DATA_WRITE)
    def createNewRelease(self, name, app_id, app_revision, description,
                         public):
        """
        Create a new release with the ``name`` within the application. The ``app_revision``
        will permit to automatically choose this release when uploading an extension
        with a matching `app_revision`` metadata.

        :param name: Name of the new release
        :param app_id: Application ID
        :param app_revision: Revision of the application corresponding to this release
        :param description: Description of the new release
        :param public: Whether the release should be publicly visible

        :return: The new release folder
        """
        creator = self.getCurrentUser()
        application = self._model.load(app_id, user=creator)
        if not description:
            description = ''
        release = self._model.createFolder(parent=application,
                                           name=name,
                                           description=description,
                                           parentType='Folder',
                                           public=public,
                                           creator=creator)

        return self._model.setMetadata(release, {'revision': app_revision})

    @autoDescribeRoute(
        Description('Get all the releases from an application.').responseClass(
            'Folder').param('app_id', 'The application\'s ID.').pagingParams(
                defaultSort='name').errorResponse(
                    'ID was invalid.').errorResponse(
                        'Read permission denied on the application.', 403))
    @access.user(scope=TokenScope.DATA_READ)
    def getAllReleases(self, app_id, limit, offset, sort):
        """
        Get a list of all the release of an application.

        :param app_id: Application ID
        :return: List of all release within the application
        """
        user = self.getCurrentUser()
        application = self._model.load(app_id, user=user)
        # It returns the nightly release too
        return list(
            self._model.childFolders(application,
                                     'Folder',
                                     user=user,
                                     limit=limit,
                                     offset=offset,
                                     sort=sort))

    @autoDescribeRoute(
        Description(
            'Get a particular releases by ID or name from an application.').
        responseClass('Folder').param(
            'app_id', 'The application\'s ID.').param(
                'release_id_or_name',
                'The release\'s ID or name.').errorResponse(
                    'ID or name was invalid.').errorResponse(
                        'Read permission denied on the application.', 403))
    @access.user(scope=TokenScope.DATA_READ)
    def getReleaseByIdOrName(self, app_id, release_id_or_name):
        """
        Get the release folder by ID or by name.

        :param app_id: Application ID
        :param release_id_or_name: Could be either the release ID or the release name
        :return: The release folder
        """
        user = self.getCurrentUser()
        application = self._model.load(app_id, user=user)

        if ObjectId.is_valid(release_id_or_name):
            return self._model.load(release_id_or_name, user=user)
        release_folder = list(
            self._model.childFolders(
                application,
                'Folder',
                filters={'lowerName': release_id_or_name.lower()}))
        if not release_folder:
            return None
        return release_folder[0]

    @access.user(scope=TokenScope.DATA_WRITE)
    @autoDescribeRoute(
        Description('Delete a release by ID or name.').modelParam(
            'app_id', model=Folder,
            level=AccessType.ADMIN).param('release_id_or_name',
                                          'The release\'s ID or name.').
        param('progress',
              'Whether to record progress on this task.',
              required=False,
              dataType='boolean',
              default=False).errorResponse('ID was invalid.').errorResponse(
                  'Admin access was denied for the release.', 403))
    def deleteReleaseByIdOrName(self, folder, release_id_or_name, progress):
        """
        Delete a release by ID or name.

        :param app_id: Application ID
        :param release_id_or_name: Could be either the release ID or the release name
        :param progress: Whether to record progress on this task
        :return: Confirmation message with the deleted release name
        """
        user = self.getCurrentUser()

        if ObjectId.is_valid(release_id_or_name):
            release = self._model.load(release_id_or_name, user=user)
        else:
            release_folder = list(
                self._model.childFolders(
                    folder,
                    'Folder',
                    filters={'lowerName': release_id_or_name.lower()}))
            if not release_folder:
                raise Exception("Couldn't find release %s" %
                                release_id_or_name)
            release = release_folder[0]

        return _deleteFolder(release, progress, self.getCurrentUser())

    @autoDescribeRoute(
        Description('List or search available extensions.').responseClass(
            'Extension').param('app_id', 'The ID of the application.').param(
                'release_id', 'The release id.', required=False).param(
                    'extension_id', 'The extension id.', required=False).param(
                        'os',
                        'The target operating system of the package.',
                        required=False,
                        enum=['linux', 'win',
                              'macosx']).param('arch',
                                               'The os chip architecture.',
                                               required=False,
                                               enum=['i386', 'amd64']).
        param('app_revision', 'The revision of the package.',
              required=False).param(
                  'search',
                  'Text matched against extension name or description.',
                  required=False).pagingParams(
                      defaultSort='created').errorResponse())
    @access.cookie
    @access.public
    def getExtensions(self, app_id, release_id, extension_id, os, arch,
                      app_revision, search, limit, offset, sort):
        """
        Get a list of extension which is filtered by some optional parameters:

        :param app_id: Application ID
        :param release_id: Release ID
        :param extension_id: Extension ID
        :param os: The operation system used for the extension.
        :param arch: The architecture compatible with the extension.
        :param app_revision: The revision of the application
        :param search: Text search on the name of the extension
        :return: The list of extensions
        """
        filters = {
            '$and': [{
                'meta.app_id': {
                    '$eq': app_id
                }
            }, {
                'meta.os': {
                    '$exists': True
                }
            }, {
                'meta.arch': {
                    '$exists': True
                }
            }, {
                'meta.revision': {
                    '$exists': True
                }
            }]
        }
        if ObjectId.is_valid(extension_id):
            filters['_id'] = ObjectId(extension_id)
        if ObjectId.is_valid(release_id):
            filters['folderId'] = ObjectId(release_id)
        if os:
            filters['meta.os'] = os
        if arch:
            filters['meta.arch'] = arch
        if app_revision:
            filters['meta.revision'] = app_revision
        if search:
            # Provide a full text search on baseName
            filters['meta.baseName'] = search

        return list(ExtensionModel().find(query=filters,
                                          limit=limit,
                                          offset=offset,
                                          sort=sort))

    @autoDescribeRoute(
        Description('Get a particular extension by name from an application.').
        responseClass('Item').param('app_id', 'The application\'s ID.').param(
            'extension_name', 'The extension\'s name.').errorResponse(
                'ID or name was invalid.').errorResponse(
                    'Read permission denied on the application.', 403))
    @access.user(scope=TokenScope.DATA_READ)
    def getExtensionByName(self, app_id, extension_name):
        """
        Get the extension item by name.

        :param app_id: Application ID
        :param extension_name: The extension name
        :return: The extension item
        """
        user = self.getCurrentUser()
        application = self._model.load(app_id, user=user)

        release_folder = list(self._model.childFolders(application, 'Folder'))
        if not release_folder:
            raise Exception('The application has no release')
        for release in release_folder:
            extensions = list(
                self._model.childItems(
                    release, filters={'lowerName': extension_name.lower()}))
            if extensions:
                return extensions[0]
        return None

    @autoDescribeRoute(  # noqa: C901
        Description('Create or Update an extension package.').param(
            'app_id', 'The ID of the App.').param(
                'os',
                'The target operating system of the package.',
                enum=['linux', 'win',
                      'macosx']).param('arch',
                                       'The os chip architecture.',
                                       enum=['i386', 'amd64']).
        param('baseName',
              'The baseName of the package (ie installer baseName).').param(
                  'repository_type',
                  'The type of the repository (svn, git).').param(
                      'repository_url', 'The url of the repository.').param(
                          'revision',
                          'The svn or git revision of the extension.').param(
                              'app_revision',
                              'The revision of the application '
                              'that the extension was built against.').param(
                                  'packagetype',
                                  'Installer, data, etc.').param(
                                      'codebase',
                                      'The codebase baseName (Ex: Slicer4).').
        param('description', 'Text describing the extension.').param(
            'release',
            'Release identifier (Ex: 0.0.1, 0.0.2, 0.1).',
            required=False).param(
                'icon_url',
                'The url of the icon for the extension.',
                required=False).param(
                    'development_status',
                    'Arbitrary description of the status of the extension '
                    '(stable, active, etc).',
                    required=False).
        param(
            'category',
            'Category under which to place the extension. Subcategories should be '
            'delimited by character. If none is passed, will render under '
            'the Miscellaneous category..',
            required=False
        ).param(
            'enabled',
            'Boolean indicating if the extension should be automatically enabled '
            'after its installation.',
            required=False).param('homepage',
                                  'The url of the extension homepage.',
                                  required=False).
        param('screenshots',
              'Space-separate list of URLs of screenshots for the extension.',
              required=False).param('contributors',
                                    'List of contributors of the extension',
                                    required=False).errorResponse())
    @access.cookie
    @access.public
    def createOrUpdateExtension(self, app_id, os, arch, baseName,
                                repository_type, repository_url, revision,
                                app_revision, packagetype, codebase,
                                description, release, icon_url,
                                development_status, category, enabled,
                                homepage, screenshots, contributors):
        """
        Upload an extension package in the database, in a specific release with providing
        ``release_id``. Or by default in the **'Nightly'** folder.

        :param app_id: The ID of the application.
        :param os: The operation system used for the extension.
        :param arch: The architecture compatible with the extension.
        :param baseName: The base name of the extension.
        :param repository_type: The type of repository (github, gitlab, ...).
        :param repository_url: The Url of the repository.
        :param revision: The revision of the extension.
        :param app_revision: The revision of the application.
        :param packagetype: Type of the extension.
        :param codebase: The codebase baseName.
        :param description: The description of the extension.
        :return: The status of the upload.
        """
        creator = self.getCurrentUser()
        application = self._model.load(app_id, user=creator)
        release_folder = None
        # Find the release by metadata revision
        releases = self._model.childFolders(application,
                                            'Folder',
                                            user=creator)
        for folder in releases:
            if 'meta' in folder:
                if folder['meta']['revision'] == app_revision:
                    release_folder = folder
                    break
        if not release_folder:
            # Only the nightly folder in the list
            release_folder = list(
                self._model.childFolders(
                    application,
                    'Folder',
                    user=creator,
                    filters={'name': constants.NIGHTLY_RELEASE_NAME}))
            if not release_folder:
                raise Exception('The %s folder not found.' %
                                constants.NIGHTLY_RELEASE_NAME)
            release_folder = release_folder[0]

        params = {
            'app_id': app_id,
            'baseName': baseName,
            'os': os,
            'arch': arch,
            'repository_type': repository_type,
            'repository_url': repository_url,
            'revision': revision,
            'app_revision': app_revision,
            'packagetype': packagetype,
            'codebase': codebase,
            'description': description
        }
        if release:
            params['release'] = release
        if icon_url:
            params['icon_url'] = icon_url
        if development_status:
            params['development_status'] = development_status
        if category:
            params['category'] = category
        if enabled:
            params['enabled'] = enabled
        if homepage:
            params['homepage'] = homepage
        if screenshots:
            params['screenshots'] = screenshots
        if contributors:
            params['contributors'] = contributors

        name = application['meta']['extensionNameTemplate'].format(**params)
        filters = {'name': name}
        # Only one extensions should be in this list
        extensions = list(ExtensionModel().get(release_folder,
                                               filters=filters))
        if not len(extensions):
            # The extension doesn't exist yet:
            extension = ExtensionModel().createExtension(
                name, creator, release_folder, params)
        elif len(extensions) == 1:
            extension = extensions[0]
        else:
            raise Exception(
                'Too many extensions found for the same name :"%s"' % name)

        # Check the file inside the extension Item
        files = Item().childFiles(extension)
        if files.count() == 1:
            old_file = files.next()
            # catch the event of upload success and remove the file
            events.bind('model.file.finalizeUpload.after', 'application',
                        File().remove(old_file))
        elif not files.count():
            # Extension new or empty
            pass
        else:
            raise Exception("More than 1 binary file in the extension.")

        old_meta = {
            'baseName': extension['meta']['baseName'],
            'os': extension['meta']['os'],
            'arch': extension['meta']['arch'],
            'revision': extension['meta']['revision'],
            'app_revision': extension['meta']['app_revision']
        }
        identifier_meta = {
            'baseName': baseName,
            'os': os,
            'arch': arch,
            'revision': revision,
            'app_revision': app_revision
        }
        if identifier_meta == old_meta and len(extensions):
            # The revision is the same than these before, no need to upload
            extension = ExtensionModel().setMetadata(extension, params)
            events.unbind('model.file.finalizeUpload.after', 'application')

        # Ready to upload the binary file
        return extension

    @access.user(scope=TokenScope.DATA_WRITE)
    @autoDescribeRoute(
        Description('Delete an Extension by ID.').param(
            'app_id', 'The ID of the App.').modelParam(
                'ext_id', model=Item, level=AccessType.WRITE).errorResponse(
                    'ID was invalid.').errorResponse(
                        'Admin access was denied for the extension.', 403))
    def deleteExtension(self, app_id, item):
        """
        Delete the extension by ID.

        :param app_id: Application ID
        :param ext_id: Extension ID
        :return: Confirmation message with the name of the deleted extension
        """
        Item().remove(item)
        return {'message': 'Deleted extension %s.' % item['name']}