diff --git a/lib/galaxy/webapps/galaxy/api/users.py b/lib/galaxy/webapps/galaxy/api/users.py index 08bf3b295ae4..0e39c648ed2a 100644 --- a/lib/galaxy/webapps/galaxy/api/users.py +++ b/lib/galaxy/webapps/galaxy/api/users.py @@ -3,9 +3,12 @@ """ import logging -import datetime +import random import re +import socket +from datetime import datetime +from markupsafe import escape from sqlalchemy import false, true, and_, or_ from galaxy import exceptions, util, web @@ -14,6 +17,7 @@ from galaxy.security.validate_user_input import validate_email from galaxy.security.validate_user_input import validate_password from galaxy.security.validate_user_input import validate_publicname +from galaxy.web import url_for from galaxy.web import _future_expose_api as expose_api from galaxy.web import _future_expose_api_anonymous as expose_api_anonymous from galaxy.web.base.controller import BaseAPIController @@ -24,7 +28,7 @@ from galaxy.web.base.controller import UsesFormDefinitionsMixin from galaxy.web.form_builder import AddressField from galaxy.tools.toolbox.filters import FilterFactory -from galaxy.util import docstring_trim, listify +from galaxy.util import docstring_trim, listify, hash_util from galaxy.util.odict import odict @@ -231,9 +235,7 @@ def undelete( self, trans, **kwd ): # TODO: move to more basal, common resource than this def anon_user_api_value( self, trans ): - """ - Returns data for an anonymous user, truncated to only usage and quota_percent - """ + """Return data for an anonymous user, truncated to only usage and quota_percent""" usage = trans.app.quota_agent.get_usage( trans ) percent = trans.app.quota_agent.get_percent( trans=trans, usage=usage ) return {'total_disk_usage': int( usage ), @@ -242,9 +244,7 @@ def anon_user_api_value( self, trans ): @expose_api def get_information(self, trans, id, **kwd): - ''' - Returns user details such as public username, type, addresses, etc. - ''' + """Return user details such as username, email, addresses etc.""" user = self._get_user(trans, id) email = user.email username = user.username @@ -315,9 +315,7 @@ def get_information(self, trans, id, **kwd): @expose_api def set_information(self, trans, id, payload={}, **kwd): - ''' - Save a user's email address, public username, type, addresses etc. - ''' + """Save a user's email, username, addresses etc.""" user = self._get_user(trans, id) email = payload.get('email') username = payload.get('username') @@ -327,14 +325,17 @@ def set_information(self, trans, id, payload={}, **kwd): message = validate_publicname(trans, username, user) if message: raise MessageException(message) - # Update user email and user's private role name which must match if user.email != email: + # Update user email and user's private role name which must match private_role = trans.app.security_agent.get_private_user_role(user) private_role.name = email private_role.description = 'Private role for ' + email user.email = email + trans.sa_session.add(user) trans.sa_session.add(private_role) + trans.sa_session.flush() if trans.app.config.user_activation_on: + # Deactivate the user if email was changed and activation is on. user.active = False if self.send_verification_email(trans, user.email, user.username): message = 'The login information has been updated with the changes.
Verification email has been sent to your new email address. Please verify it by clicking the activation link in the email.
Please check your spam/trash folder in case you cannot find the message.' @@ -343,8 +344,8 @@ def set_information(self, trans, id, payload={}, **kwd): if trans.app.config.error_email_to is not None: message += ' Contact: %s' % trans.app.config.error_email_to raise MessageException(message) - # Update public name if user.username != username: + # Update public name user.username = username # Update user custom form user_info_form_id = payload.get('info|form_id') @@ -393,6 +394,60 @@ def set_information(self, trans, id, payload={}, **kwd): trans.log_event('User information added') return {'message': 'User information has been saved.'} + def send_verification_email( self, trans, email, username ): + """ + Send the verification email containing the activation link to the user's email. + """ + if username is None: + username = trans.user.username + activation_link = self.prepare_activation_link( trans, escape( email ) ) + + host = trans.request.host.split( ':' )[ 0 ] + if host in [ 'localhost', '127.0.0.1', '0.0.0.0' ]: + host = socket.getfqdn() + body = ("Hello %s,\n\n" + "In order to complete the activation process for %s begun on %s at %s, please click on the following link to verify your account:\n\n" + "%s \n\n" + "By clicking on the above link and opening a Galaxy account you are also confirming that you have read and agreed to Galaxy's Terms and Conditions for use of this service (%s). This includes a quota limit of one account per user. Attempts to subvert this limit by creating multiple accounts or through any other method may result in termination of all associated accounts and data.\n\n" + "Please contact us if you need help with your account at: %s. You can also browse resources available at: %s. \n\n" + "More about the Galaxy Project can be found at galaxyproject.org\n\n" + "Your Galaxy Team" % (escape( username ), escape( email ), + datetime.utcnow().strftime( "%D"), + trans.request.host, activation_link, + trans.app.config.terms_url, + trans.app.config.error_email_to, + trans.app.config.instance_resource_url)) + to = email + frm = trans.app.config.email_from or 'galaxy-no-reply@' + host + subject = 'Galaxy Account Activation' + try: + util.send_mail( frm, to, subject, body, trans.app.config ) + return True + except Exception: + log.exception( 'Unable to send the activation email.' ) + return False + + def prepare_activation_link( self, trans, email ): + """ + Prepare the account activation link for the user. + """ + activation_token = self.get_activation_token( trans, email ) + activation_link = url_for( controller='user', action='activate', activation_token=activation_token, email=email, qualified=True ) + return activation_link + + def get_activation_token( self, trans, email ): + """ + Check for the activation token. Create new activation token and store it in the database if no token found. + """ + user = trans.sa_session.query( trans.app.model.User ).filter( trans.app.model.User.table.c.email == email ).first() + activation_token = user.activation_token + if activation_token is None: + activation_token = hash_util.new_secure_hash( str( random.getrandbits( 256 ) ) ) + user.activation_token = activation_token + trans.sa_session.add( user ) + trans.sa_session.flush() + return activation_token + def _validate_email_publicname(self, email, username): ''' Validate email and username using regex ''' if email == '' or not isinstance( email, basestring ):