Exemplo n.º 1
0
    def updateUser(self, user, firstName, lastName, email, mobileNumber, role, admin, status):
        user['firstName'] = firstName
        user['lastName'] = lastName
        user['email'] = email
        user['mobileNumber'] = mobileNumber
        roles_dict = json.loads(role)

        # rolesObjId={}
        # i=-1
        # for key in roles_dict:
        #     z = ObjectId(key)
        #     a = i+1
        #     a=str(a)
        #     rolesObjId[a] = z
        #     i=i+1

        user['role'] = roles_dict

        # Only admins can change admin state
        if admin is not None:
            if self.getCurrentUser()['admin']:
                user['admin'] = admin
            elif user['admin'] is not admin:
                raise AccessException('Only admins may change admin status.')

        # Only admins can change status
        if status is not None and status != user.get('status', 'enabled'):
            if not self.getCurrentUser()['admin']:
                raise AccessException('Only admins may change status.')
            if user['status'] == 'pending' and status == 'enabled':
                # Send email on the 'pending' -> 'enabled' transition
                self._model._sendApprovedEmail(user)
            user['status'] = status

        return self._model.save(user)
Exemplo n.º 2
0
 def verifyLogin(self, user):
     """
     Raises an exception if user's account is disabled or one of the auth policies
     is not fulfilled.
     """
     if user.get('status', 'enabled') == 'disabled':
         raise AccessException('Account is disabled.', extra='disabled')
     if self.emailVerificationRequired(user):
         raise AccessException('Email verification required.',
                               extra='emailVerification')
     if self.adminApprovalRequired(user):
         raise AccessException('Account approval required.',
                               extra='accountApproval')
Exemplo n.º 3
0
 def wrapped(*args, **kwargs):
     if not rest.getCurrentToken():
         raise AccessException(
             'You must be logged in or have a valid auth token.')
     if required:
         Token().requireScope(rest.getCurrentToken(), scope)
     return fun(*args, **kwargs)
Exemplo n.º 4
0
    def verifyEmail(self, user, token):
        token = Token().load(token,
                             user=user,
                             level=AccessType.ADMIN,
                             objectId=False,
                             exc=True)
        delta = (token['expires'] - datetime.datetime.utcnow()).total_seconds()
        hasScope = Token().hasScope(token, TokenScope.EMAIL_VERIFICATION)

        if token.get('userId') != user['_id'] or delta <= 0 or not hasScope:
            raise AccessException('The token is invalid or expired.')

        user['emailVerified'] = True
        Token().remove(token)
        user = self._model.save(user)

        if self._model.canLogin(user):
            setCurrentUser(user)
            authToken = self.sendAuthTokenCookie(user)
            return {
                'user': self._model.filter(user, user),
                'authToken': {
                    'token': authToken['_id'],
                    'expires': authToken['expires'],
                    'scope': authToken['scope']
                },
                'message': 'Email verification succeeded.'
            }
        else:
            return {
                'user': self._model.filter(user, user),
                'message': 'Email verification succeeded.'
            }
Exemplo n.º 5
0
    def read_chunk(self, event, path, root, user=None):
        params = event.info["params"]
        if "chunk" in params:
            chunk = params["chunk"]
            if isinstance(chunk, cherrypy._cpreqbody.Part):
                # Seek is the only obvious way to get the length of the part
                chunk.file.seek(0, os.SEEK_END)
                size = chunk.file.tell()
                chunk.file.seek(0, os.SEEK_SET)
                chunk = RequestBodyStream(chunk.file, size=size)
        else:
            chunk = RequestBodyStream(cherrypy.request.body)

        if not user:
            user = self.getCurrentUser()
        offset = int(params.get("offset", 0))
        upload = Upload().load(params["uploadId"])

        if upload["userId"] != user["_id"]:
            raise AccessException("You did not initiate this upload.")

        if upload["received"] != offset:
            raise RestException(
                "Server has received %s bytes, but client sent offset %s."
                % (upload["received"], offset)
            )

        try:
            fobj = self._handle_chunk(upload, chunk, filter=True, user=user)
            event.preventDefault().addResponse(fobj)
        except IOError as exc:
            if exc.errno == errno.EACCES:
                raise Exception("Failed to store upload.")
            raise
