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

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

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

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

        if not doc['parentCollection'] in ('folder', 'user', 'collection'):
            # Internal error; this shouldn't happen
            raise GirderException(
                'Invalid folder parent type: %s.' % doc['parentCollection'],
                'girder.models.folder.invalid-parent-type')
        name = doc['name']
        # If the folder already exists with the current name, don't check.
        # Although we don't want duplicate names, they can occur when there are
        # simultaneous uploads, and also because Mongo has no guaranteed
        # multi-collection uniqueness constraints.  If this occurs, and we are
        # changing a non-name property, don't validate the name (since that may
        # fail).  If the name is being changed, validate that it is probably
        # unique.
        checkName = '_id' not in doc or not self.findOne({
            '_id': doc['_id'],
            'name': name
        })
        n = 0
        itemModel = Item()
        while checkName:
            q = {
                'parentId': doc['parentId'],
                'name': name,
                'parentCollection': doc['parentCollection']
            }
            if '_id' in doc:
                q['_id'] = {'$ne': doc['_id']}
            dupFolder = self.findOne(q, fields=['_id'])
            if doc['parentCollection'] == 'folder':
                q = {'folderId': doc['parentId'], 'name': name}
                dupItem = itemModel.findOne(q, fields=['_id'])
            else:
                dupItem = None
            if dupItem is None and dupFolder is None:
                doc['name'] = name
                break
            if not allowRename:
                if dupFolder:
                    raise ValidationException(
                        'A folder with that name '
                        'already exists here.', 'name')
                raise ValidationException(
                    'An item with that name already '
                    'exists here.', 'name')
            n += 1
            name = '%s (%d)' % (doc['name'], n)
        return doc
예제 #2
0
파일: setting.py 프로젝트: giesberge/girder
 def validateCoreEmailVerification(doc):
     doc['value'] = doc['value'].lower()
     if doc['value'] not in ('required', 'optional', 'disabled'):
         raise ValidationException(
             'Email verification must be "required", "optional", or "disabled".',
             'value')
예제 #3
0
파일: setting.py 프로젝트: giesberge/girder
 def validateEnablePasswordLogin(doc):
     if not isinstance(doc['value'], bool):
         raise ValidationException(
             'Enable password login setting must be boolean.', 'value')
예제 #4
0
파일: setting.py 프로젝트: giesberge/girder
 def validateCoreEmailFromAddress(doc):
     if not doc['value']:
         raise ValidationException('Email from address must not be blank.',
                                   'value')
예제 #5
0
파일: setting.py 프로젝트: giesberge/girder
 def validateCoreEmailHost(doc):
     if isinstance(doc['value'], six.string_types):
         doc['value'] = doc['value'].strip()
         return
     raise ValidationException('Email host must be a string.', 'value')
예제 #6
0
파일: setting.py 프로젝트: giesberge/girder
 def validateCoreBrandName(doc):
     if not doc['value']:
         raise ValidationException('The brand name may not be empty',
                                   'value')
예제 #7
0
파일: setting.py 프로젝트: giesberge/girder
 def validateSecureCookie(doc):
     if not isinstance(doc['value'], bool):
         raise ValidationException('Secure cookie option must be boolean.',
                                   'value')
예제 #8
0
    def authenticate(self, login, password, otpToken=None):
        """
        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
        :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
        """
        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.')

        # 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]

        # Verify password
        if not self._cryptContext.verify(password, user['salt']):
            raise AccessException('Login failed.')

        # 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, str):
            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':
            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
예제 #9
0
    def validate(self, doc):
        """
        Validate the user every time it is stored in the database.
        """
        doc['login'] = doc.get('login', '').lower().strip()
        doc['email'] = doc.get('email', '').lower().strip()
        doc['firstName'] = doc.get('firstName', '').strip()
        doc['lastName'] = doc.get('lastName', '').strip()
        doc['status'] = doc.get('status', 'enabled')

        if 'salt' not in doc:
            # Internal error, this should not happen
            raise Exception('Tried to save user document with no salt.')

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

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

        if doc['status'] not in ('pending', 'enabled', 'disabled'):
            raise ValidationException(
                'Status must be pending, enabled, or disabled.', 'status')

        if 'hashAlg' in doc:
            # This is a legacy field; hash algorithms are now inline with the password hash
            del doc['hashAlg']

        self._validateLogin(doc['login'])

        if not mail_utils.validateEmailAddress(doc['email']):
            raise ValidationException('Invalid email address.', 'email')

        # Ensure unique logins
        q = {'login': doc['login']}
        if '_id' in doc:
            q['_id'] = {'$ne': doc['_id']}
        existing = self.findOne(q)
        if existing is not None:
            raise ValidationException('That login is already registered.',
                                      'login')

        # Ensure unique emails
        q = {'email': doc['email']}
        if '_id' in doc:
            q['_id'] = {'$ne': doc['_id']}
        existing = self.findOne(q)
        if existing is not None:
            raise ValidationException('That email is already registered.',
                                      'email')

        # If this is the first user being created, make it an admin
        existing = self.findOne({})
        if existing is None:
            doc['admin'] = True
            # Ensure settings don't stop this user from logging in
            doc['emailVerified'] = True
            doc['status'] = 'enabled'

        return doc
