mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-10-29 17:42:12 +08:00
Merged galaxy/galaxy-central into default
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -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 ):
|
||||
|
||||
|
||||
@@ -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 ) ) )
|
||||
@@ -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 )
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user