Exemplo n.º 6
0
    def getSegmentationTasks(self, params):
        details = self.boolParam('details', params, False)

        user = self.getCurrentUser()
        userSkill = User().getSegmentationSkill(user)
        if userSkill is None:
            raise AccessException('You are not authorized to perform segmentations.')

        pipeline = list(itertools.chain(
            self._pipeline1AllImages(user),
            self._pipeline2ImagesWithSegmentations(),
            (
                self._pipeline3NoExpertSegmentations()
                if userSkill == Segmentation().Skill.EXPERT else
                self._pipeline3MissingSegmentations()
            ),
            (
                self._pipeline4ListImages()
                if details else
                self._pipeline4CountImages()
            ),
            self._pipeline5JoinDataset()
        ))

        results = list(Image().collection.aggregate(pipeline))

        return results
Exemplo n.º 7
0
    def nextSegmentationTask(self, params):
        self.requireParams(['datasetId'], params)
        user = self.getCurrentUser()

        dataset = Dataset().load(params['datasetId'],
                                 user=user,
                                 level=AccessType.READ,
                                 exc=True)

        userSkill = User().getSegmentationSkill(user)
        if userSkill is None:
            raise AccessException(
                'You are not authorized to perform segmentations.')

        pipeline = list(
            itertools.chain(
                self._pipeline1ImagesFromDataset(dataset),
                self._pipeline2ImagesWithSegmentations(),
                (
                    # TODO: prefer an image with a novice segmentation to one with
                    # no segmentations
                    self._pipeline3NoExpertSegmentations()
                    if userSkill == Segmentation().Skill.EXPERT else
                    self._pipeline3MissingSegmentations()),
                self._pipeline4RandomImage()))

        results = list(Image().collection.aggregate(pipeline))
        if not results:
            raise RestException(
                'No segmentations are needed for this dataset.', code=404)
        nextImage = results[0]

        return Image().filterSummary(nextImage, user)
Exemplo n.º 8
0
def ensureTokenScopes(token, scope):
    """
    Call this to validate a token scope for endpoints that require tokens
    other than a user authentication token. Raises an AccessException if the
    required scopes are not allowed by the given token.

    :param token: The token object used in the request.
    :type token: dict
    :param scope: The required scope or set of scopes.
    :type scope: `str or list of str`
    """
    tokenModel = Token()
    if tokenModel.hasScope(token, TokenScope.USER_AUTH):
        return

    if not tokenModel.hasScope(token, scope):
        setCurrentUser(None)
        if isinstance(scope, six.string_types):
            scope = (scope,)
        raise AccessException(
            'Invalid token scope.\n'
            'Required: %s.\n'
            'Allowed: %s' % (
                ' '.join(scope),
                '' if token is None else ' '.join(tokenModel.getAllowedScopes(token))))
Exemplo n.º 9
0
    def checkTemporaryPassword(self, user, token):
        token = Token().load(token,
                             user=user,
                             level=AccessType.ADMIN,
                             objectId=False,
                             exc=True)
        delta = (token['expires'] - datetime.datetime.utcnow()).total_seconds()
        hasScope = Token().hasScope(token, TokenScope.TEMPORARY_USER_AUTH)

        if token.get('userId') != user['_id'] or delta <= 0 or not hasScope:
            raise AccessException(
                'The token does not grant temporary access to this user.')

        # Temp auth is verified, send an actual auth token now. We keep the
        # temp token around since it can still be used on a subsequent request
        # to change the password
        authToken = self.sendAuthTokenCookie(user)

        return {
            'user': self._model.filter(user, user),
            'authToken': {
                'token': authToken['_id'],
                'expires': authToken['expires'],
                'temporary': True
            },
            'message': 'Temporary access token is valid.'
        }
