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 })
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)
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 })
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)
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)
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)
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)
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)
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
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)
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)
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')
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)
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
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)
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)
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
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
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
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.')
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
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