Managers: add a 'user_manager' in BaseController; History: allow 'history/share' to accept user ids, add browser tests for same; Browser tests: make api urls absolute and so usable in web-based tests

This commit is contained in:
Carl Eberhard
2015-01-30 16:35:12 -05:00
parent 10e7a335ad
commit 768f37ff37
4 changed files with 320 additions and 58 deletions
+2
View File
@@ -40,6 +40,7 @@ from galaxy.managers import api_keys
from galaxy.managers import tags
from galaxy.managers import workflows
from galaxy.managers import base as managers_base
from galaxy.managers import users
from galaxy.datatypes.metadata import FileParameter
from galaxy.tools.parameters import visit_input_values
from galaxy.tools.parameters.basic import DataToolParameter
@@ -69,6 +70,7 @@ class BaseController( object ):
"""Initialize an interface for application 'app'"""
self.app = app
self.sa_session = app.model.context
self.user_manager = users.UserManager( app )
def get_toolbox(self):
"""Returns the application toolbox"""
@@ -761,7 +761,9 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
histories=histories,
email=email,
send_to_err=send_to_err )
histories, send_to_users, send_to_err = self._get_histories_and_users( trans, user, id, email )
histories = self._get_histories( trans, id )
send_to_users, send_to_err = self._get_users( trans, user, email )
if not send_to_users:
if not send_to_err:
send_to_err += "%s is not a valid Galaxy user. %s" % ( email, err_msg )
@@ -769,14 +771,21 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
histories=histories,
email=email,
send_to_err=send_to_err )
if params.get( 'share_button', False ):
# The user has not yet made a choice about how to share, so dictionaries will be built for display
can_change, cannot_change, no_change_needed, unique_no_change_needed, send_to_err = \
self._populate_restricted( trans, user, histories, send_to_users, None, send_to_err, unique=True )
send_to_err += err_msg
if cannot_change and not no_change_needed and not can_change:
send_to_err = "The histories you are sharing do not contain any datasets that can be accessed by the users with which you are sharing."
return trans.fill_template( "/history/share.mako", histories=histories, email=email, send_to_err=send_to_err )
return trans.fill_template( "/history/share.mako",
histories=histories,
email=email,
send_to_err=send_to_err )
if can_change or cannot_change:
return trans.fill_template( "/history/share.mako",
histories=histories,
@@ -785,12 +794,18 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
can_change=can_change,
cannot_change=cannot_change,
no_change_needed=unique_no_change_needed )
if no_change_needed:
return self._share_histories( trans, user, send_to_err, histories=no_change_needed )
elif not send_to_err:
# User seems to be sharing an empty history
send_to_err = "You cannot share an empty history. "
return trans.fill_template( "/history/share.mako", histories=histories, email=email, send_to_err=send_to_err )
return trans.fill_template( "/history/share.mako",
histories=histories,
email=email,
send_to_err=send_to_err )
@web.expose
@web.require_login( "share restricted histories with other users" )
@@ -807,7 +822,8 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
share_button=True ) )
user = trans.get_user()
user_roles = user.all_roles()
histories, send_to_users, send_to_err = self._get_histories_and_users( trans, user, id, email )
histories = self._get_histories( trans, id )
send_to_users, send_to_err = self._get_users( trans, user, email )
send_to_err = ''
# The user has made a choice, so dictionaries will be built for sharing
can_change, cannot_change, no_change_needed, unique_no_change_needed, send_to_err = \
@@ -856,31 +872,49 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
histories_for_sharing[ send_to_user ].append( history )
return self._share_histories( trans, user, send_to_err, histories=histories_for_sharing )
def _get_histories_and_users( self, trans, user, id, email ):
if not id:
def _get_histories( self, trans, ids ):
if not ids:
# Default to the current history
id = trans.security.encode_id( trans.history.id )
id = galaxy.util.listify( id )
send_to_err = ""
ids = trans.security.encode_id( trans.history.id )
ids = galaxy.util.listify( ids )
histories = []
for history_id in id:
for history_id in ids:
histories.append( self.history_manager.get_owned( trans, self.decode_id( history_id ), trans.user ) )
return histories
def _get_users( self, trans, user, emails_or_ids ):
send_to_users = []
for email_address in galaxy.util.listify( email ):
email_address = email_address.strip()
if email_address:
if email_address == user.email:
send_to_err += "You cannot send histories to yourself. "
else:
send_to_user = trans.sa_session.query( trans.app.model.User ) \
.filter( and_( trans.app.model.User.table.c.email==email_address,
trans.app.model.User.table.c.deleted==False ) ) \
.first()
if send_to_user:
send_to_users.append( send_to_user )
else:
send_to_err += "%s is not a valid Galaxy user. " % email_address
return histories, send_to_users, send_to_err
send_to_err = ""
for string in galaxy.util.listify( emails_or_ids ):
string = string.strip()
if not string:
continue
send_to_user = None
if '@' in string:
email_address = string
send_to_user = self.user_manager.by_email( trans, email_address,
filters=[ trans.app.model.User.table.c.deleted == False ] )
else:
user_id = string
try:
decoded_user_id = self.decode_id( string )
send_to_user = self.user_manager.by_id( trans, decoded_user_id )
if send_to_user.deleted:
send_to_user = None
#TODO: in an ideal world, we would let this bubble up to web.expose which would handle it
except exceptions.MalformedId:
send_to_user = None
if not send_to_user:
send_to_err += "%s is not a valid Galaxy user. " % string
elif send_to_user == user:
send_to_err += "You cannot send histories to yourself. "
else:
send_to_users.append( send_to_user )
return send_to_users, send_to_err
def _populate( self, trans, histories_for_sharing, other, send_to_err ):
# This method will populate the histories_for_sharing dictionary with the users and
@@ -1466,7 +1500,6 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
trans.set_history( history )
return self.history_serializer.serialize_to_view( trans, history, view='detailed' )
except exceptions.MessageException, msg_exc:
print type( msg_exc )
trans.response.status = msg_exc.err_code.code
return { 'err_msg': msg_exc.err_msg, 'err_code': msg_exc.err_code.code }
+227
View File
@@ -0,0 +1,227 @@
var require = patchRequire( require ),
spaceghost = require( 'spaceghost' ).fromCasper( casper ),
xpath = require( 'casper' ).selectXPath,
utils = require( 'utils' ),
format = utils.format;
spaceghost.test.begin( 'Testing the user share form for histories', 0, function suite( test ){
spaceghost.start();
// =================================================================== globals and helpers
var email = spaceghost.user.getRandomEmail(),
password = '123456';
if( spaceghost.fixtureData.testUser ){
email = spaceghost.fixtureData.testUser.email;
password = spaceghost.fixtureData.testUser.password;
spaceghost.info( 'Will use fixtureData.testUser: ' + email );
}
var email2 = spaceghost.user.getRandomEmail(),
password2 = '123456';
if( spaceghost.fixtureData.testUser2 ){
email2 = spaceghost.fixtureData.testUser2.email;
password2 = spaceghost.fixtureData.testUser2.password;
}
var shareLink = 'a[href^="/history/share?"]',
shareSubmit = 'input[name="share_button"]',
firstUserShareButton = '#user-0-popup',
shareHistoryId = null,
shareUserId = null;
function fromUserSharePage( fn ){
spaceghost.then( function(){
this.openHomePage( function(){
this.historyoptions.clickOption( 'Share or Publish' );
});
this.waitForNavigation( 'history/sharing', function(){
this.jumpToMain( function(){
this.click( shareLink );
});
});
this.waitForNavigation( 'history/share', function(){
this.jumpToMain( function(){
fn.call( this );
});
});
});
}
function thenSwitchUser( email, password ){
spaceghost.then( function(){
spaceghost.user.logout();
spaceghost.user.loginOrRegisterUser( email, password );
});
return spaceghost;
}
function thenShareWithUser( comment, emailOrId, thenFn ){
spaceghost.then( function(){
fromUserSharePage( function(){
this.test.comment( comment );
this.fill( 'form#share', {
email : emailOrId
});
// strangely, casper's submit=true flag doesn't work well here - need to manually push the button
this.click( shareSubmit );
});
spaceghost.then( function(){
this.jumpToMain( function(){
thenFn.call( this );
});
});
});
return spaceghost;
}
// =================================================================== TESTS
// create user 1 and the test/target history
spaceghost.user.loginOrRegisterUser( email, password ).openHomePage( function(){
shareHistoryId = this.api.histories.index()[0].id;
this.info( 'shareHistoryId: ' + shareHistoryId );
});
spaceghost.then( function(){
// can't share an empty history (for some reason)
this.api.tools.thenUpload( shareHistoryId, { filepath: '../../test-data/1.bed' });
});
// create user 2 and make sure they can't access the history right now
thenSwitchUser( email2, password2 ).openHomePage( function(){
shareUserId = this.api.users.index()[0].id;
this.info( 'shareUserId: ' + shareUserId );
this.test.comment( 'user2 should not have access to test history' );
this.api.assertRaises( function(){
this.api.histories.show( shareHistoryId );
}, 403, 'History is not accessible by user', 'show failed with error' );
});
thenSwitchUser( email, password );
thenShareWithUser( "should NOT work: share using non-existant user email", 'chunkylover53@aol.com', function(){
this.test.assertExists( '.errormessage', 'found error message' );
this.test.assertSelectorHasText( '.errormessage', 'is not a valid Galaxy user', 'wording is good' );
});
thenShareWithUser( "should NOT work: share using current user email", email, function(){
this.test.assertExists( '.errormessage', 'found error message' );
this.test.assertSelectorHasText( '.errormessage', 'You cannot send histories to yourself', 'wording is good' );
});
thenShareWithUser( "should work: share using email", email2, function(){
this.test.assertExists( firstUserShareButton, 'found user share button' );
this.test.assertSelectorHasText( firstUserShareButton, email2, 'share button text is email2' );
});
// user 2 can now access the history
thenSwitchUser( email2, password2 ).openHomePage( function(){
this.test.comment( 'user 2 can now access the history' );
this.test.assert( !!this.api.histories.show( shareHistoryId ).id );
});
// remove share
thenSwitchUser( email, password ).thenOpen( spaceghost.baseUrl + '/history/sharing', function(){
this.jumpToMain( function(){
this.click( firstUserShareButton );
this.wait( 100, function(){
this.click( 'a[href^="/history/sharing?unshare_user"]' );
});
});
});
spaceghost.then( function(){
this.test.assertDoesntExist( firstUserShareButton, 'no user share button seen' );
});
thenSwitchUser( email2, password2 ).openHomePage( function(){
this.test.comment( 'user2 should not have access to test history (again)' );
this.api.assertRaises( function(){
this.api.histories.show( shareHistoryId );
}, 403, 'History is not accessible by user', 'show failed with error' );
});
// should NOT work: share using malformed id
thenSwitchUser( email, password );
thenShareWithUser( "should NOT work: share using malformed id", '1234xyz', function(){
this.test.assertExists( '.errormessage', 'found error message' );
this.test.assertSelectorHasText( '.errormessage', 'is not a valid Galaxy user', 'wording is good' );
});
//spaceghost.then( function(){
// // test user share using email
// fromUserSharePage( function(){
// this.test.comment( 'should NOT work: share using malformed id' );
// this.fill( '#share', {
// email : '1234xyz'
// });
// this.click( shareSubmit );
// });
// spaceghost.then( function(){
// this.jumpToMain( function(){
// this.test.assertExists( '.errormessage', 'found error message' );
// this.test.assertSelectorHasText( '.errormessage', 'is not a valid Galaxy user', 'wording is good' );
// });
// });
//});
// should NOT work: share using current user id
spaceghost.then( function(){
var currUserId = spaceghost.api.users.index()[0].id;
thenShareWithUser( "should NOT work: share using current user id", currUserId, function(){
this.test.assertExists( '.errormessage', 'found error message' );
this.test.assertSelectorHasText( '.errormessage',
'You cannot send histories to yourself', 'wording is good' );
});
});
//// should NOT work: share using current user id
//spaceghost.then( function(){
// var currUserId = spaceghost.api.users.index()[0].id;
// // test user share using email
// fromUserSharePage( function(){
// this.test.comment( 'should NOT work: share using current user id' );
// this.debug( 'currUserId: ' + currUserId );
// this.fill( 'form#share', {
// email : currUserId
// });
// this.click( shareSubmit );
// });
// spaceghost.then( function(){
// this.jumpToMain( function(){
// this.test.assertExists( '.errormessage', 'found error message' );
// this.test.assertSelectorHasText( '.errormessage',
// 'You cannot send histories to yourself', 'wording is good' );
// });
// });
//});
spaceghost.then( function(){
thenShareWithUser( "should work: share using id", shareUserId, function(){
this.test.assertExists( firstUserShareButton, 'found user share button' );
this.test.assertSelectorHasText( firstUserShareButton, email2, 'share button text is email2' );
});
});
//// should work: share using id
//spaceghost.then( function(){
// // test user share using email
// fromUserSharePage( function(){
// this.test.comment( 'should work: share using id' );
// this.fill( '#share', {
// email : shareUserId
// });
// this.click( shareSubmit );
// });
// spaceghost.then( function(){
// this.jumpToMain( function(){
// this.test.assertExists( firstUserShareButton, 'found user share button' );
// this.test.assertSelectorHasText( firstUserShareButton, email2, 'share button text is email2' );
// });
// });
//});
// user 2 can now access the history
thenSwitchUser( email2, password2 ).openHomePage( function(){
this.test.comment( 'user 2 can now access the history' );
this.test.assert( !!this.api.histories.show( shareHistoryId ).id );
});
/*
*/
// ===================================================================
spaceghost.run( function(){ test.done(); });
});
+32 -32
View File
@@ -187,7 +187,7 @@ ConfigurationAPI.prototype.toString = function toString(){
// -------------------------------------------------------------------
ConfigurationAPI.prototype.urlTpls = {
index : 'api/configuration'
index : '/api/configuration'
};
ConfigurationAPI.prototype.index = function index( deleted ){
@@ -209,12 +209,12 @@ HistoriesAPI.prototype.toString = function toString(){
// -------------------------------------------------------------------
HistoriesAPI.prototype.urlTpls = {
index : 'api/histories',
show : 'api/histories/%s',
create : 'api/histories',
delete_ : 'api/histories/%s',
undelete: 'api/histories/deleted/%s/undelete',
update : 'api/histories/%s',
index : '/api/histories',
show : '/api/histories/%s',
create : '/api/histories',
delete_ : '/api/histories/%s',
undelete: '/api/histories/deleted/%s/undelete',
update : '/api/histories/%s',
};
HistoriesAPI.prototype.index = function index( deleted ){
@@ -292,10 +292,10 @@ HDAAPI.prototype.toString = function toString(){
// -------------------------------------------------------------------
HDAAPI.prototype.urlTpls = {
index : 'api/histories/%s/contents',
show : 'api/histories/%s/contents/%s',
create : 'api/histories/%s/contents',
update : 'api/histories/%s/contents/%s'
index : '/api/histories/%s/contents',
show : '/api/histories/%s/contents/%s',
create : '/api/histories/%s/contents',
update : '/api/histories/%s/contents/%s'
};
HDAAPI.prototype.index = function index( historyId, ids ){
@@ -375,9 +375,9 @@ ToolsAPI.prototype.toString = function toString(){
// -------------------------------------------------------------------
ToolsAPI.prototype.urlTpls = {
index : 'api/tools',
show : 'api/tools/%s',
create : 'api/tools'
index : '/api/tools',
show : '/api/tools/%s',
create : '/api/tools'
};
ToolsAPI.prototype.index = function index( in_panel, trackster ){
@@ -651,14 +651,14 @@ WorkflowsAPI.prototype.toString = function toString(){
// -------------------------------------------------------------------
WorkflowsAPI.prototype.urlTpls = {
index : 'api/workflows',
show : 'api/workflows/%s',
index : '/api/workflows',
show : '/api/workflows/%s',
// run a workflow
create : 'api/workflows',
update : 'api/workflows/%s',
create : '/api/workflows',
update : '/api/workflows/%s',
upload : 'api/workflows/upload', // POST
download: 'api/workflows/download/%s' // GET
upload : '/api/workflows/upload', // POST
download: '/api/workflows/download/%s' // GET
};
WorkflowsAPI.prototype.index = function index(){
@@ -712,12 +712,12 @@ UsersAPI.prototype.toString = function toString(){
// -------------------------------------------------------------------
//NOTE: lots of admin only functionality in this section
UsersAPI.prototype.urlTpls = {
index : 'api/users',
show : 'api/users/%s',
create : 'api/users',
delete_ : 'api/users/%s',
undelete: 'api/users/deleted/%s/undelete',
update : 'api/users/%s'
index : '/api/users',
show : '/api/users/%s',
create : '/api/users',
delete_ : '/api/users/%s',
undelete: '/api/users/deleted/%s/undelete',
update : '/api/users/%s'
};
UsersAPI.prototype.index = function index( deleted ){
@@ -795,12 +795,12 @@ VisualizationsAPI.prototype.toString = function toString(){
// -------------------------------------------------------------------
VisualizationsAPI.prototype.urlTpls = {
index : 'api/visualizations',
show : 'api/visualizations/%s',
create : 'api/visualizations',
//delete_ : 'api/visualizations/%s',
//undelete: 'api/visualizations/deleted/%s/undelete',
update : 'api/visualizations/%s'
index : '/api/visualizations',
show : '/api/visualizations/%s',
create : '/api/visualizations',
//delete_ : '/api/visualizations/%s',
//undelete: '/api/visualizations/deleted/%s/undelete',
update : '/api/visualizations/%s'
};
VisualizationsAPI.prototype.index = function index(){