예제 #10
0
def validateHistomicsUIWebrootPath(doc):
    if not doc['value']:
        raise ValidationException('The webroot path may not be empty', 'value')
    if re.match(r'^girder$', doc['value']):
        raise ValidationException('The webroot path may not be "girder"',
                                  'value')
예제 #11
0
def validateHistomicsUIAlternateWebrootPath(doc):
    if re.match(r'(^,|)girder(,|$)', doc['value']):
        raise ValidationException(
            'The alternate webroot path may not contain "girder"', 'value')
예제 #12
0
def validateHistomicsUIColor(doc):
    if not doc['value']:
        raise ValidationException('The banner color may not be empty', 'value')
    elif not re.match(r'^#[0-9A-Fa-f]{6}$', doc['value']):
        raise ValidationException(
            'The banner color must be a hex color triplet', 'value')
예제 #13
0
    def copyFolder(self,
                   srcFolder,
                   parent=None,
                   name=None,
                   description=None,
                   parentType=None,
                   public=None,
                   creator=None,
                   progress=None,
                   firstFolder=None):
        """
        Copy a folder, including all child items and child folders.

        :param srcFolder: the folder to copy.
        :type srcFolder: dict
        :param parent: The parent document.  Must be a folder, user, or
                       collection.
        :type parent: dict
        :param name: The name of the new folder.  None to copy the original
                     name.
        :type name: str
        :param description: Description for the new folder.  None to copy the
                            original description.
        :type description: str
        :param parentType: What type the parent is:
                           ('folder' | 'user' | 'collection')
        :type parentType: str
        :param public: Public read access flag.  None to inherit from parent,
                       'original' to inherit from original folder.
        :type public: bool, None, or 'original'.
        :param creator: user representing the creator of the new folder.
        :type creator: dict
        :param progress: a progress context to record process on.
        :type progress: girder.utility.progress.ProgressContext or None.
        :param firstFolder: if not None, the first folder copied in a tree of
                            folders.
        :returns: the new folder document.
        """
        setResponseTimeLimit()
        if parentType is None:
            parentType = srcFolder['parentCollection']
        parentType = parentType.lower()
        if parentType not in ('folder', 'user', 'collection'):
            raise ValidationException('The parentType must be folder, '
                                      'collection, or user.')
        if parent is None:
            parent = ModelImporter.model(parentType).load(
                srcFolder['parentId'], force=True)
        if name is None:
            name = srcFolder['name']
        if description is None:
            description = srcFolder['description']
        if public is not None and isinstance(public, str):
            if public == 'original':
                public = srcFolder.get('public', None)
            else:
                public = public == 'true'
        newFolder = self.createFolder(parentType=parentType,
                                      parent=parent,
                                      name=name,
                                      description=description,
                                      public=public,
                                      creator=creator,
                                      allowRename=True)
        if firstFolder is None:
            firstFolder = newFolder
        return self.copyFolderComponents(srcFolder, newFolder, creator,
                                         progress, firstFolder)
