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

This commit is contained in:
Carl Eberhard
2014-11-25 14:22:08 -05:00
parent 53cca8f8f1
commit 1f51b4abeb
8 changed files with 53 additions and 48 deletions
+26 -2
View File
@@ -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 @@ class TagHandler( object ):
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 @@ class TagHandler( object ):
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 @@ class TagHandler( object ):
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 @@ class TagHandler( object ):
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 @@ class TagHandler( object ):
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 @@ class TagHandler( object ):
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 @@ class TagHandler( object ):
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 @@ class TagHandler( object ):
# 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 @@ class TagHandler( object ):
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 @@ class TagHandler( object ):
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 @@ class TagHandler( object ):
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 @@ class TagHandler( object ):
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 @@ class TagHandler( object ):
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 @@ class TagHandler( object ):
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 @@ class TagHandler( object ):
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 @@ class TagHandler( object ):
name_value_pair.append( None )
return name_value_pair
class GalaxyTagHandler( TagHandler ):
def __init__( self ):
from galaxy import model
@@ -271,6 +294,7 @@ class GalaxyTagHandler( TagHandler ):
model.VisualizationTagAssociation,
model.VisualizationTagAssociation.table.c.visualization_id )
class CommunityTagHandler( TagHandler ):
def __init__( self ):
from galaxy.webapps.tool_shed import model
+6 -4
View File
@@ -57,7 +57,7 @@ def swap_inf_nan( val ):
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 @@ def safe_dumps(*args, **kwargs):
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
+2 -2
View File
@@ -8,8 +8,8 @@ from galaxy.util.sanitize_html import sanitize_html
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' )
@@ -111,4 +111,3 @@ def is_true ( val ):
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' ]
+2 -2
View File
@@ -64,7 +64,7 @@ class TagsController ( BaseUIController, UsesTagsMixin ):
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 @@ class TagsController ( BaseUIController, UsesTagsMixin ):
# 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()
+9 -3
View File
@@ -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 @@ ${ h.dumps( get_config_dict() )}
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
+6 -4
View File
@@ -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')
+2 -30
View File
@@ -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() {