def validate(self, doc): """ Validate the user every time it is stored in the database. """ doc['email'] = doc.get('email', '').lower().strip() # if 'salt' not in doc: # # Internal error, this should not happen # raise Exception('Tried to save user document with no salt.') # # if 'hashAlg' in doc: # # This is a legacy field; hash algorithms are now inline with the password hash # del doc['hashAlg'] if not doc.get('email_encrypted', None) and not mail_utils.validateEmailAddress( doc['email']): raise ValidationException('Invalid email address.', 'email') # Ensure unique emails # TO DO: use existing user if email exists q = {'email': doc['email']} if '_id' in doc: q['_id'] = {'$ne': doc['_id']} existing = self.findOne(q) if existing is not None: raise ValidationException( ''.join([ 'That email is already registered:', str(existing["_id"]) ]), 'email') return doc
def _validateBannerColor(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')
def validate(self, doc): # Ensure no duplicate names q = {'name': doc['name']} if '_id' in doc: q['_id'] = {'$ne': doc['_id']} duplicate = self.findOne(q, fields=['_id']) if duplicate is not None: raise ValidationException( 'An assetstore with that name already ' 'exists.', 'name') # Name must not be empty if not doc['name']: raise ValidationException('Name must not be empty.', 'name') # Adapter classes validate each type internally adapter = assetstore_utilities.getAssetstoreAdapter(doc, instance=False) adapter.validateInfo(doc) # If no current assetstore exists yet, set this one as the current. current = self.findOne({'current': True}, fields=['_id']) if current is None: doc['current'] = True if 'current' not in doc: doc['current'] = False # If we are setting this as current, we should unmark all other # assetstores that have the current flag. if doc['current'] is True: self.update({'current': True}, {'$set': {'current': False}}) return doc
def validate(self, doc): from .token import Token from .user import User if doc['tokenDuration']: doc['tokenDuration'] = float(doc['tokenDuration']) else: doc['tokenDuration'] = None doc['name'] = doc['name'].strip() doc['active'] = bool(doc.get('active', True)) if doc['scope'] is not None: if not isinstance(doc['scope'], (list, tuple)): raise ValidationException('Scope must be a list, or None.') if not doc['scope']: raise ValidationException( 'Custom scope list must not be empty.') # Ensure only registered scopes are being set admin = User().load(doc['userId'], force=True)['admin'] scopes = TokenScope.scopeIds(admin) unknownScopes = set(doc['scope']) - scopes if unknownScopes: raise ValidationException('Invalid scopes: %s.' % ','.join(unknownScopes)) # Deactivating an already existing token if '_id' in doc and not doc['active']: Token().clearForApiKey(doc) return doc
def validate(self, doc): """ Validate the user every time it is stored in the database. """ for s in ['email', 'displayName', 'firstName']: if s in doc and doc[s] is None: doc[s] = '' doc['login'] = doc.get('login', '').lower().strip() doc['email'] = doc.get('email', '').lower().strip() doc['displayName'] = doc.get( 'displayName', doc.get('firstName', '') ).strip() doc['firstName'] = doc.get('firstName', '').strip() doc['status'] = doc.get('status', 'enabled') doc['deviceId'] = doc.get('deviceId', '') if 'salt' not in doc: # Internal error, this should not happen raise Exception('Tried to save user document with no salt.') if not doc['displayName']: raise ValidationException('Display name must not be empty.', 'displayName') 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 len(doc['email']) and 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') # 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
def validate(self, doc): if doc['size'] < 0: raise ValidationException('File size must not be negative.') if doc['received'] > doc['size']: raise ValidationException('Received too many bytes.') doc['updated'] = datetime.datetime.utcnow() return doc
def _validateLogin(self, login): if '@' in login: # Hard-code this constraint so we can always easily distinguish # an email address from a login raise ValidationException('Login may not contain "@".', 'login') if not re.match(r'^[a-z][\da-z\-\.]{3,}$', login): raise ValidationException( 'Login must be at least 4 characters, start with a letter, and may only contain ' 'letters, numbers, dashes, and dots.', 'login')
def _validateRouteTable(doc): nonEmptyRoutes = [route for route in doc['value'].values() if route] if GIRDER_ROUTE_ID not in doc[ 'value'] or not doc['value'][GIRDER_ROUTE_ID]: raise ValidationException('Girder root must be routable.', 'value') for key in doc['value']: if (doc['value'][key] and not doc['value'][key].startswith('/')): raise ValidationException( 'Routes must begin with a forward slash.', 'value') if len(nonEmptyRoutes) > len(set(nonEmptyRoutes)): raise ValidationException('Routes must be unique.', 'value')
def _validateLogo(doc): try: logoFile = File().load(doc['value'], level=AccessType.READ, user=None, exc=True) except ValidationException as e: # Invalid ObjectId, or non-existent document raise ValidationException(str(e), 'value') except AccessException: raise ValidationException('Logo must be publicly readable', 'value') # Store this field natively as an ObjectId doc['value'] = logoFile['_id']
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 _verify_parentType(self, parentType): parentType = parentType.lower() if parentType not in ('folder', 'user', 'collection', 'profile'): raise ValidationException( 'The parentType must be folder, collection, user, or profile.' ) return(parentType)
def inviteUser(self, group, user, level=AccessType.READ): """ Invite a user to join the group. Inviting them automatically grants the user read access to the group so that they can see it. Once they accept the invitation, they will be given the specified level of access. If the user has requested an invitation to this group, calling this will accept their request and add them to the group at the access level specified. """ from girderformindlogger.models.user import User if group['_id'] in user.get('groups', []): raise ValidationException('User is already in this group.') # If there is an outstanding request to join from this user, we # just add them to the group instead of invite them. if user['_id'] in group.get('requests', []): return self.addUser(group, user, level) if 'groupInvites' not in user: user['groupInvites'] = [] for invite in user['groupInvites']: if invite['groupId'] == group['_id']: invite['level'] = level break else: user['groupInvites'].append({ 'groupId': group['_id'], 'level': level }) return User().save(user, validate=False)
def _validateContactEmailAddress(doc): # This is typically used within an RFC 6068 "mailto:" scheme, so no display name is allowed from girderformindlogger.utility.mail_utils import validateEmailAddress if not validateEmailAddress(doc['value']): raise ValidationException( 'Contact email address must be a valid email address.', 'value')
def _validateServers(doc): serversSchema = { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'uri': { 'type': 'string', 'minLength': 1 }, 'bindName': { 'type': 'string', 'minLength': 1 }, 'baseDn': { 'type': 'string' } }, 'required': ['uri', 'bindName', 'baseDn'] } } try: jsonschema.validate(doc['value'], serversSchema) except jsonschema.ValidationError as e: raise ValidationException('Invalid LDAP servers list: ' + str(e)) for server in doc['value']: if '://' not in server['uri']: server['uri'] = 'ldap://' + server['uri'] server['password'] = server.get('password', '') server['searchField'] = server.get('searchField', 'uid')
def _validateAddToGroupPolicy(doc): doc['value'] = doc['value'].lower() if doc['value'] not in ('never', 'noadmin', 'nomod', 'yesadmin', 'yesmod', ''): raise ValidationException( 'Add to group policy must be one of "never", "noadmin", ' '"nomod", "yesadmin", or "yesmod".', 'value')
def checkOauthUser(event): """ If an OAuth2 user without a password tries to log in with a password, we want to give them a useful error message. """ user = event.info['user'] if user.get('oauth'): if isinstance(user['oauth'], dict): # Handle a legacy format where only 1 provider (Google) was stored userProviders = {'google'} else: userProviders = {val['provider'] for val in user['oauth']} # Silently skip non-installed providers availableUserProviders = userProviders & set(providers.idMap.keys()) if availableUserProviders: prettyProviderNames = ', '.join(sorted( providers.idMap[userProvider].getProviderName(external=True) for userProvider in availableUserProviders )) helpMessage = 'Please log in with %s, or use the password reset link.' % \ prettyProviderNames else: helpMessage = 'Please use the password reset link.' raise ValidationException( "You don't have a password. %s" % helpMessage)
def checkObjectForURLs(obj): """ Recursively checks if a dict is keyed with 'url'. If so, it checks for language encoding. inputs ------ obj: dict, list, string, or None returns ------- None """ if isinstance(obj, list): [checkObjectForURLs(o) for o in obj] elif isinstance(obj, dict): languageEncodedURLs = [ obj[k] for k in obj.keys() if k.split('/')[-1].split(':')[-1].lower() == "url" and checkForLanguageEncodedURL(obj[k]) ] if any(languageEncodedURLs): raise ValidationException("Language-encoded URL: {}".format( str(languageEncodedURLs))) [checkObjectForURLs(obj[k]) for k in obj.keys()] return (None)
def updateItemLicense(event): """ REST event handler to update item with license parameter, if provided. """ params = event.info['params'] if 'license' not in params: return itemModel = Item() item = itemModel.load(event.info['returnVal']['_id'], force=True, exc=True) newLicense = validateString(params['license']) if item['license'] == newLicense: return # Ensure that new license name is in configured list of licenses. # # Enforcing this here, instead of when validating the item, avoids an extra # database lookup (for the settings) on every future item save. if newLicense: licenseSetting = Setting().get(PluginSettings.LICENSES) validLicense = any( license['name'] == newLicense for group in licenseSetting for license in group['licenses']) if not validLicense: raise ValidationException( 'License name must be in configured list of licenses.', 'license') item['license'] = newLicense item = itemModel.save(item) event.preventDefault() event.addResponse(item)
def createToken(self, key, days=None): """ Create a token using an API key. :param key: The API key (the key itself, not the full document). :type key: str :param days: You may request a token duration up to the token duration of the API key itself, or pass None to use the API key duration. :type days: float or None """ from .setting import Setting from .token import Token from .user import User apiKey = self.findOne({'key': key}) if apiKey is None or not apiKey['active']: raise ValidationException('Invalid API key.') cap = apiKey['tokenDuration'] or Setting().get( SettingKey.COOKIE_LIFETIME) days = min(float(days or cap), cap) user = User().load(apiKey['userId'], force=True) # Mark last used stamp apiKey['lastUse'] = datetime.datetime.utcnow() apiKey = self.save(apiKey) token = Token().createToken(user=user, days=days, scope=apiKey['scope'], apiKey=apiKey) return (user, token)
def remove(self, assetstore, **kwargs): """ Delete an assetstore. If there are any files within this assetstore, a validation exception is raised. :param assetstore: The assetstore document to delete. :type assetstore: dict """ from girderformindlogger.models.file import File files = File().findOne({'assetstoreId': assetstore['_id']}) if files is not None: raise ValidationException( 'You may not delete an assetstore that contains files.') # delete partial uploads before we delete the store. adapter = assetstore_utilities.getAssetstoreAdapter(assetstore) try: adapter.untrackedUploads([], delete=True) except ValidationException: # this assetstore is currently unreachable, so skip this step pass # now remove the assetstore Model.remove(self, assetstore) # If after removal there is no current assetstore, then pick a # different assetstore to be the current one. current = self.findOne({'current': True}) if current is None: first = self.findOne(sort=[('created', SortDir.DESCENDING)]) if first is not None: first['current'] = True self.save(first)
def _validateSmtpPort(doc): try: doc['value'] = int(doc['value']) if doc['value'] > 0: return except ValueError: pass # We want to raise the ValidationException raise ValidationException('SMTP port must be an integer > 0.', 'value')
def _validateSettings(doc): from .quota import ValidateSizeQuota val = doc['value'] val, err = ValidateSizeQuota(val) if err: raise ValidationException(err, 'value') doc['value'] = val
def _validateCookieLifetime(doc): try: doc['value'] = int(doc['value']) if doc['value'] > 0: return except ValueError: pass # We want to raise the ValidationException raise ValidationException('Cookie lifetime must be an integer > 0.', 'value')
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: 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. """ 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, six.string_types): 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)
def _validateUploadMinimumChunkSize(doc): try: doc['value'] = int(doc['value']) if doc['value'] >= 0: return except ValueError: pass # We want to raise the ValidationException raise ValidationException( 'Upload minimum chunk size must be an integer >= 0.', 'value')
def _validateCorsAllowMethods(doc): if isinstance(doc['value'], six.string_types): methods = doc['value'].replace(',', ' ').strip().upper().split() # remove duplicates methods = list(OrderedDict.fromkeys(methods)) doc['value'] = ', '.join(methods) return raise ValidationException( 'Allowed methods must be a comma-separated list or an empty string.', 'value')
def validate(self, doc): doc['name'] = doc['name'].strip() doc['lowerName'] = doc['name'].lower() doc['description'] = doc['description'].strip() if not doc['name']: raise ValidationException('Group name must not be empty.', 'name') q = { 'lowerName': doc['lowerName'], } if '_id' in doc: q['_id'] = {'$ne': doc['_id']} duplicate = self.findOne(q, fields=['_id']) if duplicate is not None: raise ValidationException('A group with that name already exists.', field='name') return doc
def createScreen(self, name, creator, activity=None, description='', readOnly=False): """ Create a new screen. :param name: The name of the item. :type name: str :param description: Description for the screen. :type description: str :param activity: The parent activity of the screen. :param creator: User document representing the creator of the screen. :type creator: dict :param readOnly: A ready-to-use screen :type readOnly: bool :returns: The screen item document that was created. """ try: activity = ActivityModel().load(activity.get('_id', activity), level=AccessType.WRITE, user=creator) except: raise ValidationException( 'Invalid Activity ID or inadequate access rights', 'activity') now = datetime.datetime.utcnow() if not isinstance(creator, dict) or '_id' not in creator: # Internal error -- this shouldn't be called without a user. raise GirderException( 'Creator must be a user.', 'girderformindlogger.models.item.creator-not-user') if 'baseParentType' not in activity: pathFromRoot = self.parentsToRoot({'folderId': activity['_id']}, creator, force=True) activity['baseParentType'] = pathFromRoot[0]['type'] activity['baseParentId'] = pathFromRoot[0]['object']['_id'] return self.save({ 'name': self._validateString(name), 'description': self._validateString(description), 'folderId': ObjectId(activity['_id']), 'creatorId': creator['_id'], 'baseParentType': activity['baseParentType'], 'baseParentId': activity['baseParentId'], 'created': now, 'updated': now, 'size': 0, 'readOnly': readOnly })
def loadJSON(url, urlType='protocol'): from girderformindlogger.exceptions import ValidationException print("Loading {} from {}".format(urlType, url)) try: r = requests.get(url) data = json5.loads(r.text) except: return ({}) raise ValidationException('Invalid ' + urlType + ' URL: ' + url, 'url') return (data)
def listFromString(string): if type(string) not in (str, list): if string is None: return ([]) raise ValidationException('Not a string or list.', str(string)) elif type(string) == list: return (string) elif string.startswith('['): return (literal_eval(string)) else: return ([string])