Exemplo n.º 10
0
    def readChunk(self, upload, offset, params):
        """
        After the temporary upload record has been created (see initUpload),
        the bytes themselves should be passed up in ordered chunks. The user
        must remain logged in when passing each chunk, to authenticate that
        the writer of the chunk is the same as the person who initiated the
        upload. The passed offset is a verification mechanism for ensuring the
        server and client agree on the number of bytes sent/received.
        """
        if 'chunk' in params:
            # If we see the undocumented "chunk" query string parameter, then we abort trying to
            # read the body, use the query string value as chunk, and pass it through to
            # Upload().handleChunk. This case is used by the direct S3 upload process.
            chunk = params['chunk']
        else:
            chunk = RequestBodyStream(cherrypy.request.body)
        user = self.getCurrentUser()

        if upload['userId'] != user['_id']:
            raise AccessException('You did not initiate this upload.')

        if upload['received'] != offset:
            raise RestException(
                'Server has received %s bytes, but client sent offset %s.' % (
                    upload['received'], offset))
        try:
            return Upload().handleChunk(upload, chunk, filter=True, user=user)
        except IOError as exc:
            if exc.errno == errno.EACCES:
                raise Exception('Failed to store upload.')
            raise
Exemplo n.º 11
0
    def cancelUpload(self, upload):
        user = self.getCurrentUser()

        if upload['userId'] != user['_id'] and not user['admin']:
            raise AccessException('You did not initiate this upload.')

        Upload().cancelUpload(upload)
        return {'message': 'Upload canceled.'}
Exemplo n.º 12
0
    def createCollection(self, name, description, public):
        user = self.getCurrentUser()

        if not self._model.hasCreatePrivilege(user):
            raise AccessException('You are not authorized to create collections.')

        return self._model.createCollection(
            name=name, description=description, public=public, creator=user)
Exemplo n.º 13
0
    def authenticate(self, login, password):
        """
        Validate a user login via username and password. If authentication fails,
        a ``AccessException`` is raised.

        :param login: The user's login or email.
        :type login: str
        :param password: The user's password.
        :type password: str
        :returns: The corresponding user if the login was successful.
        :rtype: dict
        """
        from .password import Password

        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 '@' in login else 'login'

        user = self.findOne({loginField: login})
        if user is None:
            raise AccessException('Login failed.')

        if not Password().authenticate(user, password):
            raise AccessException('Login failed.')

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

        if self.emailVerificationRequired(user):
            raise AccessException('Email verification required.',
                                  extra='emailVerification')

        if self.adminApprovalRequired(user):
            raise AccessException('Account approval required.',
                                  extra='accountApproval')

        return user
Exemplo n.º 14
0
def require_lock(identifier, user):
    """
    Require that the user has the lock on the given dandiset.

    If user is None, then it will verify that there is no lock.
    This method should be used to ensure that a lock is present.
    Note the different between this method and require_access.
    """
    if not has_lock(identifier, user):
        locked_by = get_lock_owner(identifier)
        if locked_by is not None:
            owner_str = f"{locked_by['firstName']} {locked_by['lastName']}"
            raise AccessException(
                f"Dandiset {identifier} is currently locked by {owner_str}")
        else:
            raise AccessException(
                f"Dandiset {identifier} is currently unlocked")
Exemplo n.º 15
0
def require_access(identifier, user):
    """
    Require that the user either have the lock, or no lock is present.

    This method should be used to determine if a user can modify a dandiset.
    """
    if (not has_lock(identifier, None)) and (not has_lock(identifier, user)):
        locked_by = get_lock_owner(identifier)
        owner_str = f"{locked_by['firstName']} {locked_by['lastName']}"
        raise AccessException(
            f"Dandiset {identifier} is currently locked by {owner_str}")
