Beispiel #1
0
    def destroy(self, path):
        """
        Handle shutdown of the FUSE.

        :param path: always '/'.
        """
        Setting().unset(SettingKey.GIRDER_MOUNT_INFORMATION)
        events.trigger('server_fuse.destroy')
        return super(ServerFuse, self).destroy(path)
 def stream():
     yield file['linkUrl'][offset:endByte]
     if endByte >= len(file['linkUrl']):
         events.trigger('model.file.download.complete',
                        info={
                            'file': file,
                            'startByte': offset,
                            'endByte': endByte,
                            'redirect': False
                        })
Beispiel #3
0
 def scheduleJob(self, job):
     """
     Trigger the event to schedule this job. Other plugins are in charge of
     actually scheduling and/or executing the job, except in the case when
     the handler is 'local'.
     """
     if job.get('asynchronous', job.get('async')) is True:
         events.daemon.trigger('jobs.schedule', info=job)
     else:
         events.trigger('jobs.schedule', info=job)
Beispiel #4
0
    def copyItem(self,
                 srcItem,
                 creator,
                 name=None,
                 folder=None,
                 description=None):
        """
        Copy an item, including duplicating files and metadata.

        :param srcItem: the item to copy.
        :type srcItem: dict
        :param creator: the user who will own the copied item.
        :param name: The name of the new item.  None to copy the original name.
        :type name: str
        :param folder: The parent folder of the new item.  None to store in the
                same folder as the original item.
        :param description: Description for the new item.  None to copy the
                original description.
        :type description: str
        :returns: the new item.
        """
        from girderformindlogger.models.file import File
        from girderformindlogger.models.folder import Folder

        if name is None:
            name = srcItem['name']
        if folder is None:
            folder = Folder().load(srcItem['folderId'], force=True)
        if description is None:
            description = srcItem['description']
        newItem = self.createItem(folder=folder,
                                  name=name,
                                  creator=creator,
                                  description=description)
        # copy metadata and other extension values
        newItem['meta'] = copy.deepcopy(srcItem['meta'])
        filteredItem = self.filter(newItem, creator)
        for key in srcItem:
            if key not in filteredItem and key not in newItem:
                newItem[key] = copy.deepcopy(srcItem[key])
        # add a reference to the original item
        newItem['copyOfItem'] = srcItem['_id']
        newItem = self.save(newItem, triggerEvents=False)

        # Give listeners a chance to change things
        events.trigger('model.item.copy.prepare', (srcItem, newItem))
        # copy files
        fileModel = File()
        for file in self.childFiles(item=srcItem):
            fileModel.copyFile(file, creator=creator, item=newItem)

        # Reload to get updated size value
        newItem = self.load(newItem['_id'], force=True)
        events.trigger('model.item.copy.after', newItem)
        return newItem
 def downloadGenerator():
     for data in fileDownload():
         yield data
     if endByte is None or endByte >= file['size']:
         events.trigger('model.file.download.complete',
                        info={
                            'file': file,
                            'startByte': offset,
                            'endByte': endByte,
                            'redirect': False
                        })
Beispiel #6
0
 def _importDataAsItem(self, name, user, folder, path, files, reuseExisting=True, params=None):
     params = params or {}
     item = Item().createItem(
         name=name, creator=user, folder=folder, reuseExisting=reuseExisting)
     events.trigger('filesystem_assetstore_imported',
                    {'id': item['_id'], 'type': 'item',
                     'importPath': path})
     for fname in files:
         fpath = os.path.join(path, fname)
         if self.shouldImportFile(fpath, params):
             self.importFile(item, fpath, user, name=fname)
