def testDirectAdd(self): """ Tests the functionality of an admin user adding a user directly to a group, bypassing the invitation process. """ group = Group().createGroup('g1', self.users[0]) self.assertFalse(Group().hasAccess(group, self.users[1], AccessType.WRITE)) # Admin user can add user 1 directly if they pass force=True. resp = self.request(path='/group/%s/invitation' % group['_id'], method='POST', user=self.users[0], params={ 'force': 'true', 'userId': self.users[1]['_id'], 'level': AccessType.WRITE }) self.assertStatus(resp, 200) user1 = User().load(self.users[1]['_id'], force=True) group = Group().load(group['_id'], force=True) self.assertTrue(Group().hasAccess(group, user1, AccessType.WRITE)) self.assertFalse(Group().hasAccess(group, user1, AccessType.ADMIN)) self.assertTrue(group['_id'] in user1['groups']) # User 1 should not be able to use the force option. resp = self.request(path='/group/%s/invitation' % group['_id'], method='POST', user=self.users[1], params={ 'force': 'true', 'userId': self.users[2]['_id'] }) self.assertStatus(resp, 403) self.assertEqual(resp.json['message'], 'Administrator access required.') user2 = User().load(self.users[2]['_id'], force=True) self.assertFalse(group['_id'] in user2.get('groups', ()))
def fastCrypt(): """ Use faster password hashing to avoid unnecessary testing bottlenecks. """ from girder.models.user import User # CryptContext.update could be used to mutate the existing instance, but if this fixture's scope # is ever made more limited (so that the teardown matters), this approach is more maintainable originalCryptContext = User()._cryptContext User()._cryptContext = originalCryptContext.copy(schemes=['plaintext']) yield User()._cryptContext = originalCryptContext
def _getLdapUser(attrs, server): emails = attrs.get('mail') if not emails: raise Exception('No email record present for the given LDAP user.') if not isinstance(emails, (list, tuple)): emails = (emails,) emails = [e.decode('utf8').lower() for e in emails] existing = User().find({ 'email': {'$in': emails} }, limit=1) if existing.count(): return existing.next() return _registerLdapUser(attrs, emails[0], server)
def __init__(self): super(User, self).__init__() self.resourceName = 'user' self._model = UserModel() self.route('DELETE', ('authentication',), self.logout) self.route('DELETE', (':id',), self.deleteUser) self.route('GET', (), self.find) self.route('GET', ('me',), self.getMe) self.route('GET', ('authentication',), self.login) self.route('GET', (':id',), self.getUser) self.route('GET', (':id', 'details'), self.getUserDetails) self.route('GET', ('details',), self.getUsersDetails) self.route('POST', (), self.createUser) self.route('PUT', (':id',), self.updateUser) self.route('PUT', ('password',), self.changePassword) self.route('PUT', (':id', 'password'), self.changeUserPassword) self.route('GET', ('password', 'temporary', ':id'), self.checkTemporaryPassword) self.route('PUT', ('password', 'temporary'), self.generateTemporaryPassword) self.route('POST', (':id', 'otp'), self.initializeOtp) self.route('PUT', (':id', 'otp'), self.finalizeOtp) self.route('DELETE', (':id', 'otp'), self.removeOtp) self.route('PUT', (':id', 'verification'), self.verifyEmail) self.route('POST', ('verification',), self.sendVerificationEmail)
def testVerifyOtp(server, user): # Enable OTP otpUris = User().initializeOtp(user) user['otp']['enabled'] = True # Generate an invalid token otpToken = _tokenFromTotpUri(otpUris['totpUri'], False) with pytest.raises(AccessException): User().verifyOtp(user, otpToken) # Generate a valid token otpToken = _tokenFromTotpUri(otpUris['totpUri']) # Verify the token, which should succeed without raising an exception User().verifyOtp(user, otpToken) # Re-verify the same token, which should fail # The "server" fixture is necessary for this to work with pytest.raises(AccessException): User().verifyOtp(user, otpToken)
def listKeys(self, userId, limit, offset, sort): user = self.getCurrentUser() if userId not in {None, str(user['_id'])}: self.requireAdmin(user) user = User().load(userId, force=True, exc=True) return list(ApiKeyModel().list(user, offset=offset, limit=limit, sort=sort))
def setUp(self): base.TestCase.setUp(self) user = { 'email': '*****@*****.**', 'login': '******', 'firstName': 'First', 'lastName': 'Last', 'password': '******' } self.admin = User().createUser(**user)
def setUp(self): base.TestCase.setUp(self) self.admin = User().createUser(login='******', firstName='admin', lastName='admin', email='*****@*****.**', password='******') self.user = User().createUser(login='******', firstName='user', lastName='1', email='*****@*****.**', password='******') folders = Folder().childFolders(self.admin, parentType='user', user=self.admin) self.privateFolder, self.publicFolder = list(folders) # show full diff when objects don't match self.maxDiff = None
def check_auth_password(self, username, password): if username.lower() == 'anonymous': return paramiko.AUTH_SUCCESSFUL try: self.girderUser = User().authenticate(username, password, otpToken=True) return paramiko.AUTH_SUCCESSFUL except AccessException: return paramiko.AUTH_FAILED
def setUp(self): base.TestCase.setUp(self) self.siteAdminUser = User().createUser( email='*****@*****.**', login='******', firstName='Robert', lastName='Balboa', password='******' ) self.creatorUser = User().createUser( email='*****@*****.**', login='******', firstName='Apollo', lastName='Creed', password='******' ) creationSetting = Setting().getDefault(SettingKey.COLLECTION_CREATE_POLICY) creationSetting['open'] = True Setting().set(SettingKey.COLLECTION_CREATE_POLICY, creationSetting)
def setUp(self): super().setUp() user = { 'email': '*****@*****.**', 'login': '******', 'firstName': 'First', 'lastName': 'Last', 'password': '******' } self.admin = User().createUser(**user)
def testDatabaseConnectivityRequiresDbFixtureInTesting(): """ This test exists to verify that attempting to use Girder's model layer without using the `db` fixture will raise a reasonable exception rather than accidentally polluting users' actual databases. """ with pytest.raises(Exception) as e: User().find() assert str( e.value ) == 'You must use the "db" fixture in tests that connect to the database.'
def testRegisterAndLoginSha512(self): cherrypy.config['auth']['hash_alg'] = 'sha512' params = { 'email': '*****@*****.**', 'login': '******', 'firstName': 'First', 'lastName': 'Last', 'password': '******' } # Register a user with sha512 storage backend resp = self.request(path='/user', method='POST', params=params) self.assertStatusOk(resp) self._verifyUserDocument(resp.json) user = User().load(resp.json['_id'], force=True) self.assertEqual(user['hashAlg'], 'sha512') # Login unsuccessfully resp = self.request(path='/user/authentication', method='GET', basicAuth='goodlogin:badpassword') self.assertStatus(resp, 401) self.assertEqual('Login failed.', resp.json['message']) # Login successfully resp = self.request(path='/user/authentication', method='GET', basicAuth='goodlogin:goodpassword') self.assertStatusOk(resp) self.assertEqual('Login succeeded.', resp.json['message']) self.assertEqual('*****@*****.**', resp.json['user']['email']) self._verifyUserDocument(resp.json['user']) # Make sure we got a nice cookie self._verifyAuthCookie(resp) token = Token().load(self.cookieVal, objectId=False, force=True) self.assertEqual(str(token['userId']), resp.json['user']['_id']) # Hit the logout endpoint resp = self.request(path='/user/authentication', method='DELETE', token=token['_id']) self._verifyDeletedCookie(resp) token = Token().load(token['_id'], objectId=False, force=True) self.assertEqual(token, None) # Test disabling password login Setting().set(SettingKey.ENABLE_PASSWORD_LOGIN, False) resp = self.request( path='/user/authentication', method='GET', basicAuth='goodlogin:goodpassword') self.assertStatus(resp, 400) self.assertEqual(resp.json['message'], 'Password login is disabled on this instance.')
def import_recursive(job): try: root = job['kwargs']['root'] token = job['kwargs']['token'] user = User().load(job['userId'], force=True) children = list(Folder().childFolders(root, 'collection', user=user)) count = len(children) progress = 0 job = Job().updateJob(job, log='Started TCGA import\n', status=JobStatus.RUNNING, progressCurrent=progress, progressTotal=count) logger.info('Starting recursive TCGA import') for child in children: progress += 1 try: msg = 'Importing "%s"' % child.get('name', '') job = Job().updateJob(job, log=msg, progressMessage=msg + '\n', progressCurrent=progress) logger.debug(msg) Cohort().importDocument(child, recurse=True, user=user, token=token, job=job) job = Job().load(id=job['_id'], force=True) # handle any request to stop execution if (not job or job['status'] in (JobStatus.CANCELED, JobStatus.ERROR)): logger.info('TCGA import job halted with') return except ValidationException: logger.warning('Failed to import %s' % child.get('name', '')) logger.info('Starting recursive TCGA import') job = Job().updateJob(job, log='Finished TCGA import\n', status=JobStatus.SUCCESS, progressCurrent=count, progressMessage='Finished TCGA import') except Exception as e: logger.exception('Importing TCGA failed with %s' % str(e)) job = Job().updateJob(job, log='Import failed with %s\n' % str(e), status=JobStatus.ERROR)
def _recalculateSizes(self, progress): fixes = 0 models = [Collection(), User()] steps = sum(model.find().count() for model in models) progress.update(total=steps, current=0) for model in models: for doc in model.find(): progress.update(increment=1) _, f = model.updateSize(doc) fixes += f return fixes
def admin(db): """ Require an admin user. Provides a user with the admin flag set to True. """ from girder.models.user import User u = User().createUser(email='*****@*****.**', login='******', firstName='Admin', lastName='Admin', password='******', admin=True) yield u
def sendEmail(to=None, subject=None, text=None, toAdmins=False, bcc=None): """ Send an email. This builds the appropriate email object and then triggers an asynchronous event to send the email (handled in _sendmail). :param to: The recipient's email address, or a list of addresses. :type to: str, list/tuple, or None :param subject: The subject line of the email. :type subject: str :param text: The body of the email. :type text: str :param toAdmins: To send an email to all site administrators, set this to True, which will override any "to" argument that was passed. :type toAdmins: bool :param bcc: Recipient email address(es) that should be specified using the Bcc header. :type bcc: str, list/tuple, or None """ from girder.models.setting import Setting from girder.models.user import User to = to or () bcc = bcc or () if toAdmins: to = [u['email'] for u in User().getAdmins()] else: if isinstance(to, six.string_types): to = (to, ) if isinstance(bcc, six.string_types): bcc = (bcc, ) if not to and not bcc: raise Exception('You must specify email recipients via "to" or "bcc", ' 'or use toAdmins=True.') if isinstance(text, six.text_type): text = text.encode('utf8') msg = MIMEText(text, 'html', 'UTF-8') msg['Subject'] = subject or '[no subject]' if to: msg['To'] = ', '.join(to) if bcc: msg['Bcc'] = ', '.join(bcc) msg['From'] = Setting().get(SettingKey.EMAIL_FROM_ADDRESS) events.daemon.trigger('_sendmail', info={ 'message': msg, 'recipients': list(set(to) | set(bcc)) })
def processNotification(store: AssetstoreModel, rootFolder: GirderModel, importPath: str): """ Import at proper location """ if rootFolder is None: raise RestException('Root folder missing') # Find the correct import location given rootFolder and importPath owner = User().findOne({ '_id': ObjectId(rootFolder['creatorId'] or rootFolder['baseParentId']) }) if owner is None: raise RestException('Root has no owner') target = rootFolder importPath = importPath.lstrip('/') realImportPathDir, _ = os.path.split(importPath) storePrefix = str(store['prefix']).lstrip('/') common_path = os.path.commonpath([storePrefix, importPath]) realImportPath = common_path path_from_root = importPath[len(common_path):].lstrip('/') path_from_root_list, _ = os.path.split(path_from_root) for folder_name in path_from_root_list.split('/'): new_target = Folder().findOne({ 'parentId': ObjectId(target['_id']), 'name': folder_name, }) new_import_path = f'{realImportPath}/{folder_name}' if new_target is not None: target = new_target realImportPath = new_import_path else: break realImportPath = realImportPath.lstrip('/') if realImportPath == realImportPathDir: # All the chain of parent directories exist realImportPath = importPath Assetstore().importData( store, target, 'folder', {'importPath': realImportPath}, None, owner, force_recursive=False, )
def addVersionsAndRuns(event: events.Event) -> None: tale = event.info creator = User().load(tale['creatorId'], force=True) versions_root, _ = _createAuxFolder(tale, Constants.VERSIONS_ROOT_DIR_NAME, PluginSettings.VERSIONS_DIRS_ROOT, creator) tale["versionsRootId"] = versions_root["_id"] runs_root, _ = _createAuxFolder(tale, Constants.RUNS_ROOT_DIR_NAME, PluginSettings.RUNS_DIRS_ROOT, creator) tale["runsRootId"] = runs_root["_id"] tale = Tale().save(tale) event.addResponse(tale)
def extract(path): resources = [] if '/' in path: resources += list(Collection().find()) resources += list(User().find()) elif '/collection' in path: resources += list(Collection().find()) elif '/user' in path: resources += list(User().find()) else: resources += [lookUpPath(p)['document'] for p in path] for resource in resources: items = list(Item().find({'baseParentId': resource['_id']})) for item in items: files = list(File().find({'itemId': item['_id']})) for file in files: create_geometa(item, file)
def load(self, info): getPlugin('jobs').load(info) name = 'thumbnails' info['apiRoot'].thumbnail = rest.Thumbnail() for model in (Item(), Collection(), Folder(), User()): model.exposeFields(level=AccessType.READ, fields='_thumbnails') events.bind('model.%s.remove' % model.name, name, removeThumbnails) events.bind('model.file.remove', name, removeThumbnailLink) events.bind('data.process', name, _onUpload)
def user(db, admin): """ Require a user. Provides a regular user with no additional privileges. Note this fixture requires the admin fixture since an administrative user must exist before a regular user can. """ from girder.models.user import User u = User().createUser(email='*****@*****.**', login='******', firstName='user', lastName='user', password='******', admin=False) yield u
def testUsersDetails(self): """ Test that the user count is correct. """ # Create an admin user admin = User().createUser( firstName='Admin', lastName='Admin', login='******', email='*****@*****.**', password='******') # Create a couple of users users = [User().createUser( 'usr%s' % num, 'passwd', 'tst', 'usr', '*****@*****.**' % num) for num in [0, 1]] resp = self.request(path='/user/details', user=admin, method='GET') self.assertStatusOk(resp) self.assertEqual(resp.json['nUsers'], 3) # test for a non-admin user resp = self.request(path='/user/details', user=users[0], method='GET') self.assertStatus(resp, 403) # test for a non-user resp = self.request(path='/user/details', method='GET') self.assertStatus(resp, 401)
def load(self, info): ModelImporter.registerModel('annotationItem', AnnotationItem, plugin='dive_server') ModelImporter.registerModel('revisionLogItem', RevisionLogItem, plugin='dive_server') info["apiRoot"].dive_annotation = AnnotationResource("dive_annotation") info["apiRoot"].dive_configuration = ConfigurationResource("dive_configuration") info["apiRoot"].dive_dataset = DatasetResource("dive_dataset") info["apiRoot"].dive_rpc = RpcResource("dive_rpc") # Setup route additions for exsting resources info["apiRoot"].user.route("PUT", (":id", "use_private_queue"), use_private_queue) User().exposeFields(AccessType.READ, constants.UserPrivateQueueEnabledMarker) # Expose Job dataset assocation Job().exposeFields(AccessType.READ, constants.JOBCONST_DATASET_ID) DIVE_MAIL_TEMPLATES = Path(os.path.realpath(__file__)).parent / 'mail_templates' mail_utils.addTemplateDirectory(str(DIVE_MAIL_TEMPLATES)) # Relocate Girder info["serverRoot"], info["serverRoot"].girder = ( ClientWebroot(), info["serverRoot"], ) info["serverRoot"].api = info["serverRoot"].girder.api events.bind( "filesystem_assetstore_imported", "process_fs_import", process_fs_import, ) events.bind( "s3_assetstore_imported", "process_s3_import", process_s3_import, ) events.bind( 'model.user.save.created', 'send_new_user_email', send_new_user_email, ) # Create dependency on worker plugin.getPlugin('worker').load(info) Setting().set( 'worker.api_url', os.environ.get('WORKER_API_URL', 'http://girder:8080/api/v1'), ) broker_url = os.environ.get('CELERY_BROKER_URL', None) if broker_url is None: raise RuntimeError('CELERY_BROKER_URL must be set') Setting().set('worker.broker', broker_url)
def setUp(self): base.TestCase.setUp(self) info = { 'email': '*****@*****.**', 'login': '******', 'firstName': 'Admin', 'lastName': 'Admin', 'password': '******', 'admin': True } self.admin = User().createUser(**info)
def listChildJobs(self, job): """ Lists the child jobs for a given job :param job: Job document :type job: Job """ query = {'parentId': job['_id']} cursor = self.find(query) user = User().load(job['userId'], force=True) for r in self.filterResultsByPermission(cursor=cursor, user=user, level=AccessType.READ): yield r
def __init__(self): super().__init__() self.coll_m = Collection() self.file_m = File() self.folder_m = Folder() self.item_m = Item() self.upload_m = Upload() self.asset_m = Assetstore() self.user_m = User() self.setupRoutes()
def testPrivateUser(self): """ Make sure private users behave correctly. """ # Create an admin user User().createUser(firstName='Admin', lastName='Admin', login='******', email='*****@*****.**', password='******') # Register a private user (non-admin) pvt = User().createUser(firstName='Guy', lastName='Noir', login='******', email='*****@*****.**', password='******', public=False) self.assertEqual(pvt['public'], False) folder = six.next(Folder().childFolders(parentType='user', parent=pvt)) # Private users should be able to upload files resp = self.request(path='/item', method='POST', user=pvt, params={ 'name': 'foo.txt', 'folderId': folder['_id'] }) self.assertStatusOk(resp) itemId = resp.json['_id'] file = self.uploadFile('hi.txt', 'hello', user=pvt, parent=resp.json, parentType='item') self.assertEqual(str(file['itemId']), itemId)
def testPublishDataONE(self): with mock.patch("gwvolman.tasks.publish.apply_async"), mock.patch( "gwvolman.tasks.publish.delay") as dl: dl.return_value = FakeJob() remoteMemberNode = "nowhere" # non exisiting repository resp = self.request( path="/tale/{_id}/publish".format(**self.tale), method="PUT", user=self.user, params={"repository": remoteMemberNode}, ) self.assertStatus(resp, 400) self.assertEqual(resp.json["message"], "Unknown publisher repository (nowhere)") remoteMemberNode = "https://dev.nceas.ucsb.edu/knb/d1/mn" resp = self.request( path="/tale/{_id}/publish".format(**self.tale), method="PUT", user=self.user, params={"repository": remoteMemberNode}, ) self.assertStatus(resp, 400) self.assertEqual(resp.json["message"], "Missing a token for publisher (dataonestage2).") # "Authenticate" with DataONE token = { "access_token": "dataone_token", "provider": "dataonestage2", "resource_server": "cn-stage-2.dataone.org", "token_type": "dataone", } self.user["otherTokens"] = [token] self.user = User().save(self.user) resp = self.request( path="/tale/{_id}/publish".format(**self.tale), method="PUT", user=self.user, params={"repository": remoteMemberNode}, ) self.assertStatusOk(resp) job_kwargs = dl.call_args_list[-1][1] job_args = dl.call_args_list[-1][0] self.assertEqual(job_args[0], str(self.tale["_id"])) self.assertDictEqual(job_args[1], token) job_kwargs.pop("girder_client_token") self.assertDictEqual(job_kwargs, {"repository": remoteMemberNode})
def setUp(self): base.TestCase.setUp(self) self.user = User().createUser( firstName='First', lastName='Last', login='******', password='******', email='*****@*****.**') self.publicFolder = six.next(Folder().childFolders( parentType='user', parent=self.user, user=None, limit=1)) self.apiKey = ApiKey().createApiKey(self.user, name='') self.downloadDir = os.path.join( os.path.dirname(__file__), '_testDownload') shutil.rmtree(self.downloadDir, ignore_errors=True)
def send_approved(event): dataset = event.info['dataset'] approver = event.info['approver'] user = User().load(dataset['userId'], force=True) html = mail_utils.renderTemplate('mdb.dataset_approved.mako', { 'dataset': dataset, 'user': user, 'approver': approver }) email_address = user['email'] mail_utils.sendEmail(to=email_address, subject='Materials Data Bank: Dataset approved.', text=html)
def _authorizeUploadStep(event): """ Called before any requests dealing with partially completed uploads. Sets the request thread user to the authorized upload token creator if the requested upload is an authorized upload. """ token = getCurrentToken() uploadId = ObjectId(event.info['params'].get('uploadId')) if token and 'authorizedUploadId' in token and token[ 'authorizedUploadId'] == uploadId: user = User().load(token['userId'], force=True) setCurrentUser(user)
def setTaleFolderMapping(event: events.Event): tale = event.info root_path = Setting().get(PluginSettings.TALE_DIRS_ROOT) creator = User().load(tale["creatorId"], force=True) workspace = Tale()._createAuxFolder(tale, WORKSPACE_NAME, creator=creator) absDir = "%s/%s" % (root_path, TalePathMapper().davToPhysical("/" + str(tale["_id"]))) absDir = pathlib.Path(absDir) absDir.mkdir(parents=True, exist_ok=True) workspace.update({'fsPath': absDir.as_posix(), 'isMapping': True}) Folder().save(workspace, validate=True, triggerEvents=False) tale["workspaceId"] = workspace["_id"] tale = Tale().save(tale) event.addResponse(tale)
def createInitialUser(): try: User().createUser( ADMIN_USER, ADMIN_PASS, ADMIN_USER, ADMIN_USER, "*****@*****.**", admin=True, public=True, ) except ValidationException: print("Admin user already exists, skipping...")
def setUp(self): base.TestCase.setUp(self) user = { 'email': '*****@*****.**', 'login': '******', 'firstName': 'First', 'lastName': 'Last', 'password': '******' } self.admin = User().createUser(**user) self.infoMessage = 'Log info message' self.errorMessage = 'Log error message'
def _createOrReuseUser(cls, oauthId, email, firstName, lastName, userName=None): providerName = cls.getProviderName() # Try finding by ID first, since a user can change their email address query = { # PyMongo may not properly support full embedded document queries, # since the object order matters (and Python dicts are unordered), # so search by individual embedded fields 'oauth.provider': providerName, 'oauth.id': oauthId } if providerName == 'google': # The Google provider was previously stored as capitalized, and # legacy databases may still have these entries query['oauth.provider'] = {'$in': ['google', 'Google']} user = User().findOne(query) setId = not user # Existing users using OAuth2 for the first time will not have an ID if not user: user = User().findOne({'email': email}) dirty = False # Create the user if it's still not found if not user: policy = Setting().get(SettingKey.REGISTRATION_POLICY) if policy == 'closed': ignore = Setting().get(PluginSettings.IGNORE_REGISTRATION_POLICY) if not ignore: raise RestException( 'Registration on this instance is closed. Contact an ' 'administrator to create an account for you.') login = cls._deriveLogin(email, firstName, lastName, userName) user = User().createUser( login=login, password=None, firstName=firstName, lastName=lastName, email=email) else: # Migrate from a legacy format where only 1 provider was stored if isinstance(user.get('oauth'), dict): user['oauth'] = [user['oauth']] dirty = True # Update user data from provider if email != user['email']: user['email'] = email dirty = True # Don't set names to empty string if firstName != user['firstName'] and firstName: user['firstName'] = firstName dirty = True if lastName != user['lastName'] and lastName: user['lastName'] = lastName dirty = True if setId: user.setdefault('oauth', []).append( { 'provider': providerName, 'id': oauthId }) dirty = True if dirty: user = User().save(user) return user
GIRDER_COLLECTION = "GeoParser_Upload_Files" class GeoParser(object): @cherrypy.expose def index(self): tmpl = lookup.get_template("index.html") return tmpl.render(salutation="Hello", target="World") if __name__ == '__main__': girderUser = User() girderCollection = Collection() girderFolder = Folder() try: currentUser = girderUser.getAdmins()[0] except: print "ERROR: No user registered or logged in." existinig_collections = [each['name'] for each in girderCollection.list()] if len(existinig_collections) == 0 or not GIRDER_COLLECTION in existinig_collections: girderCollection.createCollection(GIRDER_COLLECTION, currentUser) existinig_collections = [each['name'] for each in girderCollection.list()] else: print "ERROR: Cannot create Collection. User not registered or logged in."
class User(Resource): """API Endpoint for users in the system.""" def __init__(self): super(User, self).__init__() self.resourceName = 'user' self._model = UserModel() self.route('DELETE', ('authentication',), self.logout) self.route('DELETE', (':id',), self.deleteUser) self.route('GET', (), self.find) self.route('GET', ('me',), self.getMe) self.route('GET', ('authentication',), self.login) self.route('GET', (':id',), self.getUser) self.route('GET', (':id', 'details'), self.getUserDetails) self.route('GET', ('details',), self.getUsersDetails) self.route('POST', (), self.createUser) self.route('PUT', (':id',), self.updateUser) self.route('PUT', ('password',), self.changePassword) self.route('PUT', (':id', 'password'), self.changeUserPassword) self.route('GET', ('password', 'temporary', ':id'), self.checkTemporaryPassword) self.route('PUT', ('password', 'temporary'), self.generateTemporaryPassword) self.route('POST', (':id', 'otp'), self.initializeOtp) self.route('PUT', (':id', 'otp'), self.finalizeOtp) self.route('DELETE', (':id', 'otp'), self.removeOtp) self.route('PUT', (':id', 'verification'), self.verifyEmail) self.route('POST', ('verification',), self.sendVerificationEmail) @access.public @filtermodel(model=UserModel) @autoDescribeRoute( Description('List or search for users.') .responseClass('User', array=True) .param('text', "Pass this to perform a full text search for items.", required=False) .pagingParams(defaultSort='lastName') ) def find(self, text, limit, offset, sort): return list(self._model.search( text=text, user=self.getCurrentUser(), offset=offset, limit=limit, sort=sort)) @access.public(scope=TokenScope.USER_INFO_READ) @filtermodel(model=UserModel) @autoDescribeRoute( Description('Get a user by ID.') .responseClass('User') .modelParam('id', model=UserModel, level=AccessType.READ) .errorResponse('ID was invalid.') .errorResponse('You do not have permission to see this user.', 403) ) def getUser(self, user): return user @access.public(scope=TokenScope.USER_INFO_READ) @filtermodel(model=UserModel) @autoDescribeRoute( Description('Retrieve the currently logged-in user information.') .responseClass('User') ) def getMe(self): return self.getCurrentUser() @access.public @autoDescribeRoute( Description('Log in to the system.') .notes('Pass your username and password using HTTP Basic Auth. Sends' ' a cookie that should be passed back in future requests.') .param('Girder-OTP', 'A one-time password for this user', paramType='header', required=False) .errorResponse('Missing Authorization header.', 401) .errorResponse('Invalid login or password.', 403) ) def login(self): if not Setting().get(SettingKey.ENABLE_PASSWORD_LOGIN): raise RestException('Password login is disabled on this instance.') user, token = self.getCurrentUser(returnToken=True) # Only create and send new cookie if user isn't already sending a valid one. if not user: authHeader = cherrypy.request.headers.get('Girder-Authorization') if not authHeader: authHeader = cherrypy.request.headers.get('Authorization') if not authHeader or not authHeader[0:6] == 'Basic ': raise RestException('Use HTTP Basic Authentication', 401) try: credentials = base64.b64decode(authHeader[6:]).decode('utf8') if ':' not in credentials: raise TypeError except Exception: raise RestException('Invalid HTTP Authorization header', 401) login, password = credentials.split(':', 1) otpToken = cherrypy.request.headers.get('Girder-OTP') user = self._model.authenticate(login, password, otpToken) setCurrentUser(user) token = self.sendAuthTokenCookie(user) return { 'user': self._model.filter(user, user), 'authToken': { 'token': token['_id'], 'expires': token['expires'], 'scope': token['scope'] }, 'message': 'Login succeeded.' } @access.public @autoDescribeRoute( Description('Log out of the system.') .responseClass('Token') .notes('Attempts to delete your authentication cookie.') ) def logout(self): token = self.getCurrentToken() if token: Token().remove(token) self.deleteAuthTokenCookie() return {'message': 'Logged out.'} @access.public @filtermodel(model=UserModel, addFields={'authToken'}) @autoDescribeRoute( Description('Create a new user.') .responseClass('User') .param('login', "The user's requested login.") .param('email', "The user's email address.") .param('firstName', "The user's first name.") .param('lastName', "The user's last name.") .param('password', "The user's requested password") .param('admin', 'Whether this user should be a site administrator.', required=False, dataType='boolean', default=False) .errorResponse('A parameter was invalid, or the specified login or' ' email already exists in the system.') ) def createUser(self, login, email, firstName, lastName, password, admin): currentUser = self.getCurrentUser() regPolicy = Setting().get(SettingKey.REGISTRATION_POLICY) if not currentUser or not currentUser['admin']: admin = False if regPolicy == 'closed': raise RestException( 'Registration on this instance is closed. Contact an ' 'administrator to create an account for you.') user = self._model.createUser( login=login, password=password, email=email, firstName=firstName, lastName=lastName, admin=admin) if not currentUser and self._model.canLogin(user): setCurrentUser(user) token = self.sendAuthTokenCookie(user) user['authToken'] = { 'token': token['_id'], 'expires': token['expires'] } return user @access.user @autoDescribeRoute( Description('Delete a user by ID.') .modelParam('id', model=UserModel, level=AccessType.ADMIN) .errorResponse('ID was invalid.') .errorResponse('You do not have permission to delete this user.', 403) ) def deleteUser(self, user): self._model.remove(user) return {'message': 'Deleted user %s.' % user['login']} @access.admin @autoDescribeRoute( Description('Get detailed information about all users.') .errorResponse('You are not a system administrator.', 403) ) def getUsersDetails(self): nUsers = self._model.find().count() return {'nUsers': nUsers} @access.user @filtermodel(model=UserModel) @autoDescribeRoute( Description("Update a user's information.") .modelParam('id', model=UserModel, level=AccessType.WRITE) .param('firstName', 'First name of the user.') .param('lastName', 'Last name of the user.') .param('email', 'The email of the user.') .param('admin', 'Is the user a site admin (admin access required)', required=False, dataType='boolean') .param('status', 'The account status (admin access required)', required=False, enum=('pending', 'enabled', 'disabled')) .errorResponse() .errorResponse(('You do not have write access for this user.', 'Must be an admin to create an admin.'), 403) ) 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) @access.admin @autoDescribeRoute( Description('Change a user\'s password.') .notes('Only administrators may use this endpoint.') .modelParam('id', model=UserModel, level=AccessType.ADMIN) .param('password', 'The user\'s new password.') .errorResponse('You are not an administrator.', 403) .errorResponse('The new password is invalid.') ) def changeUserPassword(self, user, password): self._model.setPassword(user, password) return {'message': 'Password changed.'} @access.user @autoDescribeRoute( Description('Change your password.') .param('old', 'Your current password or a temporary access token.') .param('new', 'Your new password.') .errorResponse(('You are not logged in.', 'Your old password is incorrect.'), 401) .errorResponse('Your new password is invalid.') ) 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.'} @access.public @autoDescribeRoute( Description('Create a temporary access token for a user. The user\'s ' 'password is not changed.') .param('email', 'Your email address.', strip=True) .errorResponse('That email does not exist in the system.') ) def generateTemporaryPassword(self, email): user = self._model.findOne({'email': email.lower()}) if not user: raise RestException('That email is not registered.') token = Token().createToken(user, days=1, scope=TokenScope.TEMPORARY_USER_AUTH) url = '%s#useraccount/%s/token/%s' % ( mail_utils.getEmailUrlPrefix(), str(user['_id']), str(token['_id'])) html = mail_utils.renderTemplate('temporaryAccess.mako', { 'url': url, 'token': str(token['_id']) }) mail_utils.sendEmail( to=email, subject='%s: Temporary access' % Setting().get(SettingKey.BRAND_NAME), text=html ) return {'message': 'Sent temporary access email.'} @access.public @autoDescribeRoute( Description('Check if a specified token is a temporary access token ' 'for the specified user. If the token is valid, returns ' 'information on the token and user.') .modelParam('id', 'The user ID to check.', model=UserModel, force=True) .param('token', 'The token to check.') .errorResponse('The token does not grant temporary access to the specified user.', 401) ) 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.' } @access.public @autoDescribeRoute( Description('Get detailed information about a user.') .modelParam('id', model=UserModel, level=AccessType.READ) .errorResponse() .errorResponse('Read access was denied on the user.', 403) ) def getUserDetails(self, user): return { 'nFolders': self._model.countFolders( user, filterUser=self.getCurrentUser(), level=AccessType.READ) } @access.user @autoDescribeRoute( Description('Initiate the enablement of one-time passwords for this user.') .modelParam('id', model=UserModel, level=AccessType.ADMIN) .errorResponse() .errorResponse('Admin access was denied on the user.', 403) ) def initializeOtp(self, user): if self._model.hasOtpEnabled(user): raise RestException('The user has already enabled one-time passwords.') otpUris = self._model.initializeOtp(user) self._model.save(user) return otpUris @access.user @autoDescribeRoute( Description('Finalize the enablement of one-time passwords for this user.') .modelParam('id', model=UserModel, level=AccessType.ADMIN) .param('Girder-OTP', 'A one-time password for this user', paramType='header') .errorResponse() .errorResponse('Admin access was denied on the user.', 403) ) def finalizeOtp(self, user): otpToken = cherrypy.request.headers.get('Girder-OTP') if not otpToken: raise RestException('The "Girder-OTP" header must be provided.') if 'otp' not in user: raise RestException('The user has not initialized one-time passwords.') if self._model.hasOtpEnabled(user): raise RestException('The user has already enabled one-time passwords.') user['otp']['enabled'] = True # This will raise an exception if the verification fails, so the user will not be saved self._model.verifyOtp(user, otpToken) self._model.save(user) @access.user @autoDescribeRoute( Description('Disable one-time passwords for this user.') .modelParam('id', model=UserModel, level=AccessType.ADMIN) .errorResponse() .errorResponse('Admin access was denied on the user.', 403) ) def removeOtp(self, user): if not self._model.hasOtpEnabled(user): raise RestException('The user has not enabled one-time passwords.') del user['otp'] self._model.save(user) @access.public @autoDescribeRoute( Description('Verify an email address using a token.') .modelParam('id', 'The user ID to check.', model=UserModel, force=True) .param('token', 'The token to check.') .errorResponse('The token is invalid or expired.', 401) ) 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.' } @access.public @autoDescribeRoute( Description('Send verification email.') .param('login', 'Your login or email address.', strip=True) .errorResponse('That login is not registered.', 401) ) def sendVerificationEmail(self, login): loginField = 'email' if '@' in login else 'login' user = self._model.findOne({loginField: login.lower()}) if not user: raise RestException('That login is not registered.', 401) self._model._sendVerificationEmail(user) return {'message': 'Sent verification email.'}
class AssignmentResource(Resource): def __init__(self): super().__init__() self.coll_m = Collection() self.file_m = File() self.folder_m = Folder() self.item_m = Item() self.upload_m = Upload() self.asset_m = Assetstore() self.user_m = User() self.setupRoutes() def setupRoutes(self): self.route('GET', (), handler=self.list) self.route('GET', (':a_id',), handler=self.getAssignment) self.route('GET', ('admin_data',), handler=self.getAdminData) def __findName(self, folder): imageFolder = self.__findImageFolder(folder) if isinstance(imageFolder, dict): return imageFolder['name'] return "" def __findImageFolder(self, folder): owner = self.__findOwner(folder) ret = "" if self.folder_m.getAccessLevel(folder, self.getCurrentUser()) == AccessType.ADMIN: # this folder was created by this user, and so it will contain the images # printFail(folder) ret = folder else: meta = folder['meta'] # this is the label file, and so should only have one entry in the metadata assert len(meta) == 1 # that one entry contains link to the image folder, key must be the creator of this folder assert str(owner['_id']) in meta, (str(owner['_id']), meta) ret = self.folder_m.load(meta[str(owner['_id'])], level=AccessType.READ, user=self.getCurrentUser()) return ret def __findLabelFolder(self, folder): owner = self.__findOwner(folder) ret = [] if self.folder_m.getAccessLevel(folder, self.getCurrentUser()) == AccessType.ADMIN: # this folder was created by this user, so it will contain images if 'meta' not in folder: return [] meta = folder['meta'] for k, v in meta.items(): ret.append(self.folder_m.load(v, level=AccessType.READ, user=self.getCurrentUser())) else: # this folder was not created by this user, so it will contain labels ret = [folder] return ret def __findOwner(self, folder): aclList = Folder().getFullAccessList(folder) for acl in aclList['users']: if acl['level'] == AccessType.ADMIN: return self.user_m.load(str(acl['id']), level=AccessType.READ, user=self.getCurrentUser()) return None @access.public @autoDescribeRoute( Description('Get list of all assignments that it owns or is a part of') .param('limit', 'Number of assignments to return') .param('offset', 'offset from 0th assignment to start looking for assignments')) @rest.rawResponse def list(self, limit, offset): try: printOk((limit, offset)) ret = self.__list(int(limit), int(offset)) cherrypy.response.headers["Content-Type"] = "application/json" return dumps(ret) except: printFail(traceback.print_exc) def __list(self, limit=0, offset=0): user = self.getCurrentUser() folders = self.folder_m.find({'parentId': user['_id'], 'parentCollection': 'user'}, limit=limit, offset=offset) ret = [] for folder in folders: if self.__findName(folder): ret.append({'name': self.__findName(folder), 'image_folder': self.__findImageFolder(folder), 'label_folders': self.__findLabelFolder(folder), 'owner': self.__findOwner(folder)}) return ret def __getAssignment(self, _id): assignments = self.__list() for assignment in assignments: # printOk2(assignment) if str(assignment['image_folder']['_id']) == _id: return assignment return None def __getAnnotators(self, assignment): ret = [] for label_folder in assignment['label_folders']: printOk(label_folder) ret.append({ 'user': self.user_m.load(label_folder['parentId'], user=self.getCurrentUser(), level=AccessType.READ), 'expanded': False, }) return ret @access.public @autoDescribeRoute( Description('Get assignment by id').param('a_id', 'folder id that controls the assignment')) @rest.rawResponse def getAssignment(self, a_id): try: assignment = self.__getAssignment(a_id) return dumps(assignment) except: printFail(traceback.print_exc) @access.public @autoDescribeRoute( Description('Get admin data associated with assignment id').param('a_id', 'folder id that controls the assignment')) @rest.rawResponse def getAdminData(self, a_id): try: assignment = self.__getAssignment(a_id) if assignment['owner']['_id'] == self.getCurrentUser()['_id']: # then current user is this assignment's admin return dumps({'annotators': self.__getAnnotators(assignment)}) except: printFail(traceback.print_exc)