commit/galaxy-central: carlfeberhard: 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
1 new commit in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/5ac9a9c3d39a/ Changeset: 5ac9a9c3d39a User: carlfeberhard Date: 2015-01-30 21:35:12+00:00 Summary: 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 Affected #: 4 files diff -r e93367302c182b66dc70503a4e02c05a2f0c19ff -r 5ac9a9c3d39a1bd2c260cc793a6a765ed2f9bfbb lib/galaxy/web/base/controller.py --- a/lib/galaxy/web/base/controller.py +++ b/lib/galaxy/web/base/controller.py @@ -40,6 +40,7 @@ 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 @@ """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""" diff -r e93367302c182b66dc70503a4e02c05a2f0c19ff -r 5ac9a9c3d39a1bd2c260cc793a6a765ed2f9bfbb lib/galaxy/webapps/galaxy/controllers/history.py --- a/lib/galaxy/webapps/galaxy/controllers/history.py +++ b/lib/galaxy/webapps/galaxy/controllers/history.py @@ -761,7 +761,9 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 ) + ids = trans.security.encode_id( trans.history.id ) + ids = galaxy.util.listify( ids ) + histories = [] + 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 = [] send_to_err = "" - histories = [] - for history_id in id: - histories.append( self.history_manager.get_owned( trans, self.decode_id( history_id ), trans.user ) ) - 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 + 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 @@ 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 } diff -r e93367302c182b66dc70503a4e02c05a2f0c19ff -r 5ac9a9c3d39a1bd2c260cc793a6a765ed2f9bfbb test/casperjs/history-share-tests.js --- /dev/null +++ b/test/casperjs/history-share-tests.js @@ -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(); }); +}); diff -r e93367302c182b66dc70503a4e02c05a2f0c19ff -r 5ac9a9c3d39a1bd2c260cc793a6a765ed2f9bfbb test/casperjs/modules/api.js --- a/test/casperjs/modules/api.js +++ b/test/casperjs/modules/api.js @@ -187,7 +187,7 @@ // ------------------------------------------------------------------- ConfigurationAPI.prototype.urlTpls = { - index : 'api/configuration' + index : '/api/configuration' }; ConfigurationAPI.prototype.index = function index( deleted ){ @@ -209,12 +209,12 @@ // ------------------------------------------------------------------- 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.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.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.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 @@ // ------------------------------------------------------------------- //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.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(){ Repository URL: https://bitbucket.org/galaxy/galaxy-central/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.
participants (1)
-
commits-noreply@bitbucket.org