Beispiel #7
0
    def copyFolderComponents(self, srcFolder, newFolder, creator, progress,
                             firstFolder=None):
        """
        Copy the items, subfolders, and extended data of a folder that was just
        copied.

        :param srcFolder: the original folder.
        :type srcFolder: dict
        :param newFolder: the new folder.
        :type newFolder: dict
        :param creator: user representing the creator of the new folder.
        :type creator: dict
        :param progress: a progress context to record process on.
        :type progress: girderformindlogger.utility.progress.ProgressContext or None.
        :param firstFolder: if not None, the first folder copied in a tree of
                            folders.
        :returns: the new folder document.
        """
        from girderformindlogger.models.item import Item

        # copy metadata and other extension values
        updated = False
        if srcFolder['meta']:
            newFolder['meta'] = copy.deepcopy(srcFolder['meta'])
            updated = True

        filteredFolder = self.filter(newFolder, creator)
        for key in srcFolder:
            if key not in filteredFolder and key not in newFolder:
                newFolder[key] = copy.deepcopy(srcFolder[key])
                updated = True
        if updated:
            newFolder = self.save(newFolder, triggerEvents=False)
        # Give listeners a chance to change things
        events.trigger('model.folder.copy.prepare', (srcFolder, newFolder))
        # copy items
        itemModel = Item()
        for item in self.childItems(folder=srcFolder):
            setResponseTimeLimit()
            itemModel.copyItem(item, creator, folder=newFolder)
            if progress:
                progress.update(increment=1, message='Copied item ' + item['name'])
        # copy subfolders
        for sub in self.childFolders(parentType='folder', parent=srcFolder, user=creator):
            if firstFolder and firstFolder['_id'] == sub['_id']:
                continue
            self.copyFolder(sub, parent=newFolder, parentType='folder',
                            creator=creator, progress=progress)
        events.trigger('model.folder.copy.after', newFolder)
        if progress:
            progress.update(increment=1, message='Copied folder ' + newFolder['name'])

        # Reload to get updated size value
        return self.load(newFolder['_id'], force=True)
Beispiel #8
0
    def _importFileToFolder(self, name, user, parent, parentType, path):
        if parentType != 'folder':
            raise ValidationException(
                'Files cannot be imported directly underneath a %s.' % parentType)

        item = Item().createItem(name=name, creator=user, folder=parent, reuseExisting=True)
        events.trigger('filesystem_assetstore_imported', {
            'id': item['_id'],
            'type': 'item',
            'importPath': path
        })
        self.importFile(item, path, user, name=name)
Beispiel #9
0
    def importData(self, parent, parentType, params, progress, user, leafFoldersAsItems):
        importPath = params['importPath']

        if not os.path.exists(importPath):
            raise ValidationException('Not found: %s.' % importPath)
        if not os.path.isdir(importPath):
            name = os.path.basename(importPath)
            progress.update(message=name)
            self._importFileToFolder(name, user, parent, parentType, importPath)
            return

        listDir = os.listdir(importPath)

        if parentType != 'folder' and any(
                os.path.isfile(os.path.join(importPath, val)) for val in listDir):
            raise ValidationException(
                'Files cannot be imported directly underneath a %s.' % parentType)

        if leafFoldersAsItems and self._hasOnlyFiles(importPath, listDir):
            self._importDataAsItem(
                os.path.basename(importPath.rstrip(os.sep)), user, parent, importPath,
                listDir, params=params)
            return

        for name in listDir:
            progress.update(message=name)
            path = os.path.join(importPath, name)

            if os.path.isdir(path):
                localListDir = os.listdir(path)
                if leafFoldersAsItems and self._hasOnlyFiles(path, localListDir):
                    self._importDataAsItem(name, user, parent, path, localListDir, params=params)
                else:
                    folder = Folder().createFolder(
                        parent=parent, name=name, parentType=parentType,
                        creator=user, reuseExisting=True)
                    events.trigger(
                        'filesystem_assetstore_imported', {
                            'id': folder['_id'],
                            'type': 'folder',
                            'importPath': path
                        })
                    nextPath = os.path.join(importPath, name)
                    self.importData(
                        folder, 'folder', params=dict(params, importPath=nextPath),
                        progress=progress, user=user, leafFoldersAsItems=leafFoldersAsItems)
            else:
                if self.shouldImportFile(path, params):
                    self._importFileToFolder(name, user, parent, parentType, path)