Exemplo n.º 16
0
def findSharedDatasetFolders(currentUser):
    folderModel = ModelImporter.model('folder')
    groupModel = ModelImporter.model('group')
    datasetSharingGroup = groupModel.findOne(
        query={'name': PluginSettings.DATASET_SHARING_GROUP_NAME})
    if not datasetSharingGroup:
        raise AccessException('user group "{0}" doesn\'t exist'.format(
            PluginSettings.DATASET_SHARING_GROUP_NAME))
    if datasetSharingGroup['_id'] not in currentUser['groups']:
        raise AccessException(
            'user doesn\'t belong to user group "{0}"'.format(
                PluginSettings.DATASET_SHARING_GROUP_NAME))

    folders = folderModel.find({
        'baseParentType': 'user',
        'parentCollection': 'user',
        'access.groups.id': datasetSharingGroup['_id'],
        'name': PluginSettings.MINERVA_SHARED_DATASET
    })
    return folders
Exemplo n.º 17
0
 def deleteSubmission(self, submission, params):
     user = self.getCurrentUser()
     phase = self.model('phase', 'covalic').load(submission['phaseId'],
                                                 force=True)
     if (user['_id'] == submission['creatorId'] or
             self.model('phase', 'covalic').hasAccess(
                 phase, user, AccessType.WRITE)):
         self.model('submission', 'covalic').remove(submission)
     else:
         raise AccessException(
             'You may only remove submissions that you made, or those under '
             'phases that you have permission to edit.')
Exemplo n.º 18
0
    def requireScope(self, token, scope):
        """
        Raise an error if given set of scopes are not included.

        :param token: The token object.
        :type token: dict
        :param scope: A scope or set of scopes that will be tested as a subset
            of the given token's allowed scopes.
        :type scope: str or list of str
        """
        if not self.hasScope(token, scope):
            raise AccessException('Invalid token scope, required: %s.' %
                                  (scope))
Exemplo n.º 19
0
def requireAdmin(user, message=None):
    """
    Calling this on a user will ensure that they have admin rights.  If not,
    raises an AccessException.

    :param user: The user to check admin flag on.
    :type user: dict.
    :param message: The exception message.
    :type message: str or None
    :raises AccessException: If the user is not an administrator.
    """
    if user is None or not user['admin']:
        raise AccessException(message or 'Administrator access required.')
Exemplo n.º 20
0
    def updateUser(self, user, firstName, lastName, email, admin, status):
        user['firstName'] = firstName
        user['lastName'] = lastName
        user['email'] = email

        # Only admins can change admin state
        if admin is not None:
            if self.getCurrentUser()['admin']:
                user['admin'] = admin
            elif user['admin'] is not admin:
                raise AccessException('Only admins may change admin status.')

        # Only admins can change status
        if status is not None and status != user.get('status', 'enabled'):
            if not self.getCurrentUser()['admin']:
                raise AccessException('Only admins may change status.')
            if user['status'] == 'pending' and status == 'enabled':
                # Send email on the 'pending' -> 'enabled' transition
                self._model._sendApprovedEmail(user)
            user['status'] = status

        return self._model.save(user)
Exemplo n.º 21
0
    def _promote(self, group, user, level):
        """
        Promote a user to moderator or administrator.

        :param group: The group to promote within.
        :param user: The user to promote.
        :param level: Either WRITE or ADMIN, for moderator or administrator.
        :type level: AccessType
        :returns: The updated group document.
        """
        if not group['_id'] in user.get('groups', []):
            raise AccessException('That user is not a group member.')

        group = self._model.setUserAccess(group, user, level=level, save=True)
        group['access'] = self._model.getFullAccessList(group)
        return group
Exemplo n.º 22
0
    def verifyOtp(self, user, otpToken):
        lastCounterKey = 'girder.models.user.%s.otp.totp.counter' % user['_id']

        # The last successfully-authenticated key (which is blacklisted from reuse)
        lastCounter = rateLimitBuffer.get(lastCounterKey) or None

        try:
            totpMatch = self._TotpFactory.verify(
                otpToken, user['otp']['totp'], last_counter=lastCounter)
        except TokenError as e:
            raise AccessException('One-time password validation failed: %s' % e)

        # The totpMatch.cache_seconds tells us prospectively how long the counter needs to be cached
        # for, but dogpile.cache expiration times work retrospectively (on "get"), so there's no
        # point to using it (over-caching just wastes cache resources, but does not impact
        # "totp.verify" security)
        rateLimitBuffer.set(lastCounterKey, totpMatch.counter)
