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 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 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 login(self): import threading from girderformindlogger.utility.mail_utils import validateEmailAddress 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('Authorization') if not authHeader: authHeader = cherrypy.request.headers.get( 'Girder-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) if validateEmailAddress(login): raise AccessException( "Please log in with a username, not an email address.") otpToken = cherrypy.request.headers.get('Girder-OTP') try: user = self._model.authenticate(login, password, otpToken) except: raise AccessException( "Incorrect password for {} if that user exists".format( login)) thread = threading.Thread( target=AppletModel().updateUserCacheAllRoles, args=(user, )) 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.' }
def createAppletFromUrl(self, name, protocolUrl, user=None, roles=None, constraints=None, email='', sendEmail=True): from girderformindlogger.models.protocol import Protocol from girderformindlogger.utility import mail_utils # we have cases to show manager's email to users if mail_utils.validateEmailAddress(email): user['email'] = email user['email_encrypted'] = False UserModel().save(user) # get a protocol from a URL protocol = Protocol().getFromUrl(protocolUrl, 'protocol', user, thread=False, refreshCache=True) protocol = protocol[0].get('protocol', protocol[0]) displayName = Protocol().preferredName(protocol) name = name if name is not None and len(name) else displayName appletName = '{}/'.format(protocolUrl) applet = self.createApplet( name=name, protocol={ '_id': 'protocol/{}'.format(str(protocol.get('_id')).split('/')[-1]), 'url': protocol.get('meta', {}).get('protocol', {}).get('url', protocolUrl) }, user=user, roles=roles, constraints=constraints, appletName=appletName)
def createAppletFromProtocolData(self, name, protocol, user=None, roles=None, constraints=None, email='', sendEmail=True): from girderformindlogger.models.protocol import Protocol from girderformindlogger.utility import mail_utils # we have cases to show manager's email to users if mail_utils.validateEmailAddress(email): user['email'] = email user['email_encrypted'] = False UserModel().save(user) # get a protocol from single json file protocol = Protocol().createProtocol(protocol, user) protocol = protocol.get('protocol', protocol) displayName = Protocol().preferredName(protocol) name = name if name is not None and len(name) else displayName appletName = '{}/'.format(protocol.get('@id')) applet = self.createApplet( name=name, protocol={ '_id': 'protocol/{}'.format(str(protocol.get('_id')).split('/')[-1]) }, user=user, roles=roles, constraints=constraints, appletName=appletName)
def _invite(applet, user, role, rsvp, subject): """ Helper function to invite a user to an applet. :param applet: Applet to invite user to :type applet: AppletModel :param user: ID (canonical or applet-specific) or email address of user to invite :type user: string :param role: Role to invite user to :type role: string :param rsvp: Require user acceptance? :type rsvp: boolean :param subject: Subject about 'user' role can inform or about which 'reviewer' role can review :type subject: string or literal :returns: New assignment (dictionary) """ if role not in USER_ROLE_KEYS: raise ValidationException('Invalid role.', 'role') thisUser = Applet().getCurrentUser() user = user if user else str(thisUser['_id']) if mail_utils.validateEmailAddress(user): user = UserModel().hash(user) if bool(rsvp): groupName = {'title': '{} {}s'.format(str(applet.get('_id')), role)} groupName['lower'] = groupName.get('title', '').lower() group = GroupModel().findOne(query={'lowerName': groupName['lower']}) if not group or group is None: group = GroupModel().createGroup( name=groupName['title'], creator=thisUser, public=bool(role in ['manager', 'reviewer'])) try: assignments = CollectionModel().createCollection(name="Assignments", public=True, reuseExisting=True) assignmentType = 'collection' except AccessException: assignments, assignmentType = selfAssignment() appletAssignment = list(FolderModel().childFolders( parent=assignments, parentType=assignmentType, user=thisUser, filters={ 'meta.applet.@id': str(applet['_id']) if '_id' in applet else None })) appletAssignment = appletAssignment[0] if len( appletAssignment) else FolderModel().setMetadata( FolderModel().createFolder( parent=assignments, name=FolderModel().preferredName(applet), parentType=assignmentType, public=False, creator=thisUser, allowRename=True, reuseExisting=False), { 'applet': { '@id': str(applet['_id']) if '_id' in applet else None } }) meta = appletAssignment.get('meta', {}) members = meta.get('members', []) if meta.get('members') is not None else [] cUser = getUserCipher(appletAssignment, user) subject = subject.upper() if subject is not None and subject.upper( ) in SPECIAL_SUBJECTS else getUserCipher( appletAssignment, str(thisUser['_id']) if subject is None else subject) thisAppletAssignment = { '@id': str(cUser), 'roles': { role: True if role not in ['reviewer', 'user'] else [subject] } } for i, u in enumerate(members): if '@id' in u and u["@id"] == str(cUser): thisAppletAssignment = members.pop(i) if 'roles' not in thisAppletAssignment: thisAppletAssignment['roles'] = {} thisAppletAssignment['roles'][role] = True if role not in [ 'reviewer', 'user' ] else [subject] if (subject in SPECIAL_SUBJECTS) or ( 'reviewer' not in thisAppletAssignment['roles']) else list( set(thisAppletAssignment['roles']['reviewer'] + [subject]).difference(set(SPECIAL_SUBJECTS)) ) if "ALL" not in thisAppletAssignment['roles'][ 'reviewer'] else ["ALL"] members.append(thisAppletAssignment) meta['members'] = members appletAssignment = FolderModel().setMetadata(appletAssignment, meta) authorizeReviewers(appletAssignment) return (appletAssignment)
def inviteUser(self, applet, role="user", email='', firstName='', lastName='', MRN=''): from girderformindlogger.models.invitation import Invitation from girderformindlogger.models.profile import Profile if not mail_utils.validateEmailAddress(email): raise ValidationException('invalid email', 'email') thisUser = self.getCurrentUser() encryptedEmail = UserModel().hash(email) invitedUser = UserModel().findOne({ 'email': encryptedEmail, 'email_encrypted': True }) if not invitedUser: invitedUser = UserModel().findOne({ 'email': email, 'email_encrypted': { '$ne': True } }) if not AppletModel().isCoordinator(applet['_id'], thisUser): raise AccessException( "Only coordinators and managers can invite users.") if role not in USER_ROLE_KEYS: raise ValidationException('Invalid role.', 'role') invitation = Invitation().createInvitationForSpecifiedUser( applet=applet, coordinator=thisUser, role=role, user=invitedUser, firstName=firstName, lastName=lastName, MRN=MRN, userEmail=encryptedEmail) url = 'web.mindlogger.org/#/invitation/%s' % (str(invitation['_id'], )) managers = mail_utils.htmlUserList(AppletModel().listUsers(applet, 'manager', force=True)) coordinators = mail_utils.htmlUserList(AppletModel().listUsers( applet, 'coordinator', force=True)) reviewers = mail_utils.htmlUserList(AppletModel().listUsers( applet, 'reviewer', force=True)) html = mail_utils.renderTemplate( 'userInvite.mako' if invitedUser else 'inviteUserWithoutAccount.mako', { 'url': url, 'userName': firstName, 'coordinatorName': thisUser['firstName'], 'appletName': applet['displayName'], 'MRN': MRN, 'managers': managers, 'coordinators': coordinators, 'reviewers': reviewers }) mail_utils.sendMail('invitation for an applet', html, [email]) return 'sent invitation mail to {}'.format(email)
def acceptInvitation(self, invitation, user, userEmail = ''): # we need to save coordinator/manager's email as plain text from girderformindlogger.models.applet import Applet from girderformindlogger.models.ID_code import IDCode from girderformindlogger.models.profile import Profile from girderformindlogger.utility import mail_utils applet = Applet().load(invitation['appletId'], force=True) profiles = None if 'idCode' in invitation: profiles = IDCode().findProfile(invitation['idCode']) if profiles and len(profiles): profile = [ pro for pro in profiles if str( pro.get('userId') )==str(user['_id']) ] profile = profile[0] if len(profile) else None else: profile = None Profile().removeWithQuery({ '_id': ObjectId(invitation['_id']) }) if profile==None or not len(profile): profile = Profile().createProfile( applet, user, role=invitation.get('role', 'user') ) IDCode().createIdCode(profile, invitation.get('idCode')) if 'schema:knows' in invitation: if 'schema:knows' not in profile: profile['schema:knows'] = invitation['schema:knows'] else: for k in invitation['schema:knows']: if k in profile['schema:knows']: profile['schema:knows'][k].extend([ r for r in invitation['schema:knows'][ k ] if r not in profile['schema:knows'][k] ]) else: profile['schema:knows'][k] = invitation['schema:knows'][ k ] # append role value profile = Profile().load(profile['_id'], force=True) profile['roles'] = profile.get('roles', []) invited_role = invitation.get('role','user') new_roles = [] # manager has get all roles by default for role in USER_ROLES.keys(): if role not in profile['roles']: if invited_role == 'manager' or invited_role == role or role == 'user': new_roles.append(role) profile['roles'].append(role) profile['firstName'] = invitation.get('firstName', '') profile['lastName'] = invitation.get('lastName', '') profile['MRN'] = invitation.get('MRN', '') Profile().save(profile, validate=False) from girderformindlogger.models.user import User as UserModel if not mail_utils.validateEmailAddress(userEmail): raise ValidationException( 'Invalid email address.', 'email' ) if invited_role != 'user' and user.get('email_encrypted', False): if UserModel().hash(userEmail) != user['email']: raise ValidationException( 'Invalid email address.', 'email' ) user['email'] = userEmail user['email_encrypted'] = False UserModel().save(user) UserModel().appendApplet(user, applet['_id'], new_roles) self.remove(invitation) return(Profile().displayProfileFields( Profile().load(profile['_id'], force=True), user ))
def createUser(self, login, password, displayName="", email="", admin=False, public=False, currentUser=None, firstName="", lastName="", encryptEmail=False): """ Create a new user with the given information. :param admin: Whether user is global administrator. :type admin: bool :param public: Whether user is publicly visible. :type public: bool :returns: The user document that was created. """ from girderformindlogger.models.group import Group from girderformindlogger.models.setting import Setting requireApproval = Setting().get( SettingKey.REGISTRATION_POLICY) == 'approve' email = "" if not email else email login = login.lower().strip() email = email.lower().strip() if self.findOne({ 'email': email, 'email_encrypted': { '$ne': True } }) or self.findOne({ 'email': self.hash(email), 'email_encrypted': True }): raise ValidationException( 'That email is already registered in the system.', ) if admin: requireApproval = False encryptEmail = False user = { 'login': login, 'email': email, 'displayName': displayName if len(displayName) else firstName if firstName is not None else "", 'firstName': firstName, 'lastName': lastName, 'created': datetime.datetime.utcnow(), 'emailVerified': False, 'status': 'pending' if requireApproval else 'enabled', 'admin': admin, 'size': 0, 'deviceId': '', 'timezone': 0, 'groups': [], 'groupInvites': [{ "groupId": gi.get('_id'), "level": 0 } for gi in list(Group().find( query={"queue": email}))] if len(email) else [], 'email_encrypted': encryptEmail } if encryptEmail: if len(email) == 0 or not mail_utils.validateEmailAddress(email): raise ValidationException('Invalid email address.', 'email') user['email'] = self.hash(user['email']) self.setPassword(user, password, save=False) self.setPublic(user, public, save=False) if currentUser: self.setUserAccess(user, user=currentUser, level=AccessType.WRITE, save=False) user['creatorId'] = currentUser['_id'] user = self.save(user) if currentUser: User().setUserAccess(doc=currentUser, user=user, level=AccessType.READ, save=True) else: user['creatorId'] = user['_id'] user = self.save(user) verifyEmail = Setting().get( SettingKey.EMAIL_VERIFICATION) != 'disabled' if verifyEmail: self._sendVerificationEmail(user, email) if requireApproval: self._sendApprovalEmail(user) Group().update(query={"queue": user['email']}, update={"$pull": { "queue": user['email'] }}, multi=True) user = self._getGroupInvitesFromProtoUser(user) self._deleteProtoUser(user) return (user)
def login(self, loginAsEmail): import threading from girderformindlogger.utility.mail_utils import validateEmailAddress if not Setting().get(SettingKey.ENABLE_PASSWORD_LOGIN): raise RestException('Password login is disabled on this instance.') user, token = self.getCurrentUser(returnToken=True) deviceId = cherrypy.request.headers.get('deviceId', '') timezone = int(cherrypy.request.headers.get('timezone', 0)) # Only create and send new cookie if user isn't already sending a valid # one. if not user: authHeader = cherrypy.request.headers.get('Authorization') if not authHeader: authHeader = cherrypy.request.headers.get( 'Girder-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) isEmail = validateEmailAddress(login) if not loginAsEmail and isEmail: raise AccessException( "Please log in with a username, not an email address." ) if loginAsEmail and not isEmail: raise AccessException( "Please enter valid email address" ) otpToken = cherrypy.request.headers.get('Girder-OTP') try: user = self._model.authenticate(login, password, otpToken, loginAsEmail = True) except: raise AccessException( "Incorrect password for {} if that user exists".format( login ) ) if user.get('exception', None): raise AccessException( user['exception'] ) if deviceId: user['deviceId'] = deviceId user['timezone'] = timezone self._model.save(user) 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.' }