Beispiel #10
0
    def isValid(status):
        event = events.trigger('jobs.status.validate', info=status)

        if event.defaultPrevented and len(event.responses):
            return event.responses[-1]

        return status in (JobStatus.INACTIVE, JobStatus.QUEUED,
                          JobStatus.RUNNING, JobStatus.SUCCESS,
                          JobStatus.ERROR, JobStatus.CANCELED)
Beispiel #11
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 #12
0
    def updateAssetstore(self, assetstore, name, root, perms, db, mongohost,
                         replicaset, bucket, prefix, accessKeyId, secret,
                         service, readOnly, region, current, inferCredentials,
                         serverSideEncryption, params):
        assetstore['name'] = name
        assetstore['current'] = current

        if assetstore['type'] == AssetstoreType.FILESYSTEM:
            self.requireParams({'root': root})
            assetstore['root'] = root
            if perms is not None:
                assetstore['perms'] = perms
        elif assetstore['type'] == AssetstoreType.GRIDFS:
            self.requireParams({'db': db})
            assetstore['db'] = db
            if mongohost is not None:
                assetstore['mongohost'] = mongohost
            if replicaset is not None:
                assetstore['replicaset'] = replicaset
        elif assetstore['type'] == AssetstoreType.S3:
            self.requireParams({'bucket': bucket})
            assetstore['bucket'] = bucket
            assetstore['prefix'] = prefix
            assetstore['accessKeyId'] = accessKeyId
            assetstore['secret'] = secret
            assetstore['service'] = service
            assetstore['region'] = region
            assetstore['inferCredentials'] = inferCredentials
            assetstore['serverSideEncryption'] = serverSideEncryption
            if readOnly is not None:
                assetstore['readOnly'] = readOnly
        else:
            event = events.trigger('assetstore.update',
                                   info={
                                       'assetstore':
                                       assetstore,
                                       'params':
                                       dict(name=name,
                                            current=current,
                                            readOnly=readOnly,
                                            root=root,
                                            perms=perms,
                                            db=db,
                                            mongohost=mongohost,
                                            replicaset=replicaset,
                                            bucket=bucket,
                                            prefix=prefix,
                                            accessKeyId=accessKeyId,
                                            secret=secret,
                                            service=service,
                                            region=region,
                                            **params)
                                   })
            if event.defaultPrevented:
                return
        return self._model.save(assetstore)
Beispiel #13
0
    def save(self, document, validate=True, triggerEvents=True):
        if validate and triggerEvents:
            event = events.trigger('.'.join(('model', self.name, 'validate')), document)
            if event.defaultPrevented:
                validate = False

        if validate:
            document = self.validate(document)

        self.encryptFields(document, self.fields)
        return self.decryptFields(super().save(document, False, triggerEvents), self.fields)
Beispiel #14
0
def _uploadHandler(event):
    """
    Whenever an additional file is uploaded to a "DICOM item", remove any
    DICOM metadata that is no longer common to all DICOM files in the item.
    """
    file = event.info['file']
    fileMetadata = _parseFile(file)
    if fileMetadata is None:
        return
    item = Item().load(file['itemId'], force=True)
    if 'dicom' in item:
        item['dicom']['meta'] = _removeUniqueMetadata(item['dicom']['meta'],
                                                      fileMetadata)
    else:
        # In this case the uploaded file is the first of the item
        item['dicom'] = {'meta': fileMetadata, 'files': []}
    item['dicom']['files'].append(_extractFileData(file, fileMetadata))
    item['dicom']['files'].sort(key=_getDicomFileSortKey)
    Item().save(item)
    events.trigger('dicom_viewer.upload.success')