Exemplo n.º 23
0
    def finalizeUpload(self, upload):
        user = self.getCurrentUser()

        if upload['userId'] != user['_id']:
            raise AccessException('You did not initiate this upload.')

        # If we don't have as much data as we were told would be uploaded and
        # the upload hasn't specified it has an alternate behavior, refuse to
        # complete the upload.
        if upload['received'] != upload['size'] and 'behavior' not in upload:
            raise RestException(
                'Server has only received %s bytes, but the file should be %s bytes.' %
                (upload['received'], upload['size']))

        file = Upload().finalizeUpload(upload)
        extraKeys = file.get('additionalFinalizeKeys', ())
        return self._model.filter(file, user, additionalKeys=extraKeys)
Exemplo n.º 24
0
def _importTar(self, assetstore, folder, path, progress):
    importGroupId = Setting().get(WHITELIST_GROUP_SETTING)
    if not importGroupId:
        raise Exception('Import whitelist group ID is not set')

    user = self.getCurrentUser()
    if importGroupId not in user['groups']:
        raise AccessException('You are not authorized to import tape archive files.')

    if assetstore is None:
        # This is a reasonable fallback behavior, but we may want something more robust.
        # Imported files are weird anyway
        assetstore = Assetstore().getCurrent()

    if assetstore['type'] != AssetstoreType.FILESYSTEM:
        raise Exception('Not a filesystem assetstore: %s' % assetstore['_id'])

    with ProgressContext(progress, user=user, title='Importing data') as ctx:
        getAssetstoreAdapter(assetstore)._importTar(path, folder, ctx, user)
Exemplo n.º 25
0
    def redirectReviewTask(self, params):
        self.requireParams(['datasetId'], params)

        user = self.getCurrentUser()
        User().requireReviewDataset(user)

        dataset = Dataset().load(params['datasetId'], user=user, level=AccessType.WRITE, exc=True)

        prereviewFolder = Dataset().prereviewFolder(dataset)
        if not (prereviewFolder
                and Folder().hasAccess(prereviewFolder, user=user, level=AccessType.READ)):
            raise AccessException(
                'User does not have access to any Pre-review images for this dataset.')

        if not Image().find({'folderId': prereviewFolder['_id']}).count():
            raise RestException('No Pre-review images are available for this dataset.')

        reviewUrl = f'/#tasks/review/{dataset["_id"]}'
        self._doRedirect(reviewUrl)
Exemplo n.º 26
0
    def listSubmissions(self, phase, userFilter, latest, limit, offset, sort, approach):
        user = self.getCurrentUser()

        # If scores are hidden, do not allow sorting by score fields
        if (phase.get('hideScores') and
                not self.model('phase', 'covalic').hasAccess(
                    phase, user, AccessType.WRITE)):
            for field, _ in sort:
                if field == 'overallScore' or field.startswith('score.'):
                    raise AccessException(
                        'Scores are hidden from participants in this phase, '
                        'you may not sort by score fields.')

        # Exclude score field
        fields = {'score': False}

        submissions = self.model('submission', 'covalic').list(
            phase, limit=limit, offset=offset, sort=sort, userFilter=userFilter,
            fields=fields, latest=latest, approach=approach)
        return [self._filterScore(phase, s, user) for s in submissions]