예제 #14
0
    def createFolder(self,
                     parent,
                     name,
                     description='',
                     parentType='folder',
                     public=None,
                     creator=None,
                     allowRename=False,
                     reuseExisting=False):
        """
        Create a new folder under the given parent.

        :param parent: The parent document. Should be a folder, user, or
                       collection.
        :type parent: dict
        :param name: The name of the folder.
        :type name: str
        :param description: Description for the folder.
        :type description: str
        :param parentType: What type the parent is:
                           ('folder' | 'user' | 'collection')
        :type parentType: str
        :param public: Public read access flag.
        :type public: bool or None to inherit from parent
        :param creator: User document representing the creator of this folder.
        :type creator: dict
        :param allowRename: if True and a folder or item of this name exists,
                            automatically rename the folder.
        :type allowRename: bool
        :param reuseExisting: If a folder with the given name already exists
            under the given parent, return that folder rather than creating a
            new one.
        :type reuseExisting: bool
        :returns: The folder document that was created.
        """
        if reuseExisting:
            existing = self.findOne({
                'parentId': parent['_id'],
                'name': name,
                'parentCollection': parentType
            })

            if existing:
                return existing

        parentType = parentType.lower()
        if parentType not in ('folder', 'user', 'collection'):
            raise ValidationException(
                'The parentType must be folder, collection, or user.')

        if parentType == 'folder':
            if 'baseParentId' not in parent:
                pathFromRoot = self.parentsToRoot(parent,
                                                  user=creator,
                                                  force=True)
                parent['baseParentId'] = pathFromRoot[0]['object']['_id']
                parent['baseParentType'] = pathFromRoot[0]['type']
        else:
            parent['baseParentId'] = parent['_id']
            parent['baseParentType'] = parentType

        now = datetime.datetime.utcnow()

        if creator is None:
            creatorId = None
        else:
            creatorId = creator.get('_id', None)

        folder = {
            'name': name,
            'description': description,
            'parentCollection': parentType,
            'baseParentId': parent['baseParentId'],
            'baseParentType': parent['baseParentType'],
            'parentId': ObjectId(parent['_id']),
            'creatorId': creatorId,
            'created': now,
            'updated': now,
            'size': 0,
            'meta': {}
        }

        if parentType in ('folder', 'collection'):
            self.copyAccessPolicies(src=parent, dest=folder, save=False)

        if creator is not None:
            self.setUserAccess(folder,
                               user=creator,
                               level=AccessType.ADMIN,
                               save=False)

        # Allow explicit public flag override if it's set.
        if public is not None and isinstance(public, bool):
            self.setPublic(folder, public, save=False)

        if allowRename:
            self.validate(folder, allowRename=True)

        # Now validate and save the folder.
        return self.save(folder)
예제 #15
0
파일: __init__.py 프로젝트: xinlaoda/girder
def validateIgnoreRegistrationPolicy(doc):
    if not isinstance(doc['value'], bool):
        raise ValidationException(
            'Ignore registration policy setting must be boolean.', 'value')
예제 #16
0
def _validateTrackingId(doc):
    if not doc['value']:
        raise ValidationException(
            'Google Analytics Tracking ID must not be empty.', 'value')
예제 #17
0
    def _testStream(self, user, token=None):
        # Should only work for users or token sessions
        resp = self.request(path='/notification/stream', method='GET')
        self.assertStatus(resp, 401)
        self.assertEqual(resp.json['message'],
                         'You must be logged in or have a valid auth token.')

        resp = self.request(path='/notification/stream',
                            method='GET',
                            user=user,
                            token=token,
                            isJson=False,
                            params={'timeout': 0})
        self.assertStatusOk(resp)
        self.assertEqual(self.getBody(resp), '')

        # Should not work when disabled
        Setting().set(SettingKey.ENABLE_NOTIFICATION_STREAM, False)
        resp = self.request(path='/notification/stream',
                            method='GET',
                            user=user,
                            token=token,
                            isJson=False,
                            params={'timeout': 0})
        self.assertStatus(resp, 503)
        Setting().set(SettingKey.ENABLE_NOTIFICATION_STREAM, True)

        # Use a very high rate-limit interval so that we don't fail on slow
        # build boxes
        with ProgressContext(True,
                             user=user,
                             token=token,
                             title='Test',
                             total=100,
                             interval=100) as progress:
            progress.update(current=1)

            # Rate limiting should make it so we didn't write the immediate
            # update within the time interval.
            resp = self.request(path='/notification/stream',
                                method='GET',
                                user=user,
                                token=token,
                                isJson=False,
                                params={'timeout': 0})
            messages = self.getSseMessages(resp)
            self.assertEqual(len(messages), 1)
            self.assertEqual(messages[0]['type'], 'progress')
            self.assertEqual(messages[0]['data']['total'], 100)
            self.assertEqual(messages[0]['data']['current'], 0)
            self.assertFalse(
                ProgressState.isComplete(messages[0]['data']['state']))

            # Now use a very short interval to test that we do save changes
            progress.interval = 0.01
            time.sleep(0.02)
            progress.update(current=2)
            resp = self.request(path='/notification/stream',
                                method='GET',
                                user=user,
                                token=token,
                                isJson=False,
                                params={'timeout': 0})
            messages = self.getSseMessages(resp)
            self.assertEqual(len(messages), 1)
            self.assertEqual(messages[0]['data']['current'], 2)
            # If we use a non-numeric value, nothing bad should happen
            time.sleep(0.02)
            progress.update(current='not_a_number')
            resp = self.request(path='/notification/stream',
                                method='GET',
                                user=user,
                                token=token,
                                isJson=False,
                                params={'timeout': 0})
            messages = self.getSseMessages(resp)
            self.assertEqual(len(messages), 1)
            self.assertEqual(messages[0]['data']['current'], 'not_a_number')
            # Updating the progress without saving and then exiting should
            # send the update.
            progress.interval = 1000
            progress.update(current=3)

            # The message should contain a timestamp
            self.assertIn('_girderTime', messages[0])
            self.assertIsInstance(messages[0]['_girderTime'], int)

            # Test that the "since" parameter correctly filters out messages
            since = messages[0]['_girderTime'] + 1
            resp = self.request(path='/notification/stream',
                                method='GET',
                                user=user,
                                token=token,
                                isJson=False,
                                params={
                                    'timeout': 0,
                                    'since': since
                                })
            messages = self.getSseMessages(resp)
            self.assertEqual(len(messages), 0)

        # Exiting the context manager should flush the most recent update.
        resp = self.request(path='/notification/stream',
                            method='GET',
                            user=user,
                            token=token,
                            isJson=False,
                            params={'timeout': 0})
        messages = self.getSseMessages(resp)
        self.assertEqual(len(messages), 1)
        self.assertEqual(messages[0]['data']['current'], 3)

        # Test a ValidationException within the progress context
        try:
            with ProgressContext(True,
                                 user=user,
                                 token=token,
                                 title='Test',
                                 total=100):
                raise ValidationException('Test Message')
        except ValidationException:
            pass

        # Exiting the context manager should flush the most recent update.
        resp = self.request(path='/notification/stream',
                            method='GET',
                            user=user,
                            token=token,
                            isJson=False,
                            params={'timeout': 0})
        messages = self.getSseMessages(resp)
        self.assertEqual(messages[-1]['data']['message'],
                         'Error: Test Message')