Beispiel #15
0
    def callback(self, provider, state, code, error):
        if error is not None:
            raise RestException("Provider returned error: '%s'." % error,
                                code=502)

        self.requireParams({'state': state, 'code': code})

        providerName = provider
        provider = providers.idMap.get(providerName)
        if not provider:
            raise RestException('Unknown provider "%s".' % providerName)

        redirect = self._validateCsrfToken(state)

        providerObj = provider(cherrypy.url())
        token = providerObj.getToken(code)

        event = events.trigger('oauth.auth_callback.before', {
            'provider': provider,
            'token': token
        })
        if event.defaultPrevented:
            raise cherrypy.HTTPRedirect(redirect)

        user = providerObj.getUser(token)

        event = events.trigger('oauth.auth_callback.after', {
            'provider': provider,
            'token': token,
            'user': user
        })
        if event.defaultPrevented:
            raise cherrypy.HTTPRedirect(redirect)

        girderToken = self.sendAuthTokenCookie(user)
        try:
            redirect = redirect.format(girderToken=str(girderToken['_id']))
        except KeyError:
            pass  # in case there's another {} that's not handled by format

        raise cherrypy.HTTPRedirect(redirect)
Beispiel #16
0
    def cancelJob(self, job):
        """
        Revoke/cancel a job. This simply triggers the jobs.cancel event and
        sets the job status to CANCELED. If one of the event handlers
        calls preventDefault() on the event, this job will *not* be put into
        the CANCELED state.

        :param job: The job to cancel.
        """
        event = events.trigger('jobs.cancel', info=job)

        if not event.defaultPrevented:
            job = self.updateJob(job, status=JobStatus.CANCELED)

        return job
Beispiel #17
0
    def validTransitions(job, status):
        """
        Returns a list of states that it is valid to transition from for the
        status.

        :param status: The status being transitioned to.
        :type status: str
        :return Returns list of states it valid to transition from.
        """
        event = events.trigger('jobs.status.validTransitions',
                               info={
                                   'job': job,
                                   'status': status
                               })

        if event.defaultPrevented and len(event.responses):
            return event.responses[-1]

        return JobStatus.valid_transitions.get(status)
Beispiel #18
0
def getCurrentUser(returnToken=False):
    """
    Returns the currently authenticated user based on the token header or
    parameter.

    :param returnToken: Whether we should return a tuple that also contains the
                        token.
    :type returnToken: bool
    :returns: the user document from the database, or None if the user is not
              logged in or the token is invalid or expired.  If
              returnToken=True, returns a tuple of (user, token).
    """
    if not returnToken and hasattr(cherrypy.request, 'girderUser'):
        return cherrypy.request.girderUser

    event = events.trigger('auth.user.get')
    if event.defaultPrevented and len(event.responses) > 0:
        return event.responses[0]

    token = getCurrentToken()

    def retVal(user, token):
        setCurrentUser(user)

        if returnToken:
            return user, token
        else:
            return user

    if (token is None
            or token['expires'] < datetime.datetime.utcnow()
            or 'userId' not in token):
        return retVal(None, token)
    else:
        try:
            ensureTokenScopes(token, getattr(
                cherrypy.request, 'requiredScopes', TokenScope.USER_AUTH))
        except AccessException:
            return retVal(None, token)

        user = User().load(token['userId'], force=True)
        return retVal(user, token)
Beispiel #19
0
    def getTargetAssetstore(self, modelType, resource, assetstore=None):
        """
        Get the assetstore for a particular target resource, i.e. where new
        data within the resource should be stored. In Girder core, this is
        always just the current assetstore, but plugins may override this
        behavior to allow for more granular assetstore selection.

        :param modelType: the type of the resource that will be stored.
        :param resource: the resource to be stored.
        :param assetstore: if specified, the preferred assetstore where the
            resource should be located.  This may be overridden.
        :returns: the selected assetstore.
        """
        from girderformindlogger.models.assetstore import Assetstore

        eventParams = {'model': modelType, 'resource': resource}
        event = events.trigger('model.upload.assetstore', eventParams)

        if event.responses:
            assetstore = event.responses[-1]
        elif not assetstore:
            assetstore = Assetstore().getCurrent()

        return assetstore
