diff --git a/lib/galaxy/security/__init__.py b/lib/galaxy/security/__init__.py index c627afc0d01..0b0fe9ba232 100644 --- a/lib/galaxy/security/__init__.py +++ b/lib/galaxy/security/__init__.py @@ -208,6 +208,75 @@ class GalaxyRBACAgent( RBACAgent ): roles.add( role ) return self.sort_by_attr( [ role for role in roles ], 'name' ) + def get_valid_dataset_roles( self, trans, dataset, query, page, page_limit ): + """ + This method retrieves the list of possible roles that user can select + in the dataset permissions form. Admins can select any role so the + results are paginated in order to save the bandwidth and to speed + things up. + Standard users can select their own private role, any fo their + sharing roles and any public role (not private and not sharing). + """ + roles = [] + if query is not None: + query = query.replace( '_', '/_' ).replace( '%', '/%' ).replace( '/', '//' ) + search_query = query + '%' + # Limit the query only to get the page needed + limit = page * page_limit + + # Admins see it all + if trans.user_is_admin(): + # Add all roles that fit the query + db_query = trans.sa_session.query( trans.app.model.Role ).filter( self.model.Role.table.c.deleted == False ) + if query is not None: + db_query = db_query.filter( self.model.Role.table.c.name.like( search_query, escape='/' ) ) + for role in ( db_query.order_by( self.model.Role.table.c.name ).limit( limit ) ): + roles.append( role ) + # Take last page of the selection + roles = roles[ ( -page_limit ): ] + + # Non-admins see the list of relevant roles + else: + if self.dataset_is_public( dataset ): + # Add the current user's private role + roles.append( self.get_private_user_role( trans.user ) ) + # Add the current user's sharing roles + for role in self.get_sharing_roles( trans.user ): + roles.append( role ) + # Add all remaining non-private, non-sharing roles + for role in trans.sa_session.query( trans.app.model.Role ) \ + .filter( and_( self.model.Role.table.c.deleted == False, + self.model.Role.table.c.type != self.model.Role.types.PRIVATE, + self.model.Role.table.c.type != self.model.Role.types.SHARING ) ) \ + .order_by( self.model.Role.table.c.name ): + roles.append( role ) + + else: + # If item has roles associated with the access permission, we need to start with them. + access_roles = dataset.get_access_roles( trans ) + for role in access_roles: + if trans.user_is_admin() or self.ok_to_display( trans.user, role ): + roles.append( role ) + # Each role potentially has users. We need to find all roles that each of those users have. + for ura in role.users: + user = ura.user + for ura2 in user.roles: + if trans.user_is_admin() or self.ok_to_display( trans.user, ura2.role ): + roles.append( ura2.role ) + # Each role also potentially has groups which, in turn, have members ( users ). We need to + # find all roles that each group's members have. + for gra in role.groups: + group = gra.group + for uga in group.users: + user = uga.user + for ura in user.roles: + if trans.user_is_admin() or self.ok_to_display( trans.user, ura.role ): + roles.append( ura.role ) + + # Omit duplicated roles by converting to set + return_roles = set( roles ) + return self.sort_by_attr( [ role for role in return_roles ], 'name' ) + def get_legitimate_roles( self, trans, item, cntrller ): """ Return a sorted list of legitimate roles that can be associated with a permission on diff --git a/lib/galaxy/webapps/galaxy/api/lda_datasets.py b/lib/galaxy/webapps/galaxy/api/lda_datasets.py index bcf3ad09b38..6df0d56df05 100644 --- a/lib/galaxy/webapps/galaxy/api/lda_datasets.py +++ b/lib/galaxy/webapps/galaxy/api/lda_datasets.py @@ -75,9 +75,16 @@ class LibraryDatasetsController( BaseAPIController, UsesVisualizationMixin ): for a given dataset permission. """ current_user_roles = trans.get_current_user_roles() - page = int( kwd.get( 'page', None ) ) - page_limit = int( kwd.get( 'page_limit', None ) ) - query = kwd.get ( 'q', None ) + + page = kwd.get( 'page', None ) + if page is not None: + page = int( page ) + + page_limit = kwd.get( 'page_limit', None ) + if page_limit is not None: + page_limit = int( page_limit ) + + query = kwd.get( 'q', None ) if page is None: page = 1 @@ -96,11 +103,8 @@ class LibraryDatasetsController( BaseAPIController, UsesVisualizationMixin ): if not can_manage: raise exceptions.InsufficientPermissionsException( 'You do not have proper permissions to access permissions.' ) - cntrller = 'standard_user' - if trans.user_is_admin(): - cntrller = 'library_admin' + roles = trans.app.security_agent.get_valid_dataset_roles( trans, dataset, query, page, page_limit ) - roles = trans.app.security_agent.get_legitimate_roles( trans, library, cntrller ) total_roles = len( roles ) return_roles = [] for role in roles: @@ -108,6 +112,23 @@ class LibraryDatasetsController( BaseAPIController, UsesVisualizationMixin ): return dict( roles=return_roles, page=page, page_limit=page_limit, total=total_roles ) + @expose_api + def get_roles( self, trans, encoded_dataset_id, **kwd ): + try: + library_dataset = self.get_library_dataset( trans, id=encoded_dataset_id, check_ownership=False, check_accessible=False ) + except Exception, e: + raise exceptions.ObjectNotFound( 'Requested dataset was not found.' + str(e) ) + dataset = library_dataset.library_dataset_dataset_association.dataset + + roles = dataset.get_access_roles( trans ) + + # roles = dataset.get_manage_permissions_roles( trans ) + + # roles = trans.app.security_agent.get_current_dataset_roles( trans, dataset, trans.app.security_agent.permitted_actions.DATASET_ACCESS ) + # Omit duplicated roles by converting to set + roles = set( roles ) + return [ role.name for role in roles ] + @expose_api def delete( self, trans, encoded_dataset_id, **kwd ): """ diff --git a/lib/galaxy/webapps/galaxy/buildapp.py b/lib/galaxy/webapps/galaxy/buildapp.py index 16297c9ed66..5e2ce4be9e4 100644 --- a/lib/galaxy/webapps/galaxy/buildapp.py +++ b/lib/galaxy/webapps/galaxy/buildapp.py @@ -224,6 +224,12 @@ def app_factory( global_conf, **kwargs ): action='show_roles', conditions=dict( method=[ "GET" ] ) ) + webapp.mapper.connect( 'show_legitimate_lda_roles', + '/api/libraries/datasets/:encoded_dataset_id/permissions/current', + controller='lda_datasets', + action='get_roles', + conditions=dict( method=[ "GET" ] ) ) + webapp.mapper.connect( 'delete_lda_item', '/api/libraries/datasets/:encoded_dataset_id', controller='lda_datasets', diff --git a/static/scripts/galaxy.library.js b/static/scripts/galaxy.library.js index f1656d235ae..12146fa7bb1 100644 --- a/static/scripts/galaxy.library.js +++ b/static/scripts/galaxy.library.js @@ -74,6 +74,7 @@ var GalaxyLibrary = Backbone.View.extend({ library_router: null, folderToolbarView: null, folderListView: null, + datasetView: null, initialize : function(){ Galaxy.libraries = this; @@ -106,10 +107,17 @@ var GalaxyLibrary = Backbone.View.extend({ }); this.library_router.on('route:dataset_detail', function(folder_id, dataset_id){ - new mod_library_dataset_view.LibraryDatasetView({id: dataset_id}) + if (Galaxy.libraries.datasetView){ + Galaxy.libraries.datasetView.$el.unbind('click'); + } + Galaxy.libraries.datasetView = new mod_library_dataset_view.LibraryDatasetView({id: dataset_id}); }); + this.library_router.on('route:dataset_permissions', function(folder_id, dataset_id){ - new mod_library_dataset_view.LibraryDatasetView({id: dataset_id, show_permissions: true}) + if (Galaxy.libraries.datasetView){ + Galaxy.libraries.datasetView.$el.unbind('click'); + } + Galaxy.libraries.datasetView = new mod_library_dataset_view.LibraryDatasetView({id: dataset_id, show_permissions: true}); }); Backbone.history.start({pushState: false}); diff --git a/static/scripts/mvc/library/library-dataset-view.js b/static/scripts/mvc/library/library-dataset-view.js index f62fe9ccd87..2e1d60ce50a 100644 --- a/static/scripts/mvc/library/library-dataset-view.js +++ b/static/scripts/mvc/library/library-dataset-view.js @@ -20,10 +20,15 @@ var LibraryDatasetView = Backbone.View.extend({ events: { "click .toolbtn_modify_dataset" : "enableModification", "click .toolbtn_cancel_modifications" : "render", - "click .toolbtn_change_permissions" : "showPermissions", "click .toolbtn-download-dataset" : "downloadDataset", "click .toolbtn-import-dataset" : "importIntoHistory", - "click .toolbtn-share-dataset" : "shareDataset" + "click .toolbtn-share-dataset" : "shareDataset", + + // missing features below + "click .toolbtn_save_modifications" : "comingSoon", + "click .btn-remove-restrictions" : "comingSoon", + "click .btn-make-private" : "comingSoon", + "click .btn-share-dataset" : "comingSoon" }, @@ -132,7 +137,7 @@ var LibraryDatasetView = Backbone.View.extend({ }, importCurrentIntoHistory: function(){ - var self = this; + // var self = this; var history_id = $(this.modal.elMain).find('select[name=dataset_import_single] option:selected').val(); var historyItem = new mod_library_model.HistoryItem(); historyItem.url = historyItem.urlRoot + history_id + '/contents'; @@ -165,88 +170,102 @@ var LibraryDatasetView = Backbone.View.extend({ mod_toastr.info('Feature coming soon.'); }, + goBack: function(){ + Galaxy.libraries.library_router.back(); + }, + showPermissions: function(){ $(".tooltip").remove(); var template = this.templateDatasetPermissions(); this.$el.html(template({item: this.model})); + // Select works different for admins + var is_admin = false; + if (Galaxy.currUser){ + is_admin = Galaxy.currUser.isAdmin(); + } + var self = this; - this.access_perm = new mod_select.View({ - css: 'access_perm', - multiple:true, - placeholder: 'Click to select a role', - container: self.$el.find('#access_perm'), - ajax: { - url: "/api/libraries/datasets/5969b1f7201f12ae/permissions", - dataType: 'json', - quietMillis: 100, - data: function (term, page) { // page is the one-based page number tracked by Select2 - return { - q: term, //search term - page_limit: 10, // page size - page: page // page number - }; - }, - results: function (data, page) { - var more = (page * 10) < data.total; // whether or not there are more results available - // notice we return the value of more so Select2 knows if more results can be loaded - return {results: data.roles, more: more}; - } - }, - formatResult : function roleFormatResult(role) { - return role.name; - }, + // load all current permissions + $.get( "/api/libraries/datasets/" + self.id + "/permissions/current").done(function(fetched_permissions) { + var selected_roles = []; + for (var i = 0; i < fetched_permissions.length; i++) { + selected_roles.push(fetched_permissions[i] + ':' + fetched_permissions[i]); + } + // ACCESS PERMISSIONS + if (is_admin){ // Admin has a special select that allows remote searching + var access_select_options = { + minimumInputLength: 1, + css: 'access_perm', + multiple:true, + placeholder: 'Click to select a role', + container: self.$el.find('#access_perm'), + ajax: { + url: "/api/libraries/datasets/" + self.id + "/permissions", + dataType: 'json', + quietMillis: 100, + data: function (term, page) { // page is the one-based page number tracked by Select2 + return { + q: term, //search term + page_limit: 10, // page size + page: page // page number + }; + }, + results: function (data, page) { + var more = (page * 10) < data.total; // whether or not there are more results available + // notice we return the value of more so Select2 knows if more results can be loaded + return {results: data.roles, more: more}; + } + }, + formatResult : function roleFormatResult(role) { + return role.name + ' type: ' + role.type; + }, - formatSelection: function roleFormatSelection(role) { - return role.name; - }, - initSelection: function(element, callback) { - // the input tag has a value attribute preloaded that points to a preselected role's id - // this function resolves that id attribute to an object that select2 can render - // using its formatResult renderer - that way the role name is shown preselected - var data = []; - $(element.val().split(",")).each(function(i) { - var item = this.split(':'); - data.push({ - id: item[1], - name: item[1] - }); + formatSelection: function roleFormatSelection(role) { + return role.name; + }, + initSelection: function(element, callback) { + // the input tag has a value attribute preloaded that points to a preselected role's id + // this function resolves that id attribute to an object that select2 can render + // using its formatResult renderer - that way the role name is shown preselected + var data = []; + $(element.val().split(",")).each(function() { + var item = this.split(':'); + data.push({ + id: item[1], + name: item[1] + }); + }); + callback(data); + }, + initialData: selected_roles.join(','), + dropdownCssClass: "bigdrop" // apply css that makes the dropdown taller + }; + + this.accessSelectObject = new mod_select.View(access_select_options); + } else { // Non-admins have select with pre-loaded options + var template = this.templateAccessSelect(); + $.get( "/api/libraries/datasets/" + self.id + "/permissions", function( data ) { + $('.access_perm').html(template({options:data.roles})); + this.accessSelectObject = $('#access_select').select2(); + }).fail(function() { + mod_toastr.error('An error occurred while fetching data with permissions. :('); }); - callback(data); - }, - initialData: 'marten@bx.psu.edu:marten@bx.psu.edu', - dropdownCssClass: "bigdrop" // apply css that makes the dropdown taller + } + }).fail(function(){ + mod_toastr.error('An error occurred while fetching data with permissions. :('); }); - // this.access_perm.$el.append($('