Beispiel #1
0
    def _matchRoute(self, method, path):
        """
        Helper function that attempts to match the requested ``method`` and ``path`` with a
        registered route specification.

        :param method: The requested HTTP method, in lowercase.
        :type method: str
        :param path: The requested path.
        :type path: tuple[str]
        :returns: A tuple of ``(route, handler, wildcards)``, where ``route`` is the registered
                  `list` of route components, ``handler`` is the route handler `function`, and
                  ``wildcards`` is a `dict` of kwargs that should be passed to the underlying
                  handler, based on the wildcard tokens of the route.
        :raises: `GirderException`, when no routes are defined on this resource.
        :raises: `RestException`, when no route can be matched.
        """
        if not self._routes:
            raise GirderException('No routes defined for resource')

        for route, handler in self._routes[method][len(path)]:
            wildcards = {}
            for routeComponent, pathComponent in six.moves.zip(route, path):
                if routeComponent[0] == ':':  # Wildcard token
                    wildcards[routeComponent[1:]] = pathComponent
                elif routeComponent != pathComponent:  # Exact match token
                    break
            else:
                return route, handler, wildcards

        raise RestException('No matching route for "%s %s"' % (method.upper(), '/'.join(path)))
Beispiel #2
0
 def discardPartialUploads(self, uploadId, userId, parentId, assetstoreId,
                           minimumAge, includeUntracked):
     filters = {}
     if uploadId is not None:
         filters['uploadId'] = uploadId
     if userId is not None:
         filters['userId'] = userId
     if assetstoreId is not None:
         filters['assetstoreId'] = assetstoreId
     if parentId is not None:
         filters['parentId'] = parentId
     if minimumAge is not None:
         filters['minimumAge'] = minimumAge
     uploadList = list(Upload().list(filters=filters))
     # Move the results to list that isn't a cursor so we don't have to have
     # the cursor sitting around while we work on the data.
     for upload in uploadList:
         try:
             Upload().cancelUpload(upload)
         except OSError as exc:
             if exc.errno == errno.EACCES:
                 raise GirderException(
                     'Failed to delete upload.',
                     'girderformindlogger.api.v1.system.delete-upload-failed'
                 )
             raise
     if includeUntracked:
         uploadList += Upload().untrackedUploads('delete', assetstoreId)
     return uploadList
Beispiel #3
0
    def getUploadAssetstore(self, event):
        """
        Handle the model.upload.assetstore event.  This event passes a
        dictionary consisting of a model type and resource document.  If the
        base document has an assetstore policy, then set an assetstore key of
        this dictionary to an assetstore document that should be used or
        prevent the default action if no appropriate assetstores are allowed.

        :param event: event record.
        """
        model, resource = self._getBaseResource(event.info['model'],
                                                event.info['resource'])
        if resource is None:
            return
        policy = resource[QUOTA_FIELD]
        assetstore = self._checkAssetstore(
            policy.get('preferredAssetstore', None))
        if assetstore is False:
            assetstore = self._checkAssetstore(
                policy.get('fallbackAssetstore', None))
            if assetstore is not False:
                logger.info(
                    'preferredAssetstore not available for %s %s, '
                    'using fallbackAssetstore', model, resource['_id'])
        if assetstore is False:
            raise GirderException('Required assetstore is unavailable')
        if assetstore:
            event.addResponse(assetstore)
Beispiel #4
0
    def removeRoute(self, method, route, resource=None):
        """
        Remove a route from the handler and documentation.

        :param method: The HTTP method, e.g. 'GET', 'POST', 'PUT'
        :type method: str
        :param route: The route, as a list of path params relative to the
                      resource root. Elements of this list starting with ':'
                      are assumed to be wildcards.
        :type route: tuple[str]
        :param resource: the name of the resource at the root of this route.
        """
        self._ensureInit()

        nLengthRoutes = self._routes[method.lower()][len(route)]
        for i, (registeredRoute, registeredHandler) in enumerate(nLengthRoutes):
            if registeredRoute == route:
                handler = registeredHandler
                del nLengthRoutes[i]
                break
        else:
            raise GirderException('No such route: %s %s' % (method, '/'.join(route)))

        # Remove the api doc
        if resource is None:
            resource = getattr(self, 'resourceName', handler.__module__.rsplit('.', 1)[-1])
        if getattr(handler, 'description', None) is not None:
            docs.removeRouteDocs(
                resource=resource, route=route, method=method,
                info=handler.description.asDict(), handler=handler)