Beispiel #20
0
    def finalizeUpload(self, upload, assetstore=None):
        """
        This should only be called manually in the case of creating an
        empty file, i.e. one that has no chunks.

        :param upload: The upload document.
        :type upload: dict
        :param assetstore: If known, the containing assetstore for the upload.
        :type assetstore: dict
        :returns: The file object that was created.
        """
        from girderformindlogger.models.assetstore import Assetstore
        from girderformindlogger.models.file import File
        from girderformindlogger.models.item import Item
        from girderformindlogger.utility import assetstore_utilities

        events.trigger('model.upload.finalize', upload)
        if assetstore is None:
            assetstore = Assetstore().load(upload['assetstoreId'])

        if 'fileId' in upload:  # Updating an existing file's contents
            file = File().load(upload['fileId'], force=True)

            # Delete the previous file contents from the containing assetstore
            assetstore_utilities.getAssetstoreAdapter(Assetstore().load(
                file['assetstoreId'])).deleteFile(file)

            item = Item().load(file['itemId'], force=True)
            File().propagateSizeChange(item, upload['size'] - file['size'])

            # Update file info
            file['creatorId'] = upload['userId']
            file['created'] = datetime.datetime.utcnow()
            file['assetstoreId'] = assetstore['_id']
            file['size'] = upload['size']
            # If the file was previously imported, it is no longer.
            if file.get('imported'):
                file['imported'] = False

        else:  # Creating a new file record
            if upload.get('attachParent'):
                item = None
            elif upload['parentType'] == 'folder':
                # Create a new item with the name of the file.
                item = Item().createItem(name=upload['name'],
                                         creator={'_id': upload['userId']},
                                         folder={'_id': upload['parentId']})
            elif upload['parentType'] == 'item':
                item = Item().load(id=upload['parentId'], force=True)
            else:
                item = None

            file = File().createFile(item=item,
                                     name=upload['name'],
                                     size=upload['size'],
                                     creator={'_id': upload['userId']},
                                     assetstore=assetstore,
                                     mimeType=upload['mimeType'],
                                     saveFile=False)
            if upload.get('attachParent'):
                if upload['parentType'] and upload['parentId']:
                    file['attachedToType'] = upload['parentType']
                    file['attachedToId'] = upload['parentId']

        adapter = assetstore_utilities.getAssetstoreAdapter(assetstore)
        file = adapter.finalizeUpload(upload, file)

        event_document = {'file': file, 'upload': upload}
        events.trigger('model.file.finalizeUpload.before', event_document)
        file = File().save(file)
        events.trigger('model.file.finalizeUpload.after', event_document)
        if '_id' in upload:
            self.remove(upload)

        logger.info('Upload complete. Upload=%s File=%s User=%s' %
                    (upload['_id'], file['_id'], upload['userId']))

        # Add an async event for handlers that wish to process this file.
        eventParams = {
            'file': file,
            'assetstore': assetstore,
            'currentToken': rest.getCurrentToken(),
            'currentUser': rest.getCurrentUser()
        }
        if 'reference' in upload:
            eventParams['reference'] = upload['reference']
        events.daemon.trigger('data.process', eventParams)

        return file
