4 new commits in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/1c33c99c5250/ Changeset: 1c33c99c5250 Branch: stable User: carlfeberhard Date: 2014-11-25 19:22:08+00:00 Summary: Sanitize tag output in tagging_common and user.tags_used; protect against closing tags in bootstrapped JSON (http://benalpert.com/2012/08/03/preventing-xss-json.html); minor fixes Affected #: 8 files diff -r 546ff6ef27b4b83e26ae228c292fd981173ac550 -r 1c33c99c5250533fd9b2e36748573d5ae6d23cfa lib/galaxy/tags/tag_handler.py --- a/lib/galaxy/tags/tag_handler.py +++ b/lib/galaxy/tags/tag_handler.py @@ -1,5 +1,7 @@ -import re, logging -from sqlalchemy.sql.expression import func, and_ +import re +import logging +from sqlalchemy.sql.expression import func +from sqlalchemy.sql.expression import and_ from sqlalchemy.sql import select log = logging.getLogger( __name__ ) @@ -25,12 +27,15 @@ self.key_value_separators = "=:" # Initialize with known classes - add to this in subclasses. self.item_tag_assoc_info = {} + def get_tag_assoc_class( self, item_class ): """Returns tag association class for item class.""" return self.item_tag_assoc_info[item_class.__name__].tag_assoc_class + def get_id_col_in_item_tag_assoc_table( self, item_class ): """Returns item id column in class' item-tag association table.""" return self.item_tag_assoc_info[item_class.__name__].item_id_col + def get_community_tags( self, trans, item=None, limit=None ): """Returns community tags for an item.""" # Get item-tag association class. @@ -58,6 +63,7 @@ tag_id = row[0] community_tags.append( self.get_tag_by_id( trans, tag_id ) ) return community_tags + def get_tool_tags( self, trans ): result_set = trans.sa_session.execute( select( columns=[ trans.app.model.ToolTagAssociation.table.c.tag_id ], from_obj=trans.app.model.ToolTagAssociation.table ).distinct() ) @@ -67,6 +73,7 @@ tag_id = row[0] tags.append( self.get_tag_by_id( trans, tag_id ) ) return tags + def remove_item_tag( self, trans, user, item, tag_name ): """Remove a tag from an item.""" # Get item tag association. @@ -78,6 +85,7 @@ item.tags.remove( item_tag_assoc ) return True return False + def delete_item_tags( self, trans, user, item ): """Delete tags from an item.""" # Delete item-tag associations. @@ -85,6 +93,7 @@ trans.sa_session.delete( tag ) # Delete tags from item. del item.tags[:] + def item_has_tag( self, trans, user, item, tag ): """Returns true if item is has a given tag.""" # Get tag name. @@ -97,6 +106,7 @@ if item_tag_assoc: return True return False + def apply_item_tag( self, trans, user, item, name, value=None ): # Use lowercase name for searching/creating tag. lc_name = name.lower() @@ -124,6 +134,7 @@ item_tag_assoc.user_value = value item_tag_assoc.value = lc_value return item_tag_assoc + def apply_item_tags( self, trans, user, item, tags_str ): """Apply tags to an item.""" # Parse tags. @@ -131,6 +142,7 @@ # Apply each tag. for name, value in parsed_tags.items(): self.apply_item_tag( trans, user, item, name, value ) + def get_tags_str( self, tags ): """Build a string from an item's tags.""" # Return empty string if there are no tags. @@ -144,14 +156,17 @@ tag_str += ":" + tag.user_value tags_str_list.append( tag_str ) return ", ".join( tags_str_list ) + def get_tag_by_id( self, trans, tag_id ): """Get a Tag object from a tag id.""" return trans.sa_session.query( trans.app.model.Tag ).filter_by( id=tag_id ).first() + def get_tag_by_name( self, trans, tag_name ): """Get a Tag object from a tag name (string).""" if tag_name: return trans.sa_session.query( trans.app.model.Tag ).filter_by( name=tag_name.lower() ).first() return None + def _create_tag( self, trans, tag_str ): """Create a Tag object from a tag string.""" tag_hierarchy = tag_str.split( self.hierarchy_separator ) @@ -169,6 +184,7 @@ parent_tag = tag tag_prefix = tag.name + self.hierarchy_separator return tag + def _get_or_create_tag( self, trans, tag_str ): """Get or create a Tag object from a tag string.""" # Scrub tag; if tag is None after being scrubbed, return None. @@ -181,6 +197,7 @@ if tag is None: tag = self._create_tag( trans, scrubbed_tag_str ) return tag + def _get_item_tag_assoc( self, user, item, tag_name ): """ Return ItemTagAssociation object for a user, item, and tag string; returns None if there is @@ -191,6 +208,7 @@ if ( item_tag_assoc.user == user ) and ( item_tag_assoc.user_tname == scrubbed_tag_name ): return item_tag_assoc return None + def parse_tags( self, tag_str ): """ Returns a list of raw (tag-name, value) pairs derived from a string; method scrubs tag names and values as well. @@ -210,6 +228,7 @@ scrubbed_value = self._scrub_tag_value( nv_pair[1] ) name_value_pairs[scrubbed_name] = scrubbed_value return name_value_pairs + def _scrub_tag_value( self, value ): """Scrub a tag value.""" # Gracefully handle None: @@ -219,6 +238,7 @@ reg_exp = re.compile( '\s' ) scrubbed_value = re.sub( reg_exp, "", value ) return scrubbed_value + def _scrub_tag_name( self, name ): """Scrub a tag name.""" # Gracefully handle None: @@ -234,12 +254,14 @@ if len( scrubbed_name ) < self.min_tag_len or len( scrubbed_name ) > self.max_tag_len: return None return scrubbed_name + def _scrub_tag_name_list( self, tag_name_list ): """Scrub a tag name list.""" scrubbed_tag_list = list() for tag in tag_name_list: scrubbed_tag_list.append( self._scrub_tag_name( tag ) ) return scrubbed_tag_list + def _get_name_value_pair( self, tag_str ): """Get name, value pair from a tag string.""" # Use regular expression to parse name, value. @@ -250,6 +272,7 @@ name_value_pair.append( None ) return name_value_pair + class GalaxyTagHandler( TagHandler ): def __init__( self ): from galaxy import model @@ -271,6 +294,7 @@ model.VisualizationTagAssociation, model.VisualizationTagAssociation.table.c.visualization_id ) + class CommunityTagHandler( TagHandler ): def __init__( self ): from galaxy.webapps.tool_shed import model diff -r 546ff6ef27b4b83e26ae228c292fd981173ac550 -r 1c33c99c5250533fd9b2e36748573d5ae6d23cfa lib/galaxy/util/json.py --- a/lib/galaxy/util/json.py +++ b/lib/galaxy/util/json.py @@ -57,7 +57,7 @@ return val -def safe_dumps(*args, **kwargs): +def safe_dumps( *args, **kwargs ): """ This is a wrapper around dumps that encodes Infinity and NaN values. It's a fairly rare case (which will be low in request volume). Basically, we tell @@ -65,10 +65,12 @@ re-encoding. """ try: - dumped = json.dumps(*args, allow_nan=False, **kwargs) + dumped = json.dumps( *args, allow_nan=False, **kwargs ) except ValueError: - obj = swap_inf_nan(copy.deepcopy(args[0])) - dumped = json.dumps(obj, allow_nan=False, **kwargs) + obj = swap_inf_nan( copy.deepcopy( args[0] ) ) + dumped = json.dumps( obj, allow_nan=False, **kwargs ) + if kwargs.get( 'escape_closing_tags', True ): + return dumped.replace( '</', '<\\/' ) return dumped diff -r 546ff6ef27b4b83e26ae228c292fd981173ac550 -r 1c33c99c5250533fd9b2e36748573d5ae6d23cfa lib/galaxy/util/validation.py --- a/lib/galaxy/util/validation.py +++ b/lib/galaxy/util/validation.py @@ -8,8 +8,8 @@ def validate_and_sanitize_basestring( key, val ): if not isinstance( val, basestring ): - raise exceptions.RequestParameterInvalidException( '%s must be a string or unicode: %s' - % ( key, str( type( val ) ) ) ) + raise exceptions.RequestParameterInvalidException( '%s must be a string or unicode: %s' + % ( key, str( type( val ) ) ) ) return unicode( sanitize_html( val, 'utf-8', 'text/html' ), 'utf-8' ) diff -r 546ff6ef27b4b83e26ae228c292fd981173ac550 -r 1c33c99c5250533fd9b2e36748573d5ae6d23cfa lib/galaxy/web/framework/helpers/__init__.py --- a/lib/galaxy/web/framework/helpers/__init__.py +++ b/lib/galaxy/web/framework/helpers/__init__.py @@ -111,4 +111,3 @@ Returns true if input is a boolean and true or is a string and looks like a true value. """ return val == True or val in [ 'True', 'true', 'T', 't' ] - diff -r 546ff6ef27b4b83e26ae228c292fd981173ac550 -r 1c33c99c5250533fd9b2e36748573d5ae6d23cfa lib/galaxy/webapps/galaxy/controllers/tag.py --- a/lib/galaxy/webapps/galaxy/controllers/tag.py +++ b/lib/galaxy/webapps/galaxy/controllers/tag.py @@ -64,7 +64,7 @@ trans.log_action( user, unicode( "untag" ), context, params ) # Retag an item. All previous tags are deleted and new tags are applied. - #@web.expose + @web.expose @web.require_login( "Apply a new set of tags to an item; previous tags are deleted." ) def retag_async( self, trans, item_id=None, item_class=None, new_tags=None ): """ @@ -73,7 +73,7 @@ # Apply tags. item = self._get_item( trans, item_class, trans.security.decode_id( item_id ) ) user = trans.user - self.get_tag_handler( trans ).delete_item_tags( trans, item ) + self.get_tag_handler( trans ).delete_item_tags( trans, user, item ) self.get_tag_handler( trans ).apply_item_tags( trans, user, item, new_tags.encode( 'utf-8' ) ) trans.sa_session.flush() diff -r 546ff6ef27b4b83e26ae228c292fd981173ac550 -r 1c33c99c5250533fd9b2e36748573d5ae6d23cfa templates/galaxy_client_app.mako --- a/templates/galaxy_client_app.mako +++ b/templates/galaxy_client_app.mako @@ -15,7 +15,7 @@ //TODO: global... %for key in kwargs: ( window.bootstrapped = window.bootstrapped || {} )[ '${key}' ] = ( - ${ h.dumps( kwargs[ key ], indent=( 2 if trans.debug else 0 ) )} ); + ${ h.dumps( kwargs[ key ], indent=( 2 if trans.debug else 0 ) ).replace( '</', '<\\/' ) } ); %endfor define( 'bootstrapped-data', function(){ return window.bootstrapped; @@ -76,11 +76,17 @@ user_dict = trans.user.to_dict( view='element', value_mapper={ 'id': trans.security.encode_id, 'total_disk_usage': float } ) user_dict[ 'quota_percent' ] = trans.app.quota_agent.get_percent( trans=trans ) + user_dict[ 'is_admin' ] = trans.user_is_admin() # tags used users_api_controller = trans.webapp.api_controllers[ 'users' ] - user_dict[ 'tags_used' ] = users_api_controller.get_user_tags_used( trans, user=trans.user ) - user_dict[ 'is_admin' ] = trans.user_is_admin() + tags_used = [] + for tag in users_api_controller.get_user_tags_used( trans, user=trans.user ): + tag = tag | h + if tag: + tags_used.append( tag ) + user_dict[ 'tags_used' ] = tags_used + return user_dict usage = 0 diff -r 546ff6ef27b4b83e26ae228c292fd981173ac550 -r 1c33c99c5250533fd9b2e36748573d5ae6d23cfa templates/tagging_common.mako --- a/templates/tagging_common.mako +++ b/templates/tagging_common.mako @@ -19,7 +19,7 @@ ## Render HTML for a list of tags. <%def name="render_tagging_element_html(elt_id=None, tags=None, editable=True, use_toggle_link=True, input_size='15', in_form=False, tag_type='individual', render_add_tag_button=True)"> ## Useful attributes. - <% + <% num_tags = len( tags ) %><div class="tag-element" @@ -50,6 +50,7 @@ elif isinstance( tag, ItemTagAssociation ): tag_name = tag.user_tname tag_value = tag.user_value + ## Convert tag name, value to unicode. if isinstance( tag_name, str ): tag_name = unicode( escape( tag_name ), 'utf-8' ) @@ -61,7 +62,7 @@ tag_str = tag_name %><span class="tag-button"> - <span class="tag-name">${tag_str}</span> + <span class="tag-name">${tag_str | h}</span> %if editable: <img class="delete-tag-img" src="${h.url_for('/static/images/delete_tag_icon_gray.png')}"/> %endif @@ -186,10 +187,11 @@ ## Build dict of tag name, values. tag_names_and_values = dict() for tag in item_tags: - tag_name = tag.user_tname + tag_name = escape( tag.user_tname ) tag_value = "" if tag.value is not None: - tag_value = tag.user_value + tag_value = escape( tag.user_value ) + ## Tag names and values may be string or unicode object. if isinstance( tag_name, str ): tag_names_and_values[unicode(tag_name, 'utf-8')] = unicode(tag_value, 'utf-8') diff -r 546ff6ef27b4b83e26ae228c292fd981173ac550 -r 1c33c99c5250533fd9b2e36748573d5ae6d23cfa templates/webapps/galaxy/galaxy.masthead.mako --- a/templates/webapps/galaxy/galaxy.masthead.mako +++ b/templates/webapps/galaxy/galaxy.masthead.mako @@ -1,32 +1,4 @@ -## get user data -<%def name="get_user_json()"> -<% - """Bootstrapping user API JSON""" - #TODO: move into common location (poss. BaseController) - if trans.user: - user_dict = trans.user.to_dict( view='element', value_mapper={ 'id': trans.security.encode_id, - 'total_disk_usage': float } ) - user_dict[ 'quota_percent' ] = trans.app.quota_agent.get_percent( trans=trans ) - users_api_controller = trans.webapp.api_controllers[ 'users' ] - user_dict[ 'tags_used' ] = users_api_controller.get_user_tags_used( trans, user=trans.user ) - user_dict[ 'is_admin' ] = trans.user_is_admin() - else: - usage = 0 - percent = None - try: - usage = trans.app.quota_agent.get_usage( trans, history=trans.history ) - percent = trans.app.quota_agent.get_percent( trans=trans, usage=usage ) - except AssertionError, assertion: - # no history for quota_agent.get_usage assertion - pass - user_dict = { - 'total_disk_usage' : int( usage ), - 'nice_total_disk_usage' : util.nice_size( usage ), - 'quota_percent' : percent - } - return user_dict -%> -</%def> +<%namespace file="/galaxy_client_app.mako" import="get_user_json" /> ## masthead head generator <%def name="load(active_view = None)"> @@ -87,7 +59,7 @@ ], function( mod_masthead, mod_menu, mod_modal, mod_frame, GalaxyUpload, user, quotameter ){ if( !Galaxy.currUser ){ // this doesn't need to wait for the page being readied - Galaxy.currUser = new user.User(${ h.dumps( get_user_json(), indent=2 ) }); + Galaxy.currUser = new user.User(${ h.dumps( masthead_config[ 'user' ][ 'json' ], indent=2 ) }); } $(function() { https://bitbucket.org/galaxy/galaxy-central/commits/07b0b60f17ee/ Changeset: 07b0b60f17ee Branch: stable User: carlfeberhard Date: 2014-11-25 19:29:48+00:00 Summary: Fix to 04a072e98658: remove unnecessary replace Affected #: 1 file diff -r 1c33c99c5250533fd9b2e36748573d5ae6d23cfa -r 07b0b60f17ee5ce4383029f1c98267284e6e0160 templates/galaxy_client_app.mako --- a/templates/galaxy_client_app.mako +++ b/templates/galaxy_client_app.mako @@ -15,7 +15,7 @@ //TODO: global... %for key in kwargs: ( window.bootstrapped = window.bootstrapped || {} )[ '${key}' ] = ( - ${ h.dumps( kwargs[ key ], indent=( 2 if trans.debug else 0 ) ).replace( '</', '<\\/' ) } ); + ${ h.dumps( kwargs[ key ], indent=( 2 if trans.debug else 0 ) ) } ); %endfor define( 'bootstrapped-data', function(){ return window.bootstrapped; https://bitbucket.org/galaxy/galaxy-central/commits/374e94196a14/ Changeset: 374e94196a14 Branch: stable User: carlfeberhard Date: 2014-11-26 17:28:28+00:00 Summary: Security, UI: minor fixes to history, dataset, and page escaping; escape js tag and annotation displays Affected #: 18 files diff -r 07b0b60f17ee5ce4383029f1c98267284e6e0160 -r 374e94196a14673993540a60d446addc6b1780a0 client/galaxy/scripts/mvc/annotations.js --- a/client/galaxy/scripts/mvc/annotations.js +++ b/client/galaxy/scripts/mvc/annotations.js @@ -56,7 +56,7 @@ '<label class="prompt">', _l( 'Annotation' ), '</label>', // set up initial tags by adding as CSV to input vals (necc. to init select2) '<div class="annotation" title="', _l( 'Edit annotation' ), '">', - annotation, + _.escape( annotation ), '</div>' ].join( '' ); }, diff -r 07b0b60f17ee5ce4383029f1c98267284e6e0160 -r 374e94196a14673993540a60d446addc6b1780a0 client/galaxy/scripts/mvc/tags.js --- a/client/galaxy/scripts/mvc/tags.js +++ b/client/galaxy/scripts/mvc/tags.js @@ -56,7 +56,9 @@ if( !_.isArray( tagsArray ) || _.isEmpty( tagsArray ) ){ return ''; } - return tagsArray.sort().join( ',' ); + return tagsArray.map( function( tag ){ + return _.escape( tag ); + }).sort().join( ',' ); }, /** @returns {jQuery} the input for this view */ diff -r 07b0b60f17ee5ce4383029f1c98267284e6e0160 -r 374e94196a14673993540a60d446addc6b1780a0 static/scripts/mvc/annotations.js --- a/static/scripts/mvc/annotations.js +++ b/static/scripts/mvc/annotations.js @@ -56,7 +56,7 @@ '<label class="prompt">', _l( 'Annotation' ), '</label>', // set up initial tags by adding as CSV to input vals (necc. to init select2) '<div class="annotation" title="', _l( 'Edit annotation' ), '">', - annotation, + _.escape( annotation ), '</div>' ].join( '' ); }, diff -r 07b0b60f17ee5ce4383029f1c98267284e6e0160 -r 374e94196a14673993540a60d446addc6b1780a0 static/scripts/mvc/tags.js --- a/static/scripts/mvc/tags.js +++ b/static/scripts/mvc/tags.js @@ -56,7 +56,9 @@ if( !_.isArray( tagsArray ) || _.isEmpty( tagsArray ) ){ return ''; } - return tagsArray.sort().join( ',' ); + return tagsArray.map( function( tag ){ + return _.escape( tag ); + }).sort().join( ',' ); }, /** @returns {jQuery} the input for this view */ diff -r 07b0b60f17ee5ce4383029f1c98267284e6e0160 -r 374e94196a14673993540a60d446addc6b1780a0 static/scripts/packed/mvc/annotations.js --- a/static/scripts/packed/mvc/annotations.js +++ b/static/scripts/packed/mvc/annotations.js @@ -1,1 +1,1 @@ -define(["mvc/base-mvc","utils/localization"],function(a,c){var b=Backbone.View.extend(a.LoggableMixin).extend(a.HiddenUntilActivatedViewMixin).extend({tagName:"div",className:"annotation-display",initialize:function(d){d=d||{};this.tooltipConfig=d.tooltipConfig||{placement:"bottom"};this.listenTo(this.model,"change:annotation",function(){this.render()});this.hiddenUntilActivated(d.$activator,d)},render:function(){var d=this;this.$el.html(this._template());this.$el.find("[title]").tooltip(this.tooltipConfig);this.$annotation().make_text_editable({use_textarea:true,on_finish:function(e){d.$annotation().text(e);d.model.save({annotation:e},{silent:true}).fail(function(){d.$annotation().text(d.model.previous("annotation"))})}});return this},_template:function(){var d=this.model.get("annotation");return['<label class="prompt">',c("Annotation"),"</label>",'<div class="annotation" title="',c("Edit annotation"),'">',d,"</div>"].join("")},$annotation:function(){return this.$el.find(".annotation")},remove:function(){this.$annotation.off();this.stopListening(this.model);Backbone.View.prototype.remove.call(this)},toString:function(){return["AnnotationEditor(",this.model+"",")"].join("")}});return{AnnotationEditor:b}}); \ No newline at end of file +define(["mvc/base-mvc","utils/localization"],function(a,c){var b=Backbone.View.extend(a.LoggableMixin).extend(a.HiddenUntilActivatedViewMixin).extend({tagName:"div",className:"annotation-display",initialize:function(d){d=d||{};this.tooltipConfig=d.tooltipConfig||{placement:"bottom"};this.listenTo(this.model,"change:annotation",function(){this.render()});this.hiddenUntilActivated(d.$activator,d)},render:function(){var d=this;this.$el.html(this._template());this.$el.find("[title]").tooltip(this.tooltipConfig);this.$annotation().make_text_editable({use_textarea:true,on_finish:function(e){d.$annotation().text(e);d.model.save({annotation:e},{silent:true}).fail(function(){d.$annotation().text(d.model.previous("annotation"))})}});return this},_template:function(){var d=this.model.get("annotation");return['<label class="prompt">',c("Annotation"),"</label>",'<div class="annotation" title="',c("Edit annotation"),'">',_.escape(d),"</div>"].join("")},$annotation:function(){return this.$el.find(".annotation")},remove:function(){this.$annotation.off();this.stopListening(this.model);Backbone.View.prototype.remove.call(this)},toString:function(){return["AnnotationEditor(",this.model+"",")"].join("")}});return{AnnotationEditor:b}}); \ No newline at end of file diff -r 07b0b60f17ee5ce4383029f1c98267284e6e0160 -r 374e94196a14673993540a60d446addc6b1780a0 static/scripts/packed/mvc/tags.js --- a/static/scripts/packed/mvc/tags.js +++ b/static/scripts/packed/mvc/tags.js @@ -1,1 +1,1 @@ -define(["mvc/base-mvc","utils/localization"],function(a,b){var c=Backbone.View.extend(a.LoggableMixin).extend(a.HiddenUntilActivatedViewMixin).extend({tagName:"div",className:"tags-display",initialize:function(d){this.listenTo(this.model,"change:tags",function(){this.render()});this.hiddenUntilActivated(d.$activator,d)},render:function(){var d=this;this.$el.html(this._template());this.$input().select2({placeholder:"Add tags",width:"100%",tags:function(){return d._getTagsUsed()}});this._setUpBehaviors();return this},_template:function(){return['<label class="prompt">',b("Tags"),"</label>",'<input class="tags-input" value="',this.tagsToCSV(),'" />'].join("")},tagsToCSV:function(){var d=this.model.get("tags");if(!_.isArray(d)||_.isEmpty(d)){return""}return d.sort().join(",")},$input:function(){return this.$el.find("input.tags-input")},_getTagsUsed:function(){return Galaxy.currUser.get("tags_used")},_setUpBehaviors:function(){var d=this;this.$input().on("change",function(e){d.model.save({tags:e.val},{silent:true});if(e.added){d._addNewTagToTagsUsed(e.added.text+"")}})},_addNewTagToTagsUsed:function(d){var e=Galaxy.currUser.get("tags_used");if(!_.contains(e,d)){e.push(d);e.sort();Galaxy.currUser.set("tags_used",e)}},remove:function(){this.$input.off();this.stopListening(this.model);Backbone.View.prototype.remove.call(this)},toString:function(){return["TagsEditor(",this.model+"",")"].join("")}});return{TagsEditor:c}}); \ No newline at end of file +define(["mvc/base-mvc","utils/localization"],function(a,b){var c=Backbone.View.extend(a.LoggableMixin).extend(a.HiddenUntilActivatedViewMixin).extend({tagName:"div",className:"tags-display",initialize:function(d){this.listenTo(this.model,"change:tags",function(){this.render()});this.hiddenUntilActivated(d.$activator,d)},render:function(){var d=this;this.$el.html(this._template());this.$input().select2({placeholder:"Add tags",width:"100%",tags:function(){return d._getTagsUsed()}});this._setUpBehaviors();return this},_template:function(){return['<label class="prompt">',b("Tags"),"</label>",'<input class="tags-input" value="',this.tagsToCSV(),'" />'].join("")},tagsToCSV:function(){var d=this.model.get("tags");if(!_.isArray(d)||_.isEmpty(d)){return""}return d.map(function(e){return _.escape(e)}).sort().join(",")},$input:function(){return this.$el.find("input.tags-input")},_getTagsUsed:function(){return Galaxy.currUser.get("tags_used")},_setUpBehaviors:function(){var d=this;this.$input().on("change",function(e){d.model.save({tags:e.val},{silent:true});if(e.added){d._addNewTagToTagsUsed(e.added.text+"")}})},_addNewTagToTagsUsed:function(d){var e=Galaxy.currUser.get("tags_used");if(!_.contains(e,d)){e.push(d);e.sort();Galaxy.currUser.set("tags_used",e)}},remove:function(){this.$input.off();this.stopListening(this.model);Backbone.View.prototype.remove.call(this)},toString:function(){return["TagsEditor(",this.model+"",")"].join("")}});return{TagsEditor:c}}); \ No newline at end of file diff -r 07b0b60f17ee5ce4383029f1c98267284e6e0160 -r 374e94196a14673993540a60d446addc6b1780a0 templates/webapps/galaxy/dataset/copy_view.mako --- a/templates/webapps/galaxy/dataset/copy_view.mako +++ b/templates/webapps/galaxy/dataset/copy_view.mako @@ -53,7 +53,7 @@ %><option value="${trans.security.encode_id(hist.id)}" ${selected}> - ${i + 1}: ${h.truncate(util.unicodify( hist.name ), 30)}${current_history_text} + ${i + 1}: ${h.truncate(util.unicodify( hist.name ), 30) | h}${current_history_text} </option> %endfor </select> @@ -70,7 +70,7 @@ %><div class="form-row"><input type="checkbox" name="source_content_ids" id="${input_id}" value="${input_id}"${checked}/> - <label for="${input_id}" style="display: inline;font-weight:normal;"> ${data.hid}: ${h.to_unicode(data.name)}</label> + <label for="${input_id}" style="display: inline;font-weight:normal;"> ${data.hid}: ${h.to_unicode(data.name) | h}</label></div> %endfor %else: @@ -95,7 +95,7 @@ if encoded_id == target_history_id: selected = " selected='selected'" %> - <option value="${encoded_id}"${selected}>${i + 1}: ${h.truncate( util.unicodify( hist.name ), 30)}${source_history_text}</option> + <option value="${encoded_id}"${selected}>${i + 1}: ${h.truncate( util.unicodify( hist.name ), 30) | h}${source_history_text}</option> %endfor </select><br /><br /><a style="margin-left: 10px;" href="javascript:void(0);" id="select-multiple">Choose multiple histories</a> @@ -110,7 +110,7 @@ %><div class="form-row"><input type="checkbox" name="target_history_ids" id="hist_${encoded_id}" value="${encoded_id}"/> - <label for="hist_${encoded_id}" style="display: inline; font-weight:normal;">${i + 1}: ${ util.unicodify( hist.name ) }${cur_history_text}</label> + <label for="hist_${encoded_id}" style="display: inline; font-weight:normal;">${i + 1}: ${ util.unicodify( hist.name ) | h }${cur_history_text}</label></div> %endfor </div> diff -r 07b0b60f17ee5ce4383029f1c98267284e6e0160 -r 374e94196a14673993540a60d446addc6b1780a0 templates/webapps/galaxy/dataset/display.mako --- a/templates/webapps/galaxy/dataset/display.mako +++ b/templates/webapps/galaxy/dataset/display.mako @@ -26,13 +26,11 @@ data.createTabularDatasetChunkedView({ // TODO: encode id. dataset_config: - _.extend( ${h.dumps( item.to_dict() )}, - { - chunk_url: "${h.url_for( controller='/dataset', action='display', - dataset_id=trans.security.encode_id( item.id ))}", - first_data_chunk: ${first_chunk} - } - ), + _.extend( ${h.dumps( item.to_dict() )}, { + chunk_url: "${h.url_for( controller='/dataset', action='display', + dataset_id=trans.security.encode_id( item.id ))}", + first_data_chunk: ${first_chunk} + }), parent_elt: $('.page-body') }); }); diff -r 07b0b60f17ee5ce4383029f1c98267284e6e0160 -r 374e94196a14673993540a60d446addc6b1780a0 templates/webapps/galaxy/dataset/embed.mako --- a/templates/webapps/galaxy/dataset/embed.mako +++ b/templates/webapps/galaxy/dataset/embed.mako @@ -4,10 +4,13 @@ %><%def name="render_item_links( dataset )"> - <a href="${h.url_for( controller='/dataset', action='display', dataset_id=trans.security.encode_id( dataset.id ), to_ext=dataset.ext )}" title="Save dataset" class="icon-button disk"></a> + <a href="${h.url_for( controller='/dataset', action='display', dataset_id=trans.security.encode_id( dataset.id ), to_ext=dataset.ext )}" + title="Save dataset" class="icon-button disk"></a> ## Links for importing and viewing an item. - <a href="${h.url_for( controller='/dataset', action='imp', dataset_id=trans.security.encode_id( item.id ) )}" title="Import dataset" class="icon-button import"></a> - <a class="icon-button go-to-full-screen" href="${h.url_for( controller='/dataset', action='display_by_username_and_slug', username=dataset.history.user.username, slug=trans.security.encode_id( dataset.id ) )}" title="Go to dataset"></a> + <a href="${h.url_for( controller='/dataset', action='imp', dataset_id=trans.security.encode_id( item.id ) )}" + title="Import dataset" class="icon-button import"></a> + <a href="${h.url_for( controller='/dataset', action='display_by_username_and_slug', username=dataset.history.user.username, slug=trans.security.encode_id( dataset.id ) )}" + title="Go to dataset" class="icon-button go-to-full-screen"></a></%def> diff -r 07b0b60f17ee5ce4383029f1c98267284e6e0160 -r 374e94196a14673993540a60d446addc6b1780a0 templates/webapps/galaxy/dataset/errors.mako --- a/templates/webapps/galaxy/dataset/errors.mako +++ b/templates/webapps/galaxy/dataset/errors.mako @@ -49,7 +49,7 @@ <body><h2>Dataset generation errors</h2> - <p><b>Dataset ${hda.hid}: ${hda.display_name()}</b></p> + <p><b>Dataset ${hda.hid}: ${hda.display_name() | h}</b></p><% job = hda.creating_job %> %if job: diff -r 07b0b60f17ee5ce4383029f1c98267284e6e0160 -r 374e94196a14673993540a60d446addc6b1780a0 templates/webapps/galaxy/dataset/security_common.mako --- a/templates/webapps/galaxy/dataset/security_common.mako +++ b/templates/webapps/galaxy/dataset/security_common.mako @@ -77,7 +77,7 @@ else: current_actions = [] permitted_actions = {}.items() - obj_str = 'unknown object %s' %obj_name + obj_str = 'unknown object %s' % obj_name obj_type = '' %><script type="text/javascript"> @@ -104,7 +104,7 @@ }); </script><div class="toolForm"> - <div class="toolFormTitle">Manage ${obj_type} permissions on ${obj_str}</div> + <div class="toolFormTitle">Manage ${obj_type} permissions on ${obj_str | h}</div><div class="toolFormBody"><form name="edit_role_associations" id="edit_role_associations" action="${form_url}" method="post"><div class="form-row"></div> diff -r 07b0b60f17ee5ce4383029f1c98267284e6e0160 -r 374e94196a14673993540a60d446addc6b1780a0 templates/webapps/galaxy/dataset/tabular_chunked.mako --- a/templates/webapps/galaxy/dataset/tabular_chunked.mako +++ b/templates/webapps/galaxy/dataset/tabular_chunked.mako @@ -15,14 +15,12 @@ } }); - require(['mvc/data'], function(data) { + require([ 'mvc/data' ], function( data ) { data.createTabularDatasetChunkedView({ - dataset_config: _.extend( ${h.dumps( trans.security.encode_dict_ids( dataset.to_dict() ) )}, - { - first_data_chunk: ${chunk} - } - ), - parent_elt: $('body') + dataset_config : _.extend( ${ h.dumps( trans.security.encode_dict_ids( dataset.to_dict() ) )}, { + first_data_chunk: ${ chunk } + }), + parent_elt : $( 'body' ) }); }); </script> diff -r 07b0b60f17ee5ce4383029f1c98267284e6e0160 -r 374e94196a14673993540a60d446addc6b1780a0 templates/webapps/galaxy/history/embed.mako --- a/templates/webapps/galaxy/history/embed.mako +++ b/templates/webapps/galaxy/history/embed.mako @@ -24,7 +24,7 @@ </a></h4> %if hasattr( item, "annotation") and item.annotation: - <div class="annotation">${item.annotation}</div> + <div class="annotation">${item.annotation | h}</div> %endif </div><div class='summary-content'> diff -r 07b0b60f17ee5ce4383029f1c98267284e6e0160 -r 374e94196a14673993540a60d446addc6b1780a0 templates/webapps/galaxy/history/history_panel.mako --- a/templates/webapps/galaxy/history/history_panel.mako +++ /dev/null @@ -1,14 +0,0 @@ -## shortcuts for script tags that create history panels -## ---------------------------------------------------------------------------- -<%def name="current_history_panel( selector_to_attach_to=None, options )"> -</%def> - - -## ---------------------------------------------------------------------------- -<%def name="history_panel( history_id, selector_to_attach_to=None, options )"> -</%def> - - -## ---------------------------------------------------------------------------- -<%def name="bootstrapped_history_panel( history, hdas, selector_to_attach_to=None, options )"> -</%def> diff -r 07b0b60f17ee5ce4383029f1c98267284e6e0160 -r 374e94196a14673993540a60d446addc6b1780a0 templates/webapps/galaxy/history/share.mako --- a/templates/webapps/galaxy/history/share.mako +++ b/templates/webapps/galaxy/history/share.mako @@ -20,7 +20,7 @@ <tr><td><input type="hidden" name="id" value="${trans.security.encode_id( history.id )}"> - ${ util.unicodify( history.name )} + ${ util.unicodify( history.name ) | h } </td><td> %if len( history.datasets ) < 1: diff -r 07b0b60f17ee5ce4383029f1c98267284e6e0160 -r 374e94196a14673993540a60d446addc6b1780a0 templates/webapps/galaxy/history/view.mako --- a/templates/webapps/galaxy/history/view.mako +++ b/templates/webapps/galaxy/history/view.mako @@ -1,4 +1,4 @@ -<%namespace file="/galaxy.masthead.mako" import="get_user_json" /> +<%namespace file="/galaxy_client_app.mako" import="get_user_json" /> ## ---------------------------------------------------------------------------- <%! @@ -166,9 +166,9 @@ // use_panels effects where the the center_panel() is rendered: // w/o it renders to the body, w/ it renders to #center - we need to adjust a few things for scrolling to work var hasMasthead = ${ 'true' if use_panels else 'false' }, - userIsOwner = ${'true' if user_is_owner else 'false'}, - historyJSON = ${h.dumps( history )}, - hdaJSON = ${h.dumps( hdas )}, + userIsOwner = ${ 'true' if user_is_owner else 'false' }, + historyJSON = ${ h.dumps( history ) }, + hdaJSON = ${ h.dumps( hdas ) }, panelToUse = ( userIsOwner )? //TODO: change class names ({ location: 'mvc/history/history-panel-edit', className: 'HistoryPanelEdit' }): diff -r 07b0b60f17ee5ce4383029f1c98267284e6e0160 -r 374e94196a14673993540a60d446addc6b1780a0 templates/webapps/galaxy/page/editor.mako --- a/templates/webapps/galaxy/page/editor.mako +++ b/templates/webapps/galaxy/page/editor.mako @@ -47,7 +47,7 @@ <a id="close-button" class="panel-header-button">Close</a></div><div class="unified-panel-header-inner"> - Page Editor <span style="font-weight: normal">| Title : ${page.title}</span> + Page Editor <span style="font-weight: normal">| Title : ${page.title | h}</span></div></div> diff -r 07b0b60f17ee5ce4383029f1c98267284e6e0160 -r 374e94196a14673993540a60d446addc6b1780a0 templates/webapps/galaxy/page/index.mako --- a/templates/webapps/galaxy/page/index.mako +++ b/templates/webapps/galaxy/page/index.mako @@ -30,7 +30,7 @@ <% page = association.page %><tr><td> - <a class="menubutton" id="shared-${i}-popup" href="${h.url_for(controller='page', action='display_by_username_and_slug', username=page.user.username, slug=page.slug)}">${page.title}</a> + <a class="menubutton" id="shared-${i}-popup" href="${h.url_for(controller='page', action='display_by_username_and_slug', username=page.user.username, slug=page.slug)}">${page.title | h}</a></td><td>${page.user.username}</td><td> https://bitbucket.org/galaxy/galaxy-central/commits/0c8ac7330cdd/ Changeset: 0c8ac7330cdd Branch: stable User: dannon Date: 2014-12-08 16:44:52+00:00 Summary: Merged in carlfeberhard/carlfeberhard-galaxy-central-stable/stable (pull request #594) [STABLE] Next-stable security fixes to stable. Affected #: 26 files diff -r 6619aac23f9bfe8d5ef59dc188359044e2ae25f7 -r 0c8ac7330cdd467e9c7ef6f02778dca07a577b79 client/galaxy/scripts/mvc/annotations.js --- a/client/galaxy/scripts/mvc/annotations.js +++ b/client/galaxy/scripts/mvc/annotations.js @@ -56,7 +56,7 @@ '<label class="prompt">', _l( 'Annotation' ), '</label>', // set up initial tags by adding as CSV to input vals (necc. to init select2) '<div class="annotation" title="', _l( 'Edit annotation' ), '">', - annotation, + _.escape( annotation ), '</div>' ].join( '' ); }, diff -r 6619aac23f9bfe8d5ef59dc188359044e2ae25f7 -r 0c8ac7330cdd467e9c7ef6f02778dca07a577b79 client/galaxy/scripts/mvc/tags.js --- a/client/galaxy/scripts/mvc/tags.js +++ b/client/galaxy/scripts/mvc/tags.js @@ -56,7 +56,9 @@ if( !_.isArray( tagsArray ) || _.isEmpty( tagsArray ) ){ return ''; } - return tagsArray.sort().join( ',' ); + return tagsArray.map( function( tag ){ + return _.escape( tag ); + }).sort().join( ',' ); }, /** @returns {jQuery} the input for this view */ diff -r 6619aac23f9bfe8d5ef59dc188359044e2ae25f7 -r 0c8ac7330cdd467e9c7ef6f02778dca07a577b79 lib/galaxy/tags/tag_handler.py --- a/lib/galaxy/tags/tag_handler.py +++ b/lib/galaxy/tags/tag_handler.py @@ -1,5 +1,7 @@ -import re, logging -from sqlalchemy.sql.expression import func, and_ +import re +import logging +from sqlalchemy.sql.expression import func +from sqlalchemy.sql.expression import and_ from sqlalchemy.sql import select log = logging.getLogger( __name__ ) @@ -25,12 +27,15 @@ self.key_value_separators = "=:" # Initialize with known classes - add to this in subclasses. self.item_tag_assoc_info = {} + def get_tag_assoc_class( self, item_class ): """Returns tag association class for item class.""" return self.item_tag_assoc_info[item_class.__name__].tag_assoc_class + def get_id_col_in_item_tag_assoc_table( self, item_class ): """Returns item id column in class' item-tag association table.""" return self.item_tag_assoc_info[item_class.__name__].item_id_col + def get_community_tags( self, trans, item=None, limit=None ): """Returns community tags for an item.""" # Get item-tag association class. @@ -58,6 +63,7 @@ tag_id = row[0] community_tags.append( self.get_tag_by_id( trans, tag_id ) ) return community_tags + def get_tool_tags( self, trans ): result_set = trans.sa_session.execute( select( columns=[ trans.app.model.ToolTagAssociation.table.c.tag_id ], from_obj=trans.app.model.ToolTagAssociation.table ).distinct() ) @@ -67,6 +73,7 @@ tag_id = row[0] tags.append( self.get_tag_by_id( trans, tag_id ) ) return tags + def remove_item_tag( self, trans, user, item, tag_name ): """Remove a tag from an item.""" # Get item tag association. @@ -78,6 +85,7 @@ item.tags.remove( item_tag_assoc ) return True return False + def delete_item_tags( self, trans, user, item ): """Delete tags from an item.""" # Delete item-tag associations. @@ -85,6 +93,7 @@ trans.sa_session.delete( tag ) # Delete tags from item. del item.tags[:] + def item_has_tag( self, trans, user, item, tag ): """Returns true if item is has a given tag.""" # Get tag name. @@ -97,6 +106,7 @@ if item_tag_assoc: return True return False + def apply_item_tag( self, trans, user, item, name, value=None ): # Use lowercase name for searching/creating tag. lc_name = name.lower() @@ -124,6 +134,7 @@ item_tag_assoc.user_value = value item_tag_assoc.value = lc_value return item_tag_assoc + def apply_item_tags( self, trans, user, item, tags_str ): """Apply tags to an item.""" # Parse tags. @@ -131,6 +142,7 @@ # Apply each tag. for name, value in parsed_tags.items(): self.apply_item_tag( trans, user, item, name, value ) + def get_tags_str( self, tags ): """Build a string from an item's tags.""" # Return empty string if there are no tags. @@ -144,14 +156,17 @@ tag_str += ":" + tag.user_value tags_str_list.append( tag_str ) return ", ".join( tags_str_list ) + def get_tag_by_id( self, trans, tag_id ): """Get a Tag object from a tag id.""" return trans.sa_session.query( trans.app.model.Tag ).filter_by( id=tag_id ).first() + def get_tag_by_name( self, trans, tag_name ): """Get a Tag object from a tag name (string).""" if tag_name: return trans.sa_session.query( trans.app.model.Tag ).filter_by( name=tag_name.lower() ).first() return None + def _create_tag( self, trans, tag_str ): """Create a Tag object from a tag string.""" tag_hierarchy = tag_str.split( self.hierarchy_separator ) @@ -169,6 +184,7 @@ parent_tag = tag tag_prefix = tag.name + self.hierarchy_separator return tag + def _get_or_create_tag( self, trans, tag_str ): """Get or create a Tag object from a tag string.""" # Scrub tag; if tag is None after being scrubbed, return None. @@ -181,6 +197,7 @@ if tag is None: tag = self._create_tag( trans, scrubbed_tag_str ) return tag + def _get_item_tag_assoc( self, user, item, tag_name ): """ Return ItemTagAssociation object for a user, item, and tag string; returns None if there is @@ -191,6 +208,7 @@ if ( item_tag_assoc.user == user ) and ( item_tag_assoc.user_tname == scrubbed_tag_name ): return item_tag_assoc return None + def parse_tags( self, tag_str ): """ Returns a list of raw (tag-name, value) pairs derived from a string; method scrubs tag names and values as well. @@ -210,6 +228,7 @@ scrubbed_value = self._scrub_tag_value( nv_pair[1] ) name_value_pairs[scrubbed_name] = scrubbed_value return name_value_pairs + def _scrub_tag_value( self, value ): """Scrub a tag value.""" # Gracefully handle None: @@ -219,6 +238,7 @@ reg_exp = re.compile( '\s' ) scrubbed_value = re.sub( reg_exp, "", value ) return scrubbed_value + def _scrub_tag_name( self, name ): """Scrub a tag name.""" # Gracefully handle None: @@ -234,12 +254,14 @@ if len( scrubbed_name ) < self.min_tag_len or len( scrubbed_name ) > self.max_tag_len: return None return scrubbed_name + def _scrub_tag_name_list( self, tag_name_list ): """Scrub a tag name list.""" scrubbed_tag_list = list() for tag in tag_name_list: scrubbed_tag_list.append( self._scrub_tag_name( tag ) ) return scrubbed_tag_list + def _get_name_value_pair( self, tag_str ): """Get name, value pair from a tag string.""" # Use regular expression to parse name, value. @@ -250,6 +272,7 @@ name_value_pair.append( None ) return name_value_pair + class GalaxyTagHandler( TagHandler ): def __init__( self ): from galaxy import model @@ -271,6 +294,7 @@ model.VisualizationTagAssociation, model.VisualizationTagAssociation.table.c.visualization_id ) + class CommunityTagHandler( TagHandler ): def __init__( self ): from galaxy.webapps.tool_shed import model diff -r 6619aac23f9bfe8d5ef59dc188359044e2ae25f7 -r 0c8ac7330cdd467e9c7ef6f02778dca07a577b79 lib/galaxy/util/json.py --- a/lib/galaxy/util/json.py +++ b/lib/galaxy/util/json.py @@ -57,7 +57,7 @@ return val -def safe_dumps(*args, **kwargs): +def safe_dumps( *args, **kwargs ): """ This is a wrapper around dumps that encodes Infinity and NaN values. It's a fairly rare case (which will be low in request volume). Basically, we tell @@ -65,10 +65,12 @@ re-encoding. """ try: - dumped = json.dumps(*args, allow_nan=False, **kwargs) + dumped = json.dumps( *args, allow_nan=False, **kwargs ) except ValueError: - obj = swap_inf_nan(copy.deepcopy(args[0])) - dumped = json.dumps(obj, allow_nan=False, **kwargs) + obj = swap_inf_nan( copy.deepcopy( args[0] ) ) + dumped = json.dumps( obj, allow_nan=False, **kwargs ) + if kwargs.get( 'escape_closing_tags', True ): + return dumped.replace( '</', '<\\/' ) return dumped diff -r 6619aac23f9bfe8d5ef59dc188359044e2ae25f7 -r 0c8ac7330cdd467e9c7ef6f02778dca07a577b79 lib/galaxy/util/validation.py --- a/lib/galaxy/util/validation.py +++ b/lib/galaxy/util/validation.py @@ -8,8 +8,8 @@ def validate_and_sanitize_basestring( key, val ): if not isinstance( val, basestring ): - raise exceptions.RequestParameterInvalidException( '%s must be a string or unicode: %s' - % ( key, str( type( val ) ) ) ) + raise exceptions.RequestParameterInvalidException( '%s must be a string or unicode: %s' + % ( key, str( type( val ) ) ) ) return unicode( sanitize_html( val, 'utf-8', 'text/html' ), 'utf-8' ) diff -r 6619aac23f9bfe8d5ef59dc188359044e2ae25f7 -r 0c8ac7330cdd467e9c7ef6f02778dca07a577b79 lib/galaxy/web/framework/helpers/__init__.py --- a/lib/galaxy/web/framework/helpers/__init__.py +++ b/lib/galaxy/web/framework/helpers/__init__.py @@ -111,4 +111,3 @@ Returns true if input is a boolean and true or is a string and looks like a true value. """ return val == True or val in [ 'True', 'true', 'T', 't' ] - diff -r 6619aac23f9bfe8d5ef59dc188359044e2ae25f7 -r 0c8ac7330cdd467e9c7ef6f02778dca07a577b79 lib/galaxy/webapps/galaxy/controllers/tag.py --- a/lib/galaxy/webapps/galaxy/controllers/tag.py +++ b/lib/galaxy/webapps/galaxy/controllers/tag.py @@ -64,7 +64,7 @@ trans.log_action( user, unicode( "untag" ), context, params ) # Retag an item. All previous tags are deleted and new tags are applied. - #@web.expose + @web.expose @web.require_login( "Apply a new set of tags to an item; previous tags are deleted." ) def retag_async( self, trans, item_id=None, item_class=None, new_tags=None ): """ @@ -73,7 +73,7 @@ # Apply tags. item = self._get_item( trans, item_class, trans.security.decode_id( item_id ) ) user = trans.user - self.get_tag_handler( trans ).delete_item_tags( trans, item ) + self.get_tag_handler( trans ).delete_item_tags( trans, user, item ) self.get_tag_handler( trans ).apply_item_tags( trans, user, item, new_tags.encode( 'utf-8' ) ) trans.sa_session.flush() diff -r 6619aac23f9bfe8d5ef59dc188359044e2ae25f7 -r 0c8ac7330cdd467e9c7ef6f02778dca07a577b79 static/scripts/mvc/annotations.js --- a/static/scripts/mvc/annotations.js +++ b/static/scripts/mvc/annotations.js @@ -56,7 +56,7 @@ '<label class="prompt">', _l( 'Annotation' ), '</label>', // set up initial tags by adding as CSV to input vals (necc. to init select2) '<div class="annotation" title="', _l( 'Edit annotation' ), '">', - annotation, + _.escape( annotation ), '</div>' ].join( '' ); }, diff -r 6619aac23f9bfe8d5ef59dc188359044e2ae25f7 -r 0c8ac7330cdd467e9c7ef6f02778dca07a577b79 static/scripts/mvc/tags.js --- a/static/scripts/mvc/tags.js +++ b/static/scripts/mvc/tags.js @@ -56,7 +56,9 @@ if( !_.isArray( tagsArray ) || _.isEmpty( tagsArray ) ){ return ''; } - return tagsArray.sort().join( ',' ); + return tagsArray.map( function( tag ){ + return _.escape( tag ); + }).sort().join( ',' ); }, /** @returns {jQuery} the input for this view */ diff -r 6619aac23f9bfe8d5ef59dc188359044e2ae25f7 -r 0c8ac7330cdd467e9c7ef6f02778dca07a577b79 static/scripts/packed/mvc/annotations.js --- a/static/scripts/packed/mvc/annotations.js +++ b/static/scripts/packed/mvc/annotations.js @@ -1,1 +1,1 @@ -define(["mvc/base-mvc","utils/localization"],function(a,c){var b=Backbone.View.extend(a.LoggableMixin).extend(a.HiddenUntilActivatedViewMixin).extend({tagName:"div",className:"annotation-display",initialize:function(d){d=d||{};this.tooltipConfig=d.tooltipConfig||{placement:"bottom"};this.listenTo(this.model,"change:annotation",function(){this.render()});this.hiddenUntilActivated(d.$activator,d)},render:function(){var d=this;this.$el.html(this._template());this.$el.find("[title]").tooltip(this.tooltipConfig);this.$annotation().make_text_editable({use_textarea:true,on_finish:function(e){d.$annotation().text(e);d.model.save({annotation:e},{silent:true}).fail(function(){d.$annotation().text(d.model.previous("annotation"))})}});return this},_template:function(){var d=this.model.get("annotation");return['<label class="prompt">',c("Annotation"),"</label>",'<div class="annotation" title="',c("Edit annotation"),'">',d,"</div>"].join("")},$annotation:function(){return this.$el.find(".annotation")},remove:function(){this.$annotation.off();this.stopListening(this.model);Backbone.View.prototype.remove.call(this)},toString:function(){return["AnnotationEditor(",this.model+"",")"].join("")}});return{AnnotationEditor:b}}); \ No newline at end of file +define(["mvc/base-mvc","utils/localization"],function(a,c){var b=Backbone.View.extend(a.LoggableMixin).extend(a.HiddenUntilActivatedViewMixin).extend({tagName:"div",className:"annotation-display",initialize:function(d){d=d||{};this.tooltipConfig=d.tooltipConfig||{placement:"bottom"};this.listenTo(this.model,"change:annotation",function(){this.render()});this.hiddenUntilActivated(d.$activator,d)},render:function(){var d=this;this.$el.html(this._template());this.$el.find("[title]").tooltip(this.tooltipConfig);this.$annotation().make_text_editable({use_textarea:true,on_finish:function(e){d.$annotation().text(e);d.model.save({annotation:e},{silent:true}).fail(function(){d.$annotation().text(d.model.previous("annotation"))})}});return this},_template:function(){var d=this.model.get("annotation");return['<label class="prompt">',c("Annotation"),"</label>",'<div class="annotation" title="',c("Edit annotation"),'">',_.escape(d),"</div>"].join("")},$annotation:function(){return this.$el.find(".annotation")},remove:function(){this.$annotation.off();this.stopListening(this.model);Backbone.View.prototype.remove.call(this)},toString:function(){return["AnnotationEditor(",this.model+"",")"].join("")}});return{AnnotationEditor:b}}); \ No newline at end of file diff -r 6619aac23f9bfe8d5ef59dc188359044e2ae25f7 -r 0c8ac7330cdd467e9c7ef6f02778dca07a577b79 static/scripts/packed/mvc/tags.js --- a/static/scripts/packed/mvc/tags.js +++ b/static/scripts/packed/mvc/tags.js @@ -1,1 +1,1 @@ -define(["mvc/base-mvc","utils/localization"],function(a,b){var c=Backbone.View.extend(a.LoggableMixin).extend(a.HiddenUntilActivatedViewMixin).extend({tagName:"div",className:"tags-display",initialize:function(d){this.listenTo(this.model,"change:tags",function(){this.render()});this.hiddenUntilActivated(d.$activator,d)},render:function(){var d=this;this.$el.html(this._template());this.$input().select2({placeholder:"Add tags",width:"100%",tags:function(){return d._getTagsUsed()}});this._setUpBehaviors();return this},_template:function(){return['<label class="prompt">',b("Tags"),"</label>",'<input class="tags-input" value="',this.tagsToCSV(),'" />'].join("")},tagsToCSV:function(){var d=this.model.get("tags");if(!_.isArray(d)||_.isEmpty(d)){return""}return d.sort().join(",")},$input:function(){return this.$el.find("input.tags-input")},_getTagsUsed:function(){return Galaxy.currUser.get("tags_used")},_setUpBehaviors:function(){var d=this;this.$input().on("change",function(e){d.model.save({tags:e.val},{silent:true});if(e.added){d._addNewTagToTagsUsed(e.added.text+"")}})},_addNewTagToTagsUsed:function(d){var e=Galaxy.currUser.get("tags_used");if(!_.contains(e,d)){e.push(d);e.sort();Galaxy.currUser.set("tags_used",e)}},remove:function(){this.$input.off();this.stopListening(this.model);Backbone.View.prototype.remove.call(this)},toString:function(){return["TagsEditor(",this.model+"",")"].join("")}});return{TagsEditor:c}}); \ No newline at end of file +define(["mvc/base-mvc","utils/localization"],function(a,b){var c=Backbone.View.extend(a.LoggableMixin).extend(a.HiddenUntilActivatedViewMixin).extend({tagName:"div",className:"tags-display",initialize:function(d){this.listenTo(this.model,"change:tags",function(){this.render()});this.hiddenUntilActivated(d.$activator,d)},render:function(){var d=this;this.$el.html(this._template());this.$input().select2({placeholder:"Add tags",width:"100%",tags:function(){return d._getTagsUsed()}});this._setUpBehaviors();return this},_template:function(){return['<label class="prompt">',b("Tags"),"</label>",'<input class="tags-input" value="',this.tagsToCSV(),'" />'].join("")},tagsToCSV:function(){var d=this.model.get("tags");if(!_.isArray(d)||_.isEmpty(d)){return""}return d.map(function(e){return _.escape(e)}).sort().join(",")},$input:function(){return this.$el.find("input.tags-input")},_getTagsUsed:function(){return Galaxy.currUser.get("tags_used")},_setUpBehaviors:function(){var d=this;this.$input().on("change",function(e){d.model.save({tags:e.val},{silent:true});if(e.added){d._addNewTagToTagsUsed(e.added.text+"")}})},_addNewTagToTagsUsed:function(d){var e=Galaxy.currUser.get("tags_used");if(!_.contains(e,d)){e.push(d);e.sort();Galaxy.currUser.set("tags_used",e)}},remove:function(){this.$input.off();this.stopListening(this.model);Backbone.View.prototype.remove.call(this)},toString:function(){return["TagsEditor(",this.model+"",")"].join("")}});return{TagsEditor:c}}); \ No newline at end of file diff -r 6619aac23f9bfe8d5ef59dc188359044e2ae25f7 -r 0c8ac7330cdd467e9c7ef6f02778dca07a577b79 templates/galaxy_client_app.mako --- a/templates/galaxy_client_app.mako +++ b/templates/galaxy_client_app.mako @@ -15,7 +15,7 @@ //TODO: global... %for key in kwargs: ( window.bootstrapped = window.bootstrapped || {} )[ '${key}' ] = ( - ${ h.dumps( kwargs[ key ], indent=( 2 if trans.debug else 0 ) )} ); + ${ h.dumps( kwargs[ key ], indent=( 2 if trans.debug else 0 ) ) } ); %endfor define( 'bootstrapped-data', function(){ return window.bootstrapped; @@ -76,11 +76,17 @@ user_dict = trans.user.to_dict( view='element', value_mapper={ 'id': trans.security.encode_id, 'total_disk_usage': float } ) user_dict[ 'quota_percent' ] = trans.app.quota_agent.get_percent( trans=trans ) + user_dict[ 'is_admin' ] = trans.user_is_admin() # tags used users_api_controller = trans.webapp.api_controllers[ 'users' ] - user_dict[ 'tags_used' ] = users_api_controller.get_user_tags_used( trans, user=trans.user ) - user_dict[ 'is_admin' ] = trans.user_is_admin() + tags_used = [] + for tag in users_api_controller.get_user_tags_used( trans, user=trans.user ): + tag = tag | h + if tag: + tags_used.append( tag ) + user_dict[ 'tags_used' ] = tags_used + return user_dict usage = 0 diff -r 6619aac23f9bfe8d5ef59dc188359044e2ae25f7 -r 0c8ac7330cdd467e9c7ef6f02778dca07a577b79 templates/tagging_common.mako --- a/templates/tagging_common.mako +++ b/templates/tagging_common.mako @@ -19,7 +19,7 @@ ## Render HTML for a list of tags. <%def name="render_tagging_element_html(elt_id=None, tags=None, editable=True, use_toggle_link=True, input_size='15', in_form=False, tag_type='individual', render_add_tag_button=True)"> ## Useful attributes. - <% + <% num_tags = len( tags ) %><div class="tag-element" @@ -50,6 +50,7 @@ elif isinstance( tag, ItemTagAssociation ): tag_name = tag.user_tname tag_value = tag.user_value + ## Convert tag name, value to unicode. if isinstance( tag_name, str ): tag_name = unicode( escape( tag_name ), 'utf-8' ) @@ -61,7 +62,7 @@ tag_str = tag_name %><span class="tag-button"> - <span class="tag-name">${tag_str}</span> + <span class="tag-name">${tag_str | h}</span> %if editable: <img class="delete-tag-img" src="${h.url_for('/static/images/delete_tag_icon_gray.png')}"/> %endif @@ -186,10 +187,11 @@ ## Build dict of tag name, values. tag_names_and_values = dict() for tag in item_tags: - tag_name = tag.user_tname + tag_name = escape( tag.user_tname ) tag_value = "" if tag.value is not None: - tag_value = tag.user_value + tag_value = escape( tag.user_value ) + ## Tag names and values may be string or unicode object. if isinstance( tag_name, str ): tag_names_and_values[unicode(tag_name, 'utf-8')] = unicode(tag_value, 'utf-8') diff -r 6619aac23f9bfe8d5ef59dc188359044e2ae25f7 -r 0c8ac7330cdd467e9c7ef6f02778dca07a577b79 templates/webapps/galaxy/dataset/copy_view.mako --- a/templates/webapps/galaxy/dataset/copy_view.mako +++ b/templates/webapps/galaxy/dataset/copy_view.mako @@ -53,7 +53,7 @@ %><option value="${trans.security.encode_id(hist.id)}" ${selected}> - ${i + 1}: ${h.truncate(util.unicodify( hist.name ), 30)}${current_history_text} + ${i + 1}: ${h.truncate(util.unicodify( hist.name ), 30) | h}${current_history_text} </option> %endfor </select> @@ -70,7 +70,7 @@ %><div class="form-row"><input type="checkbox" name="source_content_ids" id="${input_id}" value="${input_id}"${checked}/> - <label for="${input_id}" style="display: inline;font-weight:normal;"> ${data.hid}: ${h.to_unicode(data.name)}</label> + <label for="${input_id}" style="display: inline;font-weight:normal;"> ${data.hid}: ${h.to_unicode(data.name) | h}</label></div> %endfor %else: @@ -95,7 +95,7 @@ if encoded_id == target_history_id: selected = " selected='selected'" %> - <option value="${encoded_id}"${selected}>${i + 1}: ${h.truncate( util.unicodify( hist.name ), 30)}${source_history_text}</option> + <option value="${encoded_id}"${selected}>${i + 1}: ${h.truncate( util.unicodify( hist.name ), 30) | h}${source_history_text}</option> %endfor </select><br /><br /><a style="margin-left: 10px;" href="javascript:void(0);" id="select-multiple">Choose multiple histories</a> @@ -110,7 +110,7 @@ %><div class="form-row"><input type="checkbox" name="target_history_ids" id="hist_${encoded_id}" value="${encoded_id}"/> - <label for="hist_${encoded_id}" style="display: inline; font-weight:normal;">${i + 1}: ${ util.unicodify( hist.name ) }${cur_history_text}</label> + <label for="hist_${encoded_id}" style="display: inline; font-weight:normal;">${i + 1}: ${ util.unicodify( hist.name ) | h }${cur_history_text}</label></div> %endfor </div> diff -r 6619aac23f9bfe8d5ef59dc188359044e2ae25f7 -r 0c8ac7330cdd467e9c7ef6f02778dca07a577b79 templates/webapps/galaxy/dataset/display.mako --- a/templates/webapps/galaxy/dataset/display.mako +++ b/templates/webapps/galaxy/dataset/display.mako @@ -26,13 +26,11 @@ data.createTabularDatasetChunkedView({ // TODO: encode id. dataset_config: - _.extend( ${h.dumps( item.to_dict() )}, - { - chunk_url: "${h.url_for( controller='/dataset', action='display', - dataset_id=trans.security.encode_id( item.id ))}", - first_data_chunk: ${first_chunk} - } - ), + _.extend( ${h.dumps( item.to_dict() )}, { + chunk_url: "${h.url_for( controller='/dataset', action='display', + dataset_id=trans.security.encode_id( item.id ))}", + first_data_chunk: ${first_chunk} + }), parent_elt: $('.page-body') }); }); diff -r 6619aac23f9bfe8d5ef59dc188359044e2ae25f7 -r 0c8ac7330cdd467e9c7ef6f02778dca07a577b79 templates/webapps/galaxy/dataset/embed.mako --- a/templates/webapps/galaxy/dataset/embed.mako +++ b/templates/webapps/galaxy/dataset/embed.mako @@ -4,10 +4,13 @@ %><%def name="render_item_links( dataset )"> - <a href="${h.url_for( controller='/dataset', action='display', dataset_id=trans.security.encode_id( dataset.id ), to_ext=dataset.ext )}" title="Save dataset" class="icon-button disk"></a> + <a href="${h.url_for( controller='/dataset', action='display', dataset_id=trans.security.encode_id( dataset.id ), to_ext=dataset.ext )}" + title="Save dataset" class="icon-button disk"></a> ## Links for importing and viewing an item. - <a href="${h.url_for( controller='/dataset', action='imp', dataset_id=trans.security.encode_id( item.id ) )}" title="Import dataset" class="icon-button import"></a> - <a class="icon-button go-to-full-screen" href="${h.url_for( controller='/dataset', action='display_by_username_and_slug', username=dataset.history.user.username, slug=trans.security.encode_id( dataset.id ) )}" title="Go to dataset"></a> + <a href="${h.url_for( controller='/dataset', action='imp', dataset_id=trans.security.encode_id( item.id ) )}" + title="Import dataset" class="icon-button import"></a> + <a href="${h.url_for( controller='/dataset', action='display_by_username_and_slug', username=dataset.history.user.username, slug=trans.security.encode_id( dataset.id ) )}" + title="Go to dataset" class="icon-button go-to-full-screen"></a></%def> diff -r 6619aac23f9bfe8d5ef59dc188359044e2ae25f7 -r 0c8ac7330cdd467e9c7ef6f02778dca07a577b79 templates/webapps/galaxy/dataset/errors.mako --- a/templates/webapps/galaxy/dataset/errors.mako +++ b/templates/webapps/galaxy/dataset/errors.mako @@ -49,7 +49,7 @@ <body><h2>Dataset generation errors</h2> - <p><b>Dataset ${hda.hid}: ${hda.display_name()}</b></p> + <p><b>Dataset ${hda.hid}: ${hda.display_name() | h}</b></p><% job = hda.creating_job %> %if job: diff -r 6619aac23f9bfe8d5ef59dc188359044e2ae25f7 -r 0c8ac7330cdd467e9c7ef6f02778dca07a577b79 templates/webapps/galaxy/dataset/security_common.mako --- a/templates/webapps/galaxy/dataset/security_common.mako +++ b/templates/webapps/galaxy/dataset/security_common.mako @@ -77,7 +77,7 @@ else: current_actions = [] permitted_actions = {}.items() - obj_str = 'unknown object %s' %obj_name + obj_str = 'unknown object %s' % obj_name obj_type = '' %><script type="text/javascript"> @@ -104,7 +104,7 @@ }); </script><div class="toolForm"> - <div class="toolFormTitle">Manage ${obj_type} permissions on ${obj_str}</div> + <div class="toolFormTitle">Manage ${obj_type} permissions on ${obj_str | h}</div><div class="toolFormBody"><form name="edit_role_associations" id="edit_role_associations" action="${form_url}" method="post"><div class="form-row"></div> diff -r 6619aac23f9bfe8d5ef59dc188359044e2ae25f7 -r 0c8ac7330cdd467e9c7ef6f02778dca07a577b79 templates/webapps/galaxy/dataset/tabular_chunked.mako --- a/templates/webapps/galaxy/dataset/tabular_chunked.mako +++ b/templates/webapps/galaxy/dataset/tabular_chunked.mako @@ -15,14 +15,12 @@ } }); - require(['mvc/data'], function(data) { + require([ 'mvc/data' ], function( data ) { data.createTabularDatasetChunkedView({ - dataset_config: _.extend( ${h.dumps( trans.security.encode_dict_ids( dataset.to_dict() ) )}, - { - first_data_chunk: ${chunk} - } - ), - parent_elt: $('body') + dataset_config : _.extend( ${ h.dumps( trans.security.encode_dict_ids( dataset.to_dict() ) )}, { + first_data_chunk: ${ chunk } + }), + parent_elt : $( 'body' ) }); }); </script> diff -r 6619aac23f9bfe8d5ef59dc188359044e2ae25f7 -r 0c8ac7330cdd467e9c7ef6f02778dca07a577b79 templates/webapps/galaxy/galaxy.masthead.mako --- a/templates/webapps/galaxy/galaxy.masthead.mako +++ b/templates/webapps/galaxy/galaxy.masthead.mako @@ -1,32 +1,4 @@ -## get user data -<%def name="get_user_json()"> -<% - """Bootstrapping user API JSON""" - #TODO: move into common location (poss. BaseController) - if trans.user: - user_dict = trans.user.to_dict( view='element', value_mapper={ 'id': trans.security.encode_id, - 'total_disk_usage': float } ) - user_dict[ 'quota_percent' ] = trans.app.quota_agent.get_percent( trans=trans ) - users_api_controller = trans.webapp.api_controllers[ 'users' ] - user_dict[ 'tags_used' ] = users_api_controller.get_user_tags_used( trans, user=trans.user ) - user_dict[ 'is_admin' ] = trans.user_is_admin() - else: - usage = 0 - percent = None - try: - usage = trans.app.quota_agent.get_usage( trans, history=trans.history ) - percent = trans.app.quota_agent.get_percent( trans=trans, usage=usage ) - except AssertionError, assertion: - # no history for quota_agent.get_usage assertion - pass - user_dict = { - 'total_disk_usage' : int( usage ), - 'nice_total_disk_usage' : util.nice_size( usage ), - 'quota_percent' : percent - } - return user_dict -%> -</%def> +<%namespace file="/galaxy_client_app.mako" import="get_user_json" /> ## masthead head generator <%def name="load(active_view = None)"> @@ -87,7 +59,7 @@ ], function( mod_masthead, mod_menu, mod_modal, mod_frame, GalaxyUpload, user, quotameter ){ if( !Galaxy.currUser ){ // this doesn't need to wait for the page being readied - Galaxy.currUser = new user.User(${ h.dumps( get_user_json(), indent=2 ) }); + Galaxy.currUser = new user.User(${ h.dumps( masthead_config[ 'user' ][ 'json' ], indent=2 ) }); } $(function() { diff -r 6619aac23f9bfe8d5ef59dc188359044e2ae25f7 -r 0c8ac7330cdd467e9c7ef6f02778dca07a577b79 templates/webapps/galaxy/history/embed.mako --- a/templates/webapps/galaxy/history/embed.mako +++ b/templates/webapps/galaxy/history/embed.mako @@ -24,7 +24,7 @@ </a></h4> %if hasattr( item, "annotation") and item.annotation: - <div class="annotation">${item.annotation}</div> + <div class="annotation">${item.annotation | h}</div> %endif </div><div class='summary-content'> diff -r 6619aac23f9bfe8d5ef59dc188359044e2ae25f7 -r 0c8ac7330cdd467e9c7ef6f02778dca07a577b79 templates/webapps/galaxy/history/history_panel.mako --- a/templates/webapps/galaxy/history/history_panel.mako +++ /dev/null @@ -1,14 +0,0 @@ -## shortcuts for script tags that create history panels -## ---------------------------------------------------------------------------- -<%def name="current_history_panel( selector_to_attach_to=None, options )"> -</%def> - - -## ---------------------------------------------------------------------------- -<%def name="history_panel( history_id, selector_to_attach_to=None, options )"> -</%def> - - -## ---------------------------------------------------------------------------- -<%def name="bootstrapped_history_panel( history, hdas, selector_to_attach_to=None, options )"> -</%def> diff -r 6619aac23f9bfe8d5ef59dc188359044e2ae25f7 -r 0c8ac7330cdd467e9c7ef6f02778dca07a577b79 templates/webapps/galaxy/history/share.mako --- a/templates/webapps/galaxy/history/share.mako +++ b/templates/webapps/galaxy/history/share.mako @@ -20,7 +20,7 @@ <tr><td><input type="hidden" name="id" value="${trans.security.encode_id( history.id )}"> - ${ util.unicodify( history.name )} + ${ util.unicodify( history.name ) | h } </td><td> %if len( history.datasets ) < 1: diff -r 6619aac23f9bfe8d5ef59dc188359044e2ae25f7 -r 0c8ac7330cdd467e9c7ef6f02778dca07a577b79 templates/webapps/galaxy/history/view.mako --- a/templates/webapps/galaxy/history/view.mako +++ b/templates/webapps/galaxy/history/view.mako @@ -1,4 +1,4 @@ -<%namespace file="/galaxy.masthead.mako" import="get_user_json" /> +<%namespace file="/galaxy_client_app.mako" import="get_user_json" /> ## ---------------------------------------------------------------------------- <%! @@ -166,9 +166,9 @@ // use_panels effects where the the center_panel() is rendered: // w/o it renders to the body, w/ it renders to #center - we need to adjust a few things for scrolling to work var hasMasthead = ${ 'true' if use_panels else 'false' }, - userIsOwner = ${'true' if user_is_owner else 'false'}, - historyJSON = ${h.dumps( history )}, - hdaJSON = ${h.dumps( hdas )}, + userIsOwner = ${ 'true' if user_is_owner else 'false' }, + historyJSON = ${ h.dumps( history ) }, + hdaJSON = ${ h.dumps( hdas ) }, panelToUse = ( userIsOwner )? //TODO: change class names ({ location: 'mvc/history/history-panel-edit', className: 'HistoryPanelEdit' }): diff -r 6619aac23f9bfe8d5ef59dc188359044e2ae25f7 -r 0c8ac7330cdd467e9c7ef6f02778dca07a577b79 templates/webapps/galaxy/page/editor.mako --- a/templates/webapps/galaxy/page/editor.mako +++ b/templates/webapps/galaxy/page/editor.mako @@ -47,7 +47,7 @@ <a id="close-button" class="panel-header-button">Close</a></div><div class="unified-panel-header-inner"> - Page Editor <span style="font-weight: normal">| Title : ${page.title}</span> + Page Editor <span style="font-weight: normal">| Title : ${page.title | h}</span></div></div> diff -r 6619aac23f9bfe8d5ef59dc188359044e2ae25f7 -r 0c8ac7330cdd467e9c7ef6f02778dca07a577b79 templates/webapps/galaxy/page/index.mako --- a/templates/webapps/galaxy/page/index.mako +++ b/templates/webapps/galaxy/page/index.mako @@ -30,7 +30,7 @@ <% page = association.page %><tr><td> - <a class="menubutton" id="shared-${i}-popup" href="${h.url_for(controller='page', action='display_by_username_and_slug', username=page.user.username, slug=page.slug)}">${page.title}</a> + <a class="menubutton" id="shared-${i}-popup" href="${h.url_for(controller='page', action='display_by_username_and_slug', username=page.user.username, slug=page.slug)}">${page.title | h}</a></td><td>${page.user.username}</td><td> 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.