Beispiel #5
0
def getApiUrl(url=None, preferReferer=False):
    """
    In a request thread, call this to get the path to the root of the REST API.
    The returned path does *not* end in a forward slash.

    :param url: URL from which to extract the base URL. If not specified, uses
        the server root system setting. If that is not specified, uses `cherrypy.url()`
    :param preferReferer: if no url is specified, this is true, and this is in
        a cherrypy request that has a referer header that contains the api
        string, use that referer as the url.
    """
    apiStr = config.getConfig()['server']['api_root']

    if not url:
        if preferReferer and apiStr in cherrypy.request.headers.get('referer', ''):
            url = cherrypy.request.headers['referer']
        else:
            root = Setting().get(SettingKey.SERVER_ROOT)
            if root:
                return posixpath.join(root, apiStr.lstrip('/'))

    url = url or cherrypy.url()
    idx = url.find(apiStr)

    if idx < 0:
        raise GirderException('Could not determine API root in %s.' % url)

    return url[:idx + len(apiStr)]
Beispiel #6
0
    def moveFileToAssetstore(self,
                             file,
                             user,
                             assetstore,
                             progress=noProgress):
        """
        Move a file from whatever assetstore it is located in to a different
        assetstore.  This is done by downloading and re-uploading the file.

        :param file: the file to move.
        :param user: the user that is authorizing the move.
        :param assetstore: the destination assetstore.
        :param progress: optional progress context.
        :returns: the original file if it is not moved, or the newly 'uploaded'
            file if it is.
        """
        from girderformindlogger.models.file import File

        if file['assetstoreId'] == assetstore['_id']:
            return file
        # Allow an event to cancel the move.  This could be done, for instance,
        # on files that could change dynamically.
        event = events.trigger('model.upload.movefile', {
            'file': file,
            'assetstore': assetstore
        })
        if event.defaultPrevented:
            raise GirderException(
                'The file %s could not be moved to assetstore %s' %
                (file['_id'], assetstore['_id']))
        # Create a new upload record into the existing file
        upload = self.createUploadToFile(file=file,
                                         user=user,
                                         size=int(file['size']),
                                         assetstore=assetstore)
        if file['size'] == 0:
            return File().filter(self.finalizeUpload(upload), user)
        # Uploads need to be chunked for some assetstores
        chunkSize = self._getChunkSize()
        chunk = None
        for data in File().download(file, headers=False)():
            if chunk is not None:
                chunk += data
            else:
                chunk = data
            if len(chunk) >= chunkSize:
                upload = self.handleChunk(
                    upload, RequestBodyStream(six.BytesIO(chunk), len(chunk)))
                progress.update(increment=len(chunk))
                chunk = None

        if chunk is not None:
            upload = self.handleChunk(
                upload, RequestBodyStream(six.BytesIO(chunk), len(chunk)))
            progress.update(increment=len(chunk))

        return upload
Beispiel #7
0
    def createItem(self,
                   name,
                   creator,
                   folder,
                   description='',
                   reuseExisting=False,
                   validate=True):
        """
        Create a new item. The creator will be given admin access to it.

        :param name: The name of the item.
        :type name: str
        :param description: Description for the item.
        :type description: str
        :param folder: The parent folder of the item.
        :param creator: User document representing the creator of the item.
        :type creator: dict
        :param reuseExisting: If an item with the given name already exists
            under the given folder, return that item rather than creating a
            new one.
        :type reuseExisting: bool
        :returns: The item document that was created.
        """
        if reuseExisting:
            existing = self.findOne({'folderId': folder['_id'], 'name': name})
            if existing:
                return existing

        now = datetime.datetime.utcnow()

        if not isinstance(creator, dict) or '_id' not in creator:
            # Internal error -- this shouldn't be called without a user.
            raise GirderException(
                'Creator must be a user.',
                'girderformindlogger.models.item.creator-not-user')

        if 'baseParentType' not in folder:
            pathFromRoot = self.parentsToRoot({'folderId': folder['_id']},
                                              creator,
                                              force=True)
            folder['baseParentType'] = pathFromRoot[0]['type']
            folder['baseParentId'] = pathFromRoot[0]['object']['_id']

        return self.save(
            {
                'name': self._validateString(name),
                'description': self._validateString(description),
                'folderId': ObjectId(folder['_id']),
                'creatorId': creator['_id'],
                'baseParentType': folder['baseParentType'],
                'baseParentId': folder['baseParentId'],
                'created': now,
                'updated': now,
                'size': 0,
                'meta': {}
            },
            validate=validate)
    def createScreen(self,
                     name,
                     creator,
                     activity=None,
                     description='',
                     readOnly=False):
        """
        Create a new screen.

        :param name: The name of the item.
        :type name: str
        :param description: Description for the screen.
        :type description: str
        :param activity: The parent activity of the screen.
        :param creator: User document representing the creator of the screen.
        :type creator: dict
        :param readOnly: A ready-to-use screen
        :type readOnly: bool
        :returns: The screen item document that was created.
        """
        try:
            activity = ActivityModel().load(activity.get('_id', activity),
                                            level=AccessType.WRITE,
                                            user=creator)
        except:
            raise ValidationException(
                'Invalid Activity ID or inadequate access rights', 'activity')

        now = datetime.datetime.utcnow()

        if not isinstance(creator, dict) or '_id' not in creator:
            # Internal error -- this shouldn't be called without a user.
            raise GirderException(
                'Creator must be a user.',
                'girderformindlogger.models.item.creator-not-user')

        if 'baseParentType' not in activity:
            pathFromRoot = self.parentsToRoot({'folderId': activity['_id']},
                                              creator,
                                              force=True)
            activity['baseParentType'] = pathFromRoot[0]['type']
            activity['baseParentId'] = pathFromRoot[0]['object']['_id']

        return self.save({
            'name': self._validateString(name),
            'description': self._validateString(description),
            'folderId': ObjectId(activity['_id']),
            'creatorId': creator['_id'],
            'baseParentType': activity['baseParentType'],
            'baseParentId': activity['baseParentId'],
            'created': now,
            'updated': now,
            'size': 0,
            'readOnly': readOnly
        })
    def getCurrent(self):
        """
        Returns the current assetstore. If none exists, this will raise a 500
        exception.
        """
        current = self.findOne({'current': True})
        if current is None:
            raise GirderException(
                'No current assetstore is set.',
                'girderformindlogger.model.assetstore.no-current-assetstore')

        return current