Beispiel #21
0
    def updateJob(self,
                  job,
                  log=None,
                  overwrite=False,
                  status=None,
                  progressTotal=None,
                  progressCurrent=None,
                  notify=True,
                  progressMessage=None,
                  otherFields=None):
        """
        Update an existing job. Any of the updateable fields that are set to None in the kwargs of
        this method will not be modified. If you set progress information on the job for the first
        time and set notify=True, a new notification record for the job progress will be created.
        If notify=True, job status changes will also create a notification with type="job_status",
        and log changes will create a notification with type="job_log".

        :param job: The job document to update.
        :param log: Message to append to the job log. If you wish to overwrite
            instead of append, pass overwrite=True.
        :type log: str
        :param overwrite: Whether to overwrite the log (default is append).
        :type overwrite: bool
        :param status: New status for the job.
        :type status: JobStatus
        :param progressTotal: Max progress value for this job.
        :param otherFields: Any additional fields to set on the job.
        :type otherFields: dict
        """
        event = events.trigger(
            'jobs.job.update', {
                'job': job,
                'params': {
                    'log': log,
                    'overwrite': overwrite,
                    'status': status,
                    'progressTotal': progressTotal,
                    'progressMessage': progressMessage,
                    'otherFields': otherFields
                }
            })

        if event.defaultPrevented:
            return job

        now = datetime.datetime.utcnow()
        user = None
        otherFields = otherFields or {}
        if job['userId']:
            user = User().load(job['userId'], force=True)

        query = {'_id': job['_id']}

        updates = {'$push': {}, '$set': {}}

        statusChanged = False
        if log is not None:
            self._updateLog(job, log, overwrite, now, notify, user, updates)
        if status is not None:
            try:
                status = int(status)
            except ValueError:
                # Allow non int states
                pass
            statusChanged = status != job['status']
            self._updateStatus(job, status, now, query, updates)
        if progressMessage is not None or progressCurrent is not None or progressTotal is not None:
            self._updateProgress(job, progressTotal, progressCurrent,
                                 progressMessage, notify, user, updates)

        for k, v in six.viewitems(otherFields):
            job[k] = v
            updates['$set'][k] = v

        if updates['$set'] or updates['$push']:
            if not updates['$push']:
                del updates['$push']
            job['updated'] = now
            updates['$set']['updated'] = now

            updateResult = self.update(query, update=updates, multi=False)
            # If our query didn't match anything then our state transition
            # was not valid. So raise an exception
            if updateResult.matched_count != 1:
                job = self.load(job['_id'], force=True)
                msg = "Invalid state transition to '%s', Current state is '%s'." % (
                    status, job['status'])
                raise ValidationException(msg, field='status')

            events.trigger('jobs.job.update.after', {'job': job})

        # We don't want todo this until we know the update was successful
        if statusChanged and user is not None and notify:
            self._createUpdateStatusNotification(now, user, job)

        return job