예제 #18
0
 def key1v2(doc):
     raise ValidationException('key1v2')
예제 #19
0
파일: setting.py 프로젝트: giesberge/girder
 def validateCorePrivacyNotice(doc):
     if not doc['value']:
         raise ValidationException(
             'The privacy notice link may not be empty', 'value')
예제 #20
0
 def key2v1(doc):
     raise ValidationException('key2v1')
예제 #21
0
파일: setting.py 프로젝트: giesberge/girder
 def validateCoreServerRoot(doc):
     val = doc['value']
     if val and not val.startswith('http://') and not val.startswith(
             'https://'):
         raise ValidationException(
             'Server root must start with http:// or https://.', 'value')
예제 #22
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
예제 #23
0
파일: setting.py 프로젝트: giesberge/girder
 def validateCoreContactEmailAddress(doc):
     if not doc['value']:
         raise ValidationException(
             'Contact email address must not be blank.', 'value')
예제 #24
0
 def _validateStatus(self, status):
     if not JobStatus.isValid(status):
         raise ValidationException('Invalid job status %s.' % status,
                                   field='status')
예제 #25
0
파일: setting.py 프로젝트: giesberge/girder
 def validateCoreRegistrationPolicy(doc):
     doc['value'] = doc['value'].lower()
     if doc['value'] not in ('open', 'closed', 'approve'):
         raise ValidationException(
             'Registration policy must be "open", "closed", or "approve".',
             'value')
예제 #26
0
 def _validateChild(self, parentJob, childJob):
     if str(parentJob['_id']) == str(childJob['_id']):
         raise ValidationException('Child Id cannot be equal to Parent Id')
     if childJob['parentId']:
         raise ValidationException('Cannot overwrite the Parent Id')
예제 #27
0
파일: setting.py 프로젝트: giesberge/girder
 def validateApiKeys(doc):
     if not isinstance(doc['value'], bool):
         raise ValidationException('API key setting must be boolean.',
                                   'value')
예제 #28
0
파일: __init__.py 프로젝트: xinlaoda/girder
def validateProvidersEnabled(doc):
    if not isinstance(doc['value'], (list, tuple)):
        raise ValidationException('The enabled providers must be a list.',
                                  'value')
예제 #29
0
파일: setting.py 프로젝트: giesberge/girder
 def validateCoreSmtpHost(doc):
     if not doc['value']:
         raise ValidationException('SMTP host must not be blank.', 'value')
예제 #30
0
def validateBoolean(doc):
    val = doc['value']
    if str(val).lower() not in ('false', 'true', ''):
        raise ValidationException('%s must be a boolean.' % doc['key'],
                                  'value')
    doc['value'] = (str(val).lower() != 'false')