Merged galaxy/galaxy-central into default

This commit is contained in:
Carl Eberhard
2015-01-13 16:17:43 -05:00
9 changed files with 187 additions and 92 deletions
-4
View File
@@ -354,10 +354,6 @@ paste.app_factory = galaxy.web.buildapp:app_factory
# If no message specified the warning box will not be shown.
#registration_warning_message = Please register only one account - we provide this service free of charge and have limited computational resources. Multi-accounts are tracked and will be subjected to account termination and data deletion.
# When users opt to reset passwords, new ones are created, this option
# specifies the length of these passwords.
#reset_password_length = 15
# -- Account activation
-1
View File
@@ -161,7 +161,6 @@ class Configuration( object ):
self.job_walltime_delta = timedelta( 0, s, 0, 0, m, h )
self.admin_users = kwargs.get( "admin_users", "" )
self.admin_users_list = [u.strip() for u in self.admin_users.split(',') if u]
self.reset_password_length = int( kwargs.get('reset_password_length', '15') )
self.mailing_join_addr = kwargs.get('mailing_join_addr', 'galaxy-announce-join@bx.psu.edu')
self.error_email_to = kwargs.get( 'error_email_to', None )
self.activation_email = kwargs.get( 'activation_email', None )
+10 -1
View File
@@ -18,6 +18,7 @@ import json
import socket
import time
import numbers
from datetime import datetime, timedelta
from uuid import UUID, uuid4
from string import Template
from itertools import ifilter
@@ -31,7 +32,7 @@ from galaxy.model.item_attrs import Dictifiable, UsesAnnotations
import galaxy.model.orm.now
from galaxy.security import get_permitted_actions
from galaxy.util import is_multi_byte, nice_size, Params, restore_text, send_mail
from galaxy.util import ready_name_for_url
from galaxy.util import ready_name_for_url, unique_id
from galaxy.util.bunch import Bunch
from galaxy.util.hash_util import new_secure_hash
from galaxy.util.directory_hash import directory_hash_id
@@ -261,6 +262,14 @@ class User( object, Dictifiable ):
environment = User.user_template_environment( user )
return Template( in_string ).safe_substitute( environment )
class PasswordResetToken( object ):
def __init__( self, user, token=None):
if token:
self.token = token
else:
self.token = unique_id()
self.user = user
self.expiration_time = datetime.now() + timedelta(hours=24)
class BaseJobMetric( object ):
+9
View File
@@ -68,6 +68,11 @@ model.UserOpenID.table = Table( "galaxy_user_openid", metadata,
Column( "provider", TrimmedString( 255 ) ),
)
model.PasswordResetToken.table = Table("password_reset_token", metadata,
Column( "token", String( 32 ), primary_key=True, unique=True, index=True ),
Column( "expiration_time", DateTime ),
Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ) )
model.History.table = Table( "history", metadata,
Column( "id", Integer, primary_key=True),
Column( "create_time", DateTime, default=now ),
@@ -1565,6 +1570,10 @@ mapper( model.User, model.User.table,
api_keys=relation( model.APIKeys, backref="user", order_by=desc( model.APIKeys.table.c.create_time ) ),
) )
mapper( model.PasswordResetToken, model.PasswordResetToken.table,
properties=dict( user=relation( model.User, backref="reset_tokens") ) )
# Set up proxy so that this syntax is possible:
# <user_obj>.preferences[pref_name] = pref_value
model.User.preferences = association_proxy('_preferences', 'value', creator=model.UserPreference)
@@ -0,0 +1,43 @@
"""
Migration script for the password reset table
"""
from sqlalchemy import *
from sqlalchemy.orm import *
from migrate import *
from migrate.changeset import *
from galaxy.model.custom_types import *
import datetime
now = datetime.datetime.utcnow
import logging
log = logging.getLogger( __name__ )
metadata = MetaData()
PasswordResetToken_table = Table("password_reset_token", metadata,
Column( "token", String( 32 ), primary_key=True, unique=True, index=True ),
Column( "expiration_time", DateTime ),
Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ))
def upgrade(migrate_engine):
metadata.bind = migrate_engine
print __doc__
metadata.reflect()
try:
PasswordResetToken_table.create()
except Exception as e:
print str(e)
log.exception("Creating %s table failed: %s" % (PasswordResetToken_table.name, str( e ) ) )
def downgrade(migrate_engine):
metadata.bind = migrate_engine
metadata.reflect()
try:
PasswordResetToken_table.drop()
except Exception as e:
print str(e)
log.exception("Dropping %s table failed: %s" % (PasswordResetToken_table.name, str( e ) ) )
+86 -56
View File
@@ -7,7 +7,6 @@ import logging
import os
import random
import socket
import string
import urllib
from datetime import datetime, timedelta
@@ -29,18 +28,24 @@ from galaxy.web.base.controller import (BaseUIController,
UsesFormDefinitionsMixin)
from galaxy.web.form_builder import build_select_field, CheckboxField
from galaxy.web.framework.helpers import escape, grids, time_ago
from galaxy.web.framework.helpers import time_ago, grids
from markupsafe import escape
log = logging.getLogger( __name__ )
require_login_template = """
REQUIRE_LOGIN_TEMPLATE = """
<p>
This %s has been configured such that only users who are logged in may use it.%s
</p>
<p/>
"""
PASSWORD_RESET_TEMPLATE = """
To reset your Galaxy password for the instance at %s, use the following link:
<a href="%s">%s</a>
If you did not make this request, no action is necessary on your part, though
you may want to notify an administrator."""
class UserOpenIDGrid( grids.Grid ):
use_panels = False
@@ -477,14 +482,14 @@ class User( BaseUIController, UsesFormDefinitionsMixin, CreatesUsersMixin, Creat
create_account_str = " If you don't already have an account, <a href='%s'>you may create one</a>." % \
web.url_for( controller='user', action='create', cntrller='user' )
if trans.webapp.name == 'galaxy':
header = require_login_template % ( "Galaxy instance", create_account_str )
header = REQUIRE_LOGIN_TEMPLATE % ( "Galaxy instance", create_account_str )
else:
header = require_login_template % ( "Galaxy tool shed", create_account_str )
header = REQUIRE_LOGIN_TEMPLATE % ( "Galaxy tool shed", create_account_str )
else:
if trans.webapp.name == 'galaxy':
header = require_login_template % ( "Galaxy instance", "" )
header = REQUIRE_LOGIN_TEMPLATE % ( "Galaxy instance", "" )
else:
header = require_login_template % ( "Galaxy tool shed", "" )
header = REQUIRE_LOGIN_TEMPLATE % ( "Galaxy tool shed", "" )
return trans.fill_template( '/user/login.mako',
email=email,
header=header,
@@ -1031,38 +1036,6 @@ class User( BaseUIController, UsesFormDefinitionsMixin, CreatesUsersMixin, Creat
trans.sa_session.add( user )
trans.sa_session.flush()
message = 'The login information has been updated with the changes.'
elif user and params.get( 'change_password_button', False ):
# Editing password. Do not sanitize passwords, so get from kwd
# and not params (which were sanitized).
password = kwd.get( 'password', '' )
confirm = kwd.get( 'confirm', '' )
ok = True
if not is_admin:
# If the current user is changing their own password, validate their current password
current = kwd.get( 'current', '' )
if not trans.user.check_password( current ):
message = 'Invalid current password'
status = 'error'
ok = False
if ok:
# Validate the new password
message = validate_password( trans, password, confirm )
if message:
status = 'error'
else:
# Save new password
user.set_password_cleartext( password )
# Invalidate all other sessions
for other_galaxy_session in trans.sa_session.query( trans.app.model.GalaxySession ) \
.filter( and_( trans.app.model.GalaxySession.table.c.user_id == trans.user.id,
trans.app.model.GalaxySession.table.c.is_valid is True,
trans.app.model.GalaxySession.table.c.id != trans.galaxy_session.id ) ):
other_galaxy_session.is_valid = False
trans.sa_session.add( other_galaxy_session )
trans.sa_session.add( user )
trans.sa_session.flush()
trans.log_event( "User change password" )
message = 'The password has been changed and any other existing Galaxy sessions have been logged out (but jobs in histories in those sessions will not be interrupted).'
elif user and params.get( 'edit_user_info_button', False ):
# Edit user information - webapp MUST BE 'galaxy'
user_type_fd_id = params.get( 'user_type_fd_id', 'none' )
@@ -1105,47 +1078,104 @@ class User( BaseUIController, UsesFormDefinitionsMixin, CreatesUsersMixin, Creat
**kwd ) )
@web.expose
def reset_password( self, trans, email=None, **kwd ):
def change_password( self, trans, token=None, **kwd):
"""
Provides a form with which one can change their password. If token is
provided, don't require current password.
"""
status = None
message = None
user = None
if kwd.get( 'change_password_button', False ):
password = kwd.get( 'password', '' )
confirm = kwd.get( 'confirm', '' )
current = kwd.get( 'current', '' )
token_result = None
if token:
# If a token was supplied, validate and set user
token_result = trans.sa_session.query( trans.app.model.PasswordResetToken ).get(token)
if token_result and token_result.expiration_time > datetime.now():
user = token_result.user
else:
return trans.show_error_message("Invalid or expired password reset token, please request a new one.")
else:
# The user is changing their own password, validate their current password
if trans.user.check_password( current ):
user = trans.user
else:
message = 'Invalid current password'
status = 'error'
if user:
# Validate the new password
message = validate_password( trans, password, confirm )
if message:
status = 'error'
else:
# Save new password
user.set_password_cleartext( password )
# if we used a token, invalidate it and log the user in.
if token_result:
trans.handle_user_login(token_result.user)
token_result.expiration_time = datetime.now()
trans.sa_session.add(token_result)
# Invalidate all other sessions
for other_galaxy_session in trans.sa_session.query( trans.app.model.GalaxySession ) \
.filter( and_( trans.app.model.GalaxySession.table.c.user_id == user.id,
trans.app.model.GalaxySession.table.c.is_valid is True,
trans.app.model.GalaxySession.table.c.id != trans.galaxy_session.id ) ):
other_galaxy_session.is_valid = False
trans.sa_session.add( other_galaxy_session )
trans.sa_session.add( user )
trans.sa_session.flush()
trans.log_event( "User change password" )
return trans.show_ok_message('The password has been changed and any other existing Galaxy sessions have been logged out (but jobs in histories in those sessions will not be interrupted).')
return trans.fill_template( '/user/change_password.mako',
token=token,
status=status,
message=message )
@web.expose
def reset_password( self, trans, email=None, token=None, **kwd ):
"""Reset the user's password. Send an email with the new password."""
if trans.app.config.smtp_server is None:
return trans.show_error_message( "Mail is not configured for this Galaxy instance. Please contact your local Galaxy administrator." )
message = ''
return trans.show_error_message( "Mail is not configured for this Galaxy instance "
"and password reset information cannot be sent. "
"Please contact your local Galaxy administrator." )
message = None
status = 'done'
if kwd.get( 'reset_password_button', False ):
# Default to a non-userinfo-leaking response message
message = "Your reset request for %s has been received. Please check your email account for more instructions. If you do not receive an email shortly, please contact an administrator." % ( escape( email ) )
reset_user = trans.sa_session.query( trans.app.model.User ).filter( trans.app.model.User.table.c.email == email ).first()
user = trans.get_user()
if reset_user:
if user and user.id != reset_user.id:
# This doesn't make any sense because all they have to do is log
# out and then try it again #TODO revisit why this exists.
message = "You may only reset your own password."
status = 'error'
else:
chars = string.letters + string.digits
new_pass = ""
reset_password_length = getattr( trans.app.config, "reset_password_length", 15 )
for i in range( reset_password_length ):
new_pass = new_pass + random.choice( chars )
prt = model.PasswordResetToken( reset_user )
trans.sa_session.add( prt )
trans.sa_session.flush()
host = trans.request.host.split( ':' )[ 0 ]
if host == 'localhost':
host = socket.getfqdn()
body = 'Your password on %s has been reset to:\n\n %s\n' % ( host, new_pass )
reset_url = url_for( controller='user',
action="change_password",
token=prt.token, qualified=True)
body = PASSWORD_RESET_TEMPLATE % ( host, reset_url, reset_url )
frm = 'galaxy-no-reply@' + host
subject = 'Galaxy Password Reset'
try:
util.send_mail( frm, email, subject, body, trans.app.config )
reset_user.set_password_cleartext( new_pass )
trans.sa_session.add( reset_user )
trans.sa_session.flush()
trans.log_event( "User reset password: %s" % email )
message = "Password has been reset and emailed to: %s. <a href='%s'>Click here</a> to return to the login form." % ( escape( email ), web.url_for( controller='user', action='login', noredirect='true' ) )
except Exception, e:
status = 'error'
message = 'Failed to reset password: %s' % str( e )
log.exception( 'Unable to reset password.' )
elif email is not None:
message = "The specified user does not exist."
status = 'error'
elif email is None:
email = ""
return trans.fill_template( '/user/reset_password.mako',
message=message,
status=status )
+31
View File
@@ -0,0 +1,31 @@
<%inherit file="/base.mako"/>
<%namespace file="/message.mako" import="render_msg" />
%if message:
${render_msg( message, status )}
%endif
<div class="toolForm">
<form name="change_password" id="change_password" action="${h.url_for( controller='user', action='change_password' )}" method="post" >
<div class="toolFormTitle">Change Password</div>
%if token:
<input type="hidden" name="token" value="${token|h}"/>
%elif not is_admin:
<div class="form-row">
<label>Current password:</label>
<input type="password" name="current" value="" size="40"/>
</div>
%endif
<div class="form-row">
<label>New password:</label>
<input type="password" name="password" value="" size="40"/>
</div>
<div class="form-row">
<label>Confirm:</label>
<input type="password" name="confirm" value="" size="40"/>
</div>
<div class="form-row">
<input type="submit" name="change_password_button" value="Save"/>
</div>
</form>
</div>
+2 -1
View File
@@ -6,7 +6,8 @@
<ul>
%if t.webapp.name == 'galaxy':
%if not trans.app.config.use_remote_user:
<li><a href="${h.url_for( controller='user', action='manage_user_info', cntrller=cntrller )}">${_('Manage your information')}</a> (email, password, etc.)</li>
<li><a href="${h.url_for( controller='user', action='manage_user_info', cntrller=cntrller )}">${_('Manage your information')}</a> (email, address, etc.)</li>
<li><a href="${h.url_for( controller='user', action='change_password' )}">${_('Change your password')}</a></li>
%endif
<li><a href="${h.url_for( controller='user', action='set_default_permissions', cntrller=cntrller )}">${_('Change default permissions')}</a> for new histories</li>
<li><a href="${h.url_for( controller='user', action='api_keys', cntrller=cntrller )}">${_('Manage your API keys')}</a></li>
+6 -29
View File
@@ -7,7 +7,7 @@
<script type="text/javascript">
$(document).ready(function() {
function validateString(test_string, type) {
function validateString(test_string, type) {
var mail_re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
var username_re = /^[a-z0-9\-]{4,255}$/;
if (type === 'email') {
@@ -15,7 +15,7 @@
} else if (type === 'username'){
return username_re.test(test_string);
}
}
}
function renderError(message) {
$(".donemessage").hide();
@@ -26,8 +26,9 @@
div.className = "errormessage";
div.innerHTML = message;
document.body.insertBefore( div, document.body.firstChild );
}
}
}
function renderDone(message) {
$(".errormessage").hide();
if ($(".donemessage").length === 1) {
@@ -37,8 +38,8 @@
div.className = "donemessage";
div.innerHTML = message;
document.body.insertBefore( div, document.body.firstChild );
}
}
}
original_email = $( '#email_input' ).val();
original_username = $( '#name_input' ).val();
@@ -55,7 +56,6 @@
var hidden_input = '<input type="hidden" id="login_info_button" name="login_info_button" value="Submit"/>';
$( '#send' ).attr( 'disabled', 'disabled' );
$( "#email_input" ).before( hidden_input );
if ( original_email !== email ){
if ( email.length > 255 ){ renderError( error_text_email_long ); validForm = false; }
else if ( !validateString( email, "email" ) ){ renderError( error_text_email ); validForm = false; }
@@ -66,7 +66,7 @@
if ( nothing_changed ){
renderDone( "Nothing has changed." );
}
if ( !validForm || nothing_changed ) {
if ( !validForm || nothing_changed ) {
e.preventDefault();
// reactivate the button if the form wasn't submitted
$( '#send' ).removeAttr( 'disabled' );
@@ -127,27 +127,4 @@
</div>
</form>
</div>
<p></p>
<div class="toolForm">
<form name="change_password" id="change_password" action="${h.url_for( controller='user', action='edit_info', cntrller=cntrller, user_id=trans.security.encode_id( user.id ) )}" method="post" >
<div class="toolFormTitle">Change Password</div>
%if not is_admin:
<div class="form-row">
<label>Current password:</label>
<input type="password" name="current" value="" size="40"/>
</div>
%endif
<div class="form-row">
<label>New password:</label>
<input type="password" name="password" value="" size="40"/>
</div>
<div class="form-row">
<label>Confirm:</label>
<input type="password" name="confirm" value="" size="40"/>
</div>
<div class="form-row">
<input type="submit" name="change_password_button" value="Save"/>
</div>
</form>
</div>
</%def>