Beispiel #22
0
def createThumbnail(width, height, crop, fileId, attachToType, attachToId):
    """
    Creates the thumbnail. Validation and access control must be done prior
    to the invocation of this method.
    """
    fileModel = File()
    file = fileModel.load(fileId, force=True)
    streamFn = functools.partial(fileModel.download, file, headers=False)

    event = events.trigger('thumbnails.create',
                           info={
                               'file': file,
                               'width': width,
                               'height': height,
                               'crop': crop,
                               'attachToType': attachToType,
                               'attachToId': attachToId,
                               'streamFn': streamFn
                           })

    if len(event.responses):
        resp = event.responses[-1]
        newFile = resp['file']

        if event.defaultPrevented:
            if resp.get('attach', True):
                newFile = attachThumbnail(file, newFile, attachToType,
                                          attachToId, width, height)
            return newFile
        else:
            file = newFile
            streamFn = functools.partial(fileModel.download,
                                         file,
                                         headers=False)

    if 'assetstoreId' not in file:
        # TODO we could thumbnail link files if we really wanted.
        raise Exception('File %s has no assetstore.' % fileId)

    stream = streamFn()
    data = b''.join(stream())

    image = _getImage(file['mimeType'], file['exts'], data)

    if not width:
        width = int(height * image.size[0] / image.size[1])
    elif not height:
        height = int(width * image.size[1] / image.size[0])
    elif crop:
        x1 = y1 = 0
        x2, y2 = image.size
        wr = float(image.size[0]) / width
        hr = float(image.size[1]) / height

        if hr > wr:
            y1 = int(y2 / 2 - height * wr / 2)
            y2 = int(y2 / 2 + height * wr / 2)
        else:
            x1 = int(x2 / 2 - width * hr / 2)
            x2 = int(x2 / 2 + width * hr / 2)
        image = image.crop((x1, y1, x2, y2))

    image.thumbnail((width, height), Image.ANTIALIAS)

    out = six.BytesIO()
    image.convert('RGB').save(out, 'JPEG', quality=85)
    size = out.tell()
    out.seek(0)

    thumbnail = Upload().uploadFromFile(out,
                                        size=size,
                                        name='_thumb.jpg',
                                        parentType=attachToType,
                                        parent={'_id': ObjectId(attachToId)},
                                        user=None,
                                        mimeType='image/jpeg',
                                        attachParent=True)

    return attachThumbnail(file, thumbnail, attachToType, attachToId, width,
                           height)
    def download(self,
                 file,
                 offset=0,
                 headers=True,
                 endByte=None,
                 contentDisposition=None,
                 extraParameters=None):
        """
        Use the appropriate assetstore adapter for whatever assetstore the
        file is stored in, and call downloadFile on it. If the file is a link
        file rather than a file in an assetstore, we redirect to it.

        :param file: The file to download.
        :param offset: The start byte within the file.
        :type offset: int
        :param headers: Whether to set headers (i.e. is this an HTTP request
            for a single file, or something else).
        :type headers: bool
        :param endByte: Final byte to download. If ``None``, downloads to the
            end of the file.
        :type endByte: int or None
        :param contentDisposition: Content-Disposition response header
            disposition-type value.
        :type contentDisposition: str or None
        :type extraParameters: str or None
        """
        events.trigger('model.file.download.request',
                       info={
                           'file': file,
                           'startByte': offset,
                           'endByte': endByte
                       })

        auditLogger.info('file.download',
                         extra={
                             'details': {
                                 'fileId': file['_id'],
                                 'startByte': offset,
                                 'endByte': endByte,
                                 'extraParameters': extraParameters
                             }
                         })

        if file.get('assetstoreId'):
            try:
                fileDownload = self.getAssetstoreAdapter(file).downloadFile(
                    file,
                    offset=offset,
                    headers=headers,
                    endByte=endByte,
                    contentDisposition=contentDisposition,
                    extraParameters=extraParameters)

                def downloadGenerator():
                    for data in fileDownload():
                        yield data
                    if endByte is None or endByte >= file['size']:
                        events.trigger('model.file.download.complete',
                                       info={
                                           'file': file,
                                           'startByte': offset,
                                           'endByte': endByte,
                                           'redirect': False
                                       })

                return downloadGenerator
            except cherrypy.HTTPRedirect:
                events.trigger('model.file.download.complete',
                               info={
                                   'file': file,
                                   'startByte': offset,
                                   'endByte': endByte,
                                   'redirect': True
                               })
                raise
        elif file.get('linkUrl'):
            if headers:
                events.trigger('model.file.download.complete',
                               info={
                                   'file': file,
                                   'startByte': offset,
                                   'endByte': endByte,
                                   'redirect': True
                               })
                raise cherrypy.HTTPRedirect(file['linkUrl'])
            else:
                endByte = endByte or len(file['linkUrl'])

                def stream():
                    yield file['linkUrl'][offset:endByte]
                    if endByte >= len(file['linkUrl']):
                        events.trigger('model.file.download.complete',
                                       info={
                                           'file': file,
                                           'startByte': offset,
                                           'endByte': endByte,
                                           'redirect': False
                                       })

                return stream
        else:
            raise Exception('File has no known download mechanism.')