Beispiel #10
0
def addSearchMode(mode, handler):
    """
    Register a search mode.

    New searches made for the registered mode will call the handler function. The handler function
    must take parameters: `query`, `types`, `user`, `level`, `limit`, `offset`, and return the
    search results.

    :param mode: A search mode identifier.
    :type mode: str
    :param handler: A search mode handler function.
    :type handler: function
    """
    if _allowedSearchMode.get(mode) is not None:
        raise GirderException('A search mode %r already exists.' % mode)
    _allowedSearchMode[mode] = handler
Beispiel #11
0
def _loadPlugins(info, names=None):
    """Load plugins with the given app info object.

    If `names` is None, all installed plugins will be loaded. If `names` is a
    list, then only those plugins in the provided list will be loaded.
    """
    if names is None:
        names = allPlugins()

    for name in names:
        pluginObject = getPlugin(name)

        if pluginObject is None:
            raise GirderException('Plugin %s is not installed' % name)

        pluginObject.load(info)
Beispiel #12
0
    def read(self, size=None):
        """
        Read *size* bytes from the file data.

        :param size: The number of bytes to read from the current position. The
            actual number returned could be less than this if the end of the
            file is reached. An empty response indicates that the file has been
            completely consumed.  If None or negative, read to the end of the
            file.
        :type size: int
        :rtype: bytes
        """
        if size is None or size < 0:
            size = self._file['size'] - self._pos
        if size > self._maximumReadSize:
            raise GirderException('Read exceeds maximum allowed size.')
        data = six.BytesIO()
        length = 0
        for chunk in itertools.chain(self._prev, self._stream):
            chunkLen = len(chunk)

            if chunkLen == 0:
                break

            if length + chunkLen <= size:
                data.write(chunk)
                self._prev = []
                length += chunkLen

                if length == size:
                    break
            else:
                chunkLen = min(size - length, chunkLen)
                data.write(chunk[:chunkLen])
                self._prev = [chunk[chunkLen:]]
                length += chunkLen
                break

        self._pos += length
        return data.getvalue()
Beispiel #13
0
    def downloadFile(self, file, offset=0, headers=True, endByte=None,
                     contentDisposition=None, extraParameters=None, **kwargs):
        """
        Returns a generator function that will be used to stream the file from
        disk to the response.
        """
        if endByte is None or endByte > file['size']:
            endByte = file['size']

        path = self.fullPath(file)

        if not os.path.isfile(path):
            raise GirderException(
                'File %s does not exist.' % path,
                'girderformindlogger.utility.filesystem_assetstore_adapter.'
                'file-does-not-exist')

        if headers:
            setResponseHeader('Accept-Ranges', 'bytes')
            self.setContentHeaders(file, offset, endByte, contentDisposition)

        def stream():
            bytesRead = offset
            with open(path, 'rb') as f:
                if offset > 0:
                    f.seek(offset)

                while True:
                    readLen = min(BUF_SIZE, endByte - bytesRead)
                    if readLen <= 0:
                        break

                    data = f.read(readLen)
                    bytesRead += readLen

                    if not data:
                        break
                    yield data

        return stream