Exemplo n.º 27
0
    def readChunk(self, upload, offset, params):
        """
        After the temporary upload record has been created (see initUpload),
        the bytes themselves should be passed up in ordered chunks. The user
        must remain logged in when passing each chunk, to authenticate that
        the writer of the chunk is the same as the person who initiated the
        upload. The passed offset is a verification mechanism for ensuring the
        server and client agree on the number of bytes sent/received.

        This method accepts both the legacy multipart content encoding, as
        well as passing offset and uploadId as query parameters and passing
        the chunk as the body, which is the recommended method.

        .. deprecated :: 2.2.0
        """
        if 'chunk' in params:
            chunk = params['chunk']
            if isinstance(chunk, cherrypy._cpreqbody.Part):
                # Seek is the only obvious way to get the length of the part
                chunk.file.seek(0, os.SEEK_END)
                size = chunk.file.tell()
                chunk.file.seek(0, os.SEEK_SET)
                chunk = RequestBodyStream(chunk.file, size=size)
        else:
            chunk = RequestBodyStream(cherrypy.request.body)

        user = self.getCurrentUser()

        if upload['userId'] != user['_id']:
            raise AccessException('You did not initiate this upload.')

        if upload['received'] != offset:
            raise RestException(
                'Server has received %s bytes, but client sent offset %s.' %
                (upload['received'], offset))
        try:
            return Upload().handleChunk(upload, chunk, filter=True, user=user)
        except IOError as exc:
            if exc.errno == errno.EACCES:
                raise Exception('Failed to store upload.')
            raise
Exemplo n.º 28
0
    def _importTar(self, path, folder, progress, user):
        if not os.path.isabs(path):
            path = os.path.join(self.assetstore['root'], path)

        if not os.path.isfile(path):
            raise ValidationException('Error: %s is not a file.' % path)

        folderCache = {}

        def _resolveFolder(name):
            if name in {'.', ''}:  # This file is at the top level
                return folder
            if name not in folderCache:
                tokens = name.split('/')
                sub = folder
                for token in tokens:
                    if token.strip() in {'.', ''}:
                        continue
                    sub = Folder().createFolder(sub, token, creator=user, reuseExisting=True)
                folderCache[name] = sub

            return folderCache[name]

        with tarfile.open(path, 'r') as tar:
            for entry in tar:
                if entry.isreg():
                    dir, name = os.path.split(entry.name)
                    progress.update(message=entry.name)
                    parent = _resolveFolder(dir)
                    if not Folder().hasAccess(parent, user, AccessType.WRITE):
                        raise AccessException('Write access denied for folder: %s' % folder['_id'])
                    item = Item().createItem(
                        name=name, creator=user, folder=parent, reuseExisting=True)
                    file = File().createFile(
                        name=name, creator=user, item=item, reuseExisting=True,
                        assetstore=self.assetstore, size=entry.size, saveFile=False)
                    file['path'] = ''
                    file['tarPath'] = path
                    file['imported'] = True
                    file['pathInTarfile'] = entry.name
                    File().save(file)
Exemplo n.º 29
0
    def getReviewImages(self, dataset, params):
        user = self.getCurrentUser()
        User().requireReviewDataset(user)

        prereviewFolder = Dataset().prereviewFolder(dataset)
        if not prereviewFolder:
            raise AccessException(
                'There are no pending Pre-review images for this dataset.')

        limit = int(params.get('limit', 50))

        output = [{
            field: image[field]
            for field in ['_id', 'name', 'updated', 'description', 'meta']
        } for image in Image().find({'folderId': prereviewFolder['_id']},
                                    limit=limit,
                                    sort=[('meta.clinical.diagnosis',
                                           SortDir.ASCENDING
                                           ), ('name', SortDir.ASCENDING)])]

        return output
Exemplo n.º 30
0
    def changePassword(self, old, new):
        user = self.getCurrentUser()
        token = None

        if not old:
            raise RestException('Old password must not be empty.')

        if not Password().hasPassword(user) or not Password().authenticate(user, old):
            # If not the user's actual password, check for temp access token
            token = Token().load(old, force=True, objectId=False, exc=False)
            if (not token or not token.get('userId') or
                    token['userId'] != user['_id'] or
                    not Token().hasScope(token, TokenScope.TEMPORARY_USER_AUTH)):
                raise AccessException('Old password is incorrect.')

        self._model.setPassword(user, new)

        if token:
            # Remove the temporary access token if one was used
            Token().remove(token)

        return {'message': 'Password changed.'}