Beispiel #24
0
    def authenticate(self,
                     login,
                     password,
                     otpToken=None,
                     deviceId=None,
                     timezone=0,
                     loginAsEmail=False):
        """
        Validate a user login via username and password. If authentication
        fails, an ``AccessException`` is raised.

        :param login: The user's login or email.
        :type login: str
        :param password: The user's password.
        :type password: str
        :param otpToken: A one-time password for the user. If "True", then the
                         one-time password (if required) is assumed to be
                         concatenated to the password.
        :type otpToken: str or bool or None
        :returns: The corresponding user if the login was successful.
        :rtype: dict
        """
        user = None
        event = events.trigger('model.user.authenticate', {
            'login': login,
            'password': password
        })

        if event.defaultPrevented and len(event.responses):
            return event.responses[-1]

        login = login.lower().strip()
        loginField = 'email' if loginAsEmail else 'login'

        user = self.findOne({
            loginField: self.hash(login),
            'email_encrypted': True
        })

        if user is None and loginField == 'email':
            user = self.findOne({
                loginField: login,
                'email_encrypted': {
                    '$ne': True
                }
            })

        if user is None:
            raise AccessException('Login failed. User not found.')

        # Handle users with no password
        if not self.hasPassword(user):
            e = events.trigger('no_password_login_attempt', {
                'user': user,
                'password': password
            })

            if len(e.responses):
                return e.responses[-1]

            raise ValidationException(
                'This user does not have a password. You must log in with an '
                'external service, or reset your password.')

        # Handle OTP token concatenation
        if otpToken is True and self.hasOtpEnabled(user):
            # Assume the last (typically 6) characters are the OTP, so split at
            # that point
            otpTokenLength = self._TotpFactory.digits
            otpToken = password[-otpTokenLength:]
            password = password[:-otpTokenLength]

        self._verify_password(password, user)

        # Verify OTP
        if self.hasOtpEnabled(user):
            if otpToken is None:
                raise AccessException(
                    'User authentication must include a one-time password '
                    '(typically in the "Girder-OTP" header).')
            self.verifyOtp(user, otpToken)
        elif isinstance(otpToken, six.string_types):
            raise AccessException(
                'The user has not enabled one-time passwords.')

        # This has the same behavior as User.canLogin, but returns more
        # detailed error messages
        if user.get('status', 'enabled') == 'disabled':
            return {'exception': 'Account is disabled.'}

        if self.emailVerificationRequired(user):
            return {'exception': 'Email verification is required.'}

        if self.adminApprovalRequired(user):
            return {'exception': 'Admin approval required'}

        return user
Beispiel #25
0
    def handleRoute(self, method, path, params):
        """
        Match the requested path to its corresponding route, and calls the
        handler for that route with the appropriate kwargs. If no route
        matches the path requested, throws a RestException.

        This method fires two events for each request if a matching route is
        found. The names of these events are derived from the route matched by
        the request. As an example, if the user calls GET /api/v1/item/123,
        the following two events would be fired:

            ``rest.get.item/:id.before``

        would be fired prior to calling the default API function, and

            ``rest.get.item/:id.after``

        would be fired after the route handler returns. The query params are
        passed in the info of the before and after event handlers as
        event.info['params'], and the matched route tokens are passed in
        as dict items of event.info, so in the previous example event.info would
        also contain an 'id' key with the value of 123. For endpoints with empty
        sub-routes, the trailing slash is omitted from the event name, e.g.:

            ``rest.post.group.before``

        .. note:: You will normally not need to call this method directly, as it
           is called by the internals of this class during the routing process.

        :param method: The HTTP method of the current request.
        :type method: str
        :param path: The path params of the request.
        :type path: tuple[str]
        """
        method = method.lower()

        route, handler, kwargs = self._matchRoute(method, path)

        cherrypy.request.requiredScopes = getattr(
            handler, 'requiredScopes', None) or TokenScope.USER_AUTH

        if getattr(handler, 'cookieAuth', False):
            cherrypy.request.girderAllowCookie = True

        kwargs['params'] = params
        # Add before call for the API method. Listeners can return
        # their own responses by calling preventDefault() and
        # adding a response on the event.

        if hasattr(self, 'resourceName'):
            resource = self.resourceName
        else:
            resource = handler.__module__.rsplit('.', 1)[-1]

        routeStr = '/'.join((resource, '/'.join(route))).rstrip('/')
        eventPrefix = '.'.join(('rest', method, routeStr))

        event = events.trigger('.'.join((eventPrefix, 'before')),
                               kwargs, pre=self._defaultAccess)
        if event.defaultPrevented and len(event.responses) > 0:
            val = event.responses[0]
        else:
            self._defaultAccess(handler)
            val = handler(**kwargs)

        # Fire the after-call event that has a chance to augment the
        # return value of the API method that was called. You can
        # reassign the return value completely by adding a response to
        # the event and calling preventDefault() on it.
        kwargs['returnVal'] = val
        event = events.trigger('.'.join((eventPrefix, 'after')), kwargs)
        if event.defaultPrevented and len(event.responses) > 0:
            val = event.responses[0]

        return val