Beispiel #14
0
    def validate(self, doc, allowRename=False):
        """
        Validate the name and description of the folder, ensure that it is
        associated with a valid parent and that it has a unique name.

        :param doc: the folder document to validate.
        :param allowRename: if True and a folder or item exists with the same
                            name, rename the folder so that it is unique.
        :returns: `the validated folder document`
        """
        from girderformindlogger.models.item import Item

        doc['name'] = doc['name'].strip()
        doc['lowerName'] = doc['name'].lower()
        doc['description'] = doc['description'].strip()

        if not doc['name']:
            raise ValidationException('Folder name must not be empty.', 'name')

        if not doc['parentCollection'] in ('folder', 'user', 'collection'):
            # Internal error; this shouldn't happen
            raise GirderException('Invalid folder parent type: %s.' %
                                  doc['parentCollection'],
                                  'girderformindlogger.models.folder.invalid-parent-type')
        name = doc['name']
        # If the folder already exists with the current name, don't check.
        # Although we don't want duplicate names, they can occur when there are
        # simultaneous uploads, and also because Mongo has no guaranteed
        # multi-collection uniqueness constraints.  If this occurs, and we are
        # changing a non-name property, don't validate the name (since that may
        # fail).  If the name is being changed, validate that it is probably
        # unique.
        checkName = '_id' not in doc or not self.findOne({'_id': doc['_id'], 'name': name})
        n = 0
        itemModel = Item()
        while checkName:
            q = {
                'parentId': doc['parentId'],
                'name': name,
                'parentCollection': doc['parentCollection']
            }
            if '_id' in doc:
                q['_id'] = {'$ne': doc['_id']}
            dupFolder = self.findOne(q, fields=['_id'])
            if doc['parentCollection'] == 'folder':
                q = {
                    'folderId': doc['parentId'],
                    'name': name
                }
                dupItem = itemModel.findOne(q, fields=['_id'])
            else:
                dupItem = None
            if dupItem is None and dupFolder is None:
                doc['name'] = name
                break
            if not allowRename:
                if dupFolder:
                    raise ValidationException('A folder with that name '
                                              'already exists here.', 'name')
                raise ValidationException('An item with that name already '
                                          'exists here.', 'name')
            n += 1
            name = '%s (%d)' % (doc['name'], n)
        return doc
Beispiel #15
0
    def initUpload(self, parentType, parentId, name, size, mimeType, linkUrl,
                   reference, assetstoreId):
        """
        Before any bytes of the actual file are sent, a request should be made
        to initialize the upload. This creates the temporary record of the
        forthcoming upload that will be passed in chunks to the readChunk
        method. If you pass a "linkUrl" parameter, it will make a link file
        in the designated parent.
        """
        user = self.getCurrentUser()
        parent = ModelImporter.model(parentType).load(id=parentId,
                                                      user=user,
                                                      level=AccessType.WRITE,
                                                      exc=True)

        if linkUrl is not None:
            return self._model.filter(
                self._model.createLinkFile(url=linkUrl,
                                           parent=parent,
                                           name=name,
                                           parentType=parentType,
                                           creator=user,
                                           size=size,
                                           mimeType=mimeType), user)
        else:
            self.requireParams({'size': size})
            assetstore = None
            if assetstoreId:
                self.requireAdmin(
                    user,
                    message=
                    'You must be an admin to select a destination assetstore.')
                assetstore = Assetstore().load(assetstoreId)

            chunk = None
            if size > 0 and cherrypy.request.headers.get('Content-Length'):
                ct = cherrypy.request.body.content_type.value
                if (ct not in cherrypy.request.body.processors and ct.split(
                        '/', 1)[0] not in cherrypy.request.body.processors):
                    chunk = RequestBodyStream(cherrypy.request.body)
            if chunk is not None and chunk.getSize() <= 0:
                chunk = None

            try:
                # TODO: This can be made more efficient by adding
                #    save=chunk is None
                # to the createUpload call parameters.  However, since this is
                # a breaking change, that should be deferred until a major
                # version upgrade.
                upload = Upload().createUpload(user=user,
                                               name=name,
                                               parentType=parentType,
                                               parent=parent,
                                               size=size,
                                               mimeType=mimeType,
                                               reference=reference,
                                               assetstore=assetstore)
            except OSError as exc:
                if exc.errno == errno.EACCES:
                    raise GirderException(
                        'Failed to create upload.',
                        'girderformindlogger.api.v1.file.create-upload-failed')
                raise
            if upload['size'] > 0:
                if chunk:
                    return Upload().handleChunk(upload,
                                                chunk,
                                                filter=True,
                                                user=user)

                return upload
            else:
                return self._model.filter(Upload().finalizeUpload(upload),
                                          user)