Howdy devs,

I've implemented some rather basic tool access control and am looking
for feedback on my implementation.

# Why

Our organisation wanted the ability to restrict tools to different
users/roles. As such I've implemented as an "execute" tag which can be
applied to either <section> or <tools> in the tool configuration file.

# Example galaxy-admin changes

For example:

  <section execute="a...@b.co,b...@b.co" id="EncodeTools" name="ENCODE Tools">
    <tool file="encode/gencode_partition.xml" />
    <tool execute="b...@b.co" file="encode/random_intervals.xml" />
  </section>

which would allow A and B to access gencode_parition, but only B would
be able to access random_intervals. To put it explicity

- by default, everyone can access all tools
- if section level permissions are set, then those are set as defaults
for all tools in that section
- if tool permissions are set, they will override the defaults.

# Pros and Cons

There are some good features

- non-accessible tools won't show up in the left hand panel, based on user
- non-accessible tools cannot be run or accessed.

There are some caveats however.

- existence of tools is not completely hidden.
- Labels are not hidden at all.
- workflows break completely if a tool is unavailable to a shared user
and the user copies+edits. They can be copied, and viewed (says tool not
found), but cannot be edited.

Tool names/id/version info can be found in the javascript object due to
the call to app.toolbox.tool_panel.items() in
templates/webapps/galaxy/workflow/editor.mako, as that returns the raw
tool list, rather than one that's filtered on whether or not the user
has access. I'm yet to figure out a clean fix for this. Additionally,
empty sections are still shown even if there aren't tools listed in them.

For a brief overview of my changes, please see the attached diff. (It's
missing one change because I wasn't being careful and started work on
multiple different features)

# Changeset overview

In brief, most of the changes consist of
- new method in model.User to check if an array of roles overlaps at all
with a user's roles
- modifications to appropriate files for reading in the new
tool_config.xml's options
- modification to get_tool to pass user information, as whether or not a
tool exists is now dependent on who is asking.

Please let me know if you have input on this before I create a pull
request on this feature.

# Fixes

I believe this will fix a number of previously brought up issues (at
least to my understanding of the issues listed)

+ https://trello.com/c/Zo7FAXlM/286-24-add-ability-to-password-secure-tools
+ (I saw some solution where they were adding "_beta" to tool names
which gave permissions to developers somewhere, but cannot find that now)



Cheers,
Eric Rasche

-- 
Eric Rasche
Programmer II
Center for Phage Technology
Texas A&M University
College Station, TX 77843
404-692-2048
e...@tamu.edu
rasche.e...@yandex.ru
diff -r c458a0fe1ba8 lib/galaxy/model/__init__.py
--- a/lib/galaxy/model/__init__.py	Mon Nov 04 14:56:57 2013 -0500
+++ b/lib/galaxy/model/__init__.py	Wed Nov 06 11:18:10 2013 -0600
@@ -114,6 +114,19 @@
                     roles.append( role )
         return roles
 
+    def can_execute( self, permissions=None ):
+        """
+        Check if any of a user's roles overlap with set permissions
+        """
+        # If permissions variable is NOT set, then allow access (be friendly mode)
+        if permissions is None:
+            return True
+        # Otherwise, we want to check and deny if they're not in the set
+        for role in self.all_roles():
+            if role.name in permissions:
+                return True
+        return False
+
     def get_disk_usage( self, nice_size=False ):
         """
         Return byte count of disk space used by user or a human-readable
diff -r c458a0fe1ba8 lib/galaxy/tools/__init__.py
--- a/lib/galaxy/tools/__init__.py	Mon Nov 04 14:56:57 2013 -0500
+++ b/lib/galaxy/tools/__init__.py	Wed Nov 06 11:18:10 2013 -0600
@@ -195,12 +195,19 @@
             self.index += 1
             if parsing_shed_tool_conf:
                 config_elems.append( elem )
+            permissions = None
+            try:
+                permissions = elem.get('execute').split(',')
+                log.debug('Execute section found: %s' % (':'.join(permissions)))
+            except:
+                log.debug("No execute section found")
+                pass
             if elem.tag == 'tool':
-                self.load_tool_tag_set( elem, self.tool_panel, self.integrated_tool_panel, tool_path, load_panel_dict, guid=elem.get( 'guid' ), index=index )
+                self.load_tool_tag_set( elem, self.tool_panel, self.integrated_tool_panel, tool_path, load_panel_dict, guid=elem.get( 'guid' ), index=index, permissions=permissions )
             elif elem.tag == 'workflow':
                 self.load_workflow_tag_set( elem, self.tool_panel, self.integrated_tool_panel, load_panel_dict, index=index )
             elif elem.tag == 'section':
-                self.load_section_tag_set( elem, tool_path, load_panel_dict, index=index )
+                self.load_section_tag_set( elem, tool_path, load_panel_dict, index=index, permissions=permissions )
             elif elem.tag == 'label':
                 self.load_label_tag_set( elem, self.tool_panel, self.integrated_tool_panel, load_panel_dict, index=index )
         if parsing_shed_tool_conf:
@@ -365,11 +372,16 @@
         shutil.move( filename, os.path.abspath( self.integrated_tool_panel_config ) )
         os.chmod( self.integrated_tool_panel_config, 0644 )
 
-    def get_tool( self, tool_id, tool_version=None, get_all_versions=False ):
+    def get_tool( self, tool_id, tool_version=None, get_all_versions=False, user=None):
         """Attempt to locate a tool in the tool box."""
+    	log.debug("Getting tool %s" % (tool_id))
         if tool_id in self.tools_by_id and not get_all_versions:
             #tool_id exactly matches an available tool by id (which is 'old' tool_id or guid)
-            return self.tools_by_id[ tool_id ]
+            tool = self.tools_by_id[ tool_id ]
+            if user is not None and user.can_execute(tool.permissions):
+                return self.tools_by_id[ tool_id ]
+            else:
+                return None
         #exact tool id match not found, or all versions requested, search for other options, e.g. migrated tools or different versions
         rval = []
         tv = self.__get_tool_version( tool_id )
@@ -377,12 +389,15 @@
             tool_version_ids = tv.get_version_ids( self.app )
             for tool_version_id in tool_version_ids:
                 if tool_version_id in self.tools_by_id:
-                    rval.append( self.tools_by_id[ tool_version_id ] )
+                    tool = self.tools_by_id[ tool_version_id ]
+                    if user is not None and user.can_execute(tool.permissions):
+                        rval.append( tool )
         if not rval:
             #still no tool, do a deeper search and try to match by old ids
             for tool in self.tools_by_id.itervalues():
                 if tool.old_id == tool_id:
-                    rval.append( tool )
+                    if user is not None and user.can_execute(tool.permissions):
+                        rval.append( tool )
         if rval:
             if get_all_versions:
                 return rval
@@ -390,7 +405,7 @@
                 if tool_version:
                     #return first tool with matching version
                     for tool in rval:
-                        if tool.version == tool_version:
+                        if tool.version == tool_version and user is not None and user.can_execute(tool.permissions):
                             return tool
                 #No tool matches by version, simply return the first available tool found
                 return rval[0]
@@ -464,7 +479,7 @@
                 select_field.add_option( 'version %s' % option_tup[0], option_tup[1] )
         return select_field
 
-    def load_tool_tag_set( self, elem, panel_dict, integrated_panel_dict, tool_path, load_panel_dict, guid=None, index=None ):
+    def load_tool_tag_set( self, elem, panel_dict, integrated_panel_dict, tool_path, load_panel_dict, guid=None, index=None, permissions=None ):
         try:
             path = elem.get( "file" )
             repository_id = None
@@ -492,6 +507,16 @@
                     # a new repository, so any included tools can be loaded into the tool panel.
                     can_load_into_panel_dict = True
             tool = self.load_tool( os.path.join( tool_path, path ), guid=guid, repository_id=repository_id )
+            try:
+	        # If we have special permissions on this element, use those
+		tool.permissions = elem.get("execute").split(',')
+            except:
+                # Otherwise, we'll use the parent's permissions
+                tool.permissions = permissions
+            if tool.permissions is not None:
+                log.debug("Loaded tool %s with permissions %s" % ( tool.id, ','.join(tool.permissions)))
+	    else:
+                log.debug("Loaded tool %s" % (tool.id))
             key = 'tool_%s' % str( tool.id )
             if can_load_into_panel_dict:
                 if guid is not None:
@@ -572,7 +597,7 @@
         else:
             integrated_panel_dict.insert( index, key, label )
 
-    def load_section_tag_set( self, elem, tool_path, load_panel_dict, index=None ):
+    def load_section_tag_set( self, elem, tool_path, load_panel_dict, index=None, permissions=None ):
         key = 'section_' + elem.get( "id" )
         if key in self.tool_panel:
             section = self.tool_panel[ key ]
@@ -588,11 +613,13 @@
             integrated_elems = integrated_section.elems
         for sub_index, sub_elem in enumerate( elem ):
             if sub_elem.tag == 'tool':
-                self.load_tool_tag_set( sub_elem, elems, integrated_elems, tool_path, load_panel_dict, guid=sub_elem.get( 'guid' ), index=sub_index )
+                self.load_tool_tag_set( sub_elem, elems, integrated_elems, tool_path, load_panel_dict, guid=sub_elem.get( 'guid' ), index=sub_index, permissions=permissions )
             elif sub_elem.tag == 'workflow':
                 self.load_workflow_tag_set( sub_elem, elems, integrated_elems, load_panel_dict, index=sub_index )
             elif sub_elem.tag == 'label':
                 self.load_label_tag_set( sub_elem, elems, integrated_elems, load_panel_dict, index=sub_index )
+        # If we have permissions, store those so we don't display the section as well as hiding everything below
+        section.permissions = permissions
         if load_panel_dict:
             self.tool_panel[ key ] = section
         # Always load sections into the integrated_tool_panel.
@@ -716,7 +743,6 @@
         """
         to_dict toolbox.
         """
-
         context = Bunch( toolbox=self, trans=trans, **kwds )
         if in_panel:
             panel_elts = [ val for val in self.tool_panel.itervalues() ]
@@ -737,11 +763,17 @@
                 link_details = True
             )
             for elt in panel_elts:
-                rval.append( to_dict_helper( elt, kwargs ) )
+		        # If the user is allowed to see it, let them
+        		#if elt.can_user_execute( trans ):
+                if trans.user.can_execute( elt.permissions ):
+                    rval.append( to_dict_helper( elt, kwargs ) )
         else:
             tools = []
             for id, tool in self.tools_by_id.items():
-                tools.append( tool.to_dict( trans, link_details=True ) )
+		        # If the user is allowed to see it, let them
+                #if tool.can_user_execute( trans ):
+                if trans.user.can_execute( tool.permissions ):
+                    tools.append( tool.to_dict( trans, link_details=True ) )
             rval = tools
 
         return rval
@@ -816,6 +848,7 @@
         self.id = f( elem, 'id' )
         self.version = f( elem, 'version' )
         self.elems = odict()
+        self.permissions = None
 
     def copy( self ):
         copy = ToolSection()
@@ -869,6 +902,8 @@
         """
         # Convert parameters to a dictionary of strings, and save curent
         # page in that dict
+        if not tool:
+            return None
         value = params_to_strings( tool.inputs, self.inputs, app )
         value["__page__"] = self.page
         value["__rerun_remap_job_id__"] = self.rerun_remap_job_id
@@ -996,6 +1031,8 @@
         # Parse XML element containing configuration
         self.parse( root, guid=guid )
         self.external_runJob_script = app.config.drmaa_external_runjob_script
+	self.permissions = None
+
     @property
     def sa_session( self ):
         """Returns a SQLAlchemy session"""
@@ -2954,6 +2991,7 @@
         return trans.get_history( create=create )
 
 
+
 class OutputParameterJSONTool( Tool ):
     """
     Alternate implementation of Tool that provides parameters and other values
diff -r c458a0fe1ba8 lib/galaxy/web/base/controller.py
--- a/lib/galaxy/web/base/controller.py	Mon Nov 04 14:56:57 2013 -0500
+++ b/lib/galaxy/web/base/controller.py	Wed Nov 06 11:18:10 2013 -0600
@@ -1140,7 +1140,7 @@
         job = self.get_hda_job( hda )
         if not job:
             return None
-        tool = trans.app.toolbox.get_tool( job.tool_id )
+        tool = trans.app.toolbox.get_tool( job.tool_id, user=trans.user )
         if not tool:
             return None
 
diff -r c458a0fe1ba8 lib/galaxy/webapps/galaxy/api/tools.py
--- a/lib/galaxy/webapps/galaxy/api/tools.py	Mon Nov 04 14:56:57 2013 -0500
+++ b/lib/galaxy/webapps/galaxy/api/tools.py	Wed Nov 06 11:18:10 2013 -0600
@@ -75,7 +75,7 @@
         # -- Execute tool. --
 
         # Get tool.
-        tool = trans.app.toolbox.get_tool( payload[ 'tool_id' ] ) if 'tool_id' in payload else None
+        tool = trans.app.toolbox.get_tool( payload[ 'tool_id' ], user=trans.user ) if 'tool_id' in payload else None
         if not tool:
             trans.response.status = 404
             return { "message": { "type": "error", "text" : trans.app.model.Dataset.conversion_messages.NO_TOOL } }
@@ -167,7 +167,7 @@
         #
         # Execute tool.
         #
-        tool = trans.app.toolbox.get_tool( tool_id )
+        tool = trans.app.toolbox.get_tool( tool_id, trans=trans.user )
         if not tool:
             return trans.app.model.Dataset.conversion_messages.NO_TOOL
 
@@ -242,7 +242,7 @@
         # have priority.
         #
         original_job = self.get_hda_job( original_dataset )
-        tool = trans.app.toolbox.get_tool( original_job.tool_id )
+        tool = trans.app.toolbox.get_tool( original_job.tool_id, user=trans.user )
         if not tool:
             return trans.app.model.Dataset.conversion_messages.NO_TOOL
         tool_params = dict( [ ( p.name, p.value ) for p in original_job.parameters ] )
diff -r c458a0fe1ba8 lib/galaxy/webapps/galaxy/api/workflows.py
--- a/lib/galaxy/webapps/galaxy/api/workflows.py	Mon Nov 04 14:56:57 2013 -0500
+++ b/lib/galaxy/webapps/galaxy/api/workflows.py	Wed Nov 06 11:18:10 2013 -0600
@@ -229,7 +229,7 @@
         for i, step in enumerate( workflow.steps ):
             job = None
             if step.type == 'tool' or step.type is None:
-                tool = self.app.toolbox.get_tool( step.tool_id )
+                tool = self.app.toolbox.get_tool( step.tool_id, user=self.user )
                 def callback( input, value, prefixed_name, prefixed_label ):
                     if isinstance( input, DataToolParameter ):
                         if prefixed_name in step.input_connections_by_name:
diff -r c458a0fe1ba8 lib/galaxy/webapps/galaxy/controllers/async.py
--- a/lib/galaxy/webapps/galaxy/controllers/async.py	Mon Nov 04 14:56:57 2013 -0500
+++ b/lib/galaxy/webapps/galaxy/controllers/async.py	Wed Nov 06 11:18:10 2013 -0600
@@ -42,7 +42,7 @@
 
         # initialize the tool
         toolbox = self.get_toolbox()
-        tool = toolbox.get_tool( tool_id )
+        tool = toolbox.get_tool( tool_id, user=trans.user )
         if not tool:
             return "Tool with id %s not found" % tool_id
 
diff -r c458a0fe1ba8 lib/galaxy/webapps/galaxy/controllers/dataset.py
--- a/lib/galaxy/webapps/galaxy/controllers/dataset.py	Mon Nov 04 14:56:57 2013 -0500
+++ b/lib/galaxy/webapps/galaxy/controllers/dataset.py	Wed Nov 06 11:18:10 2013 -0600
@@ -1001,7 +1001,7 @@
                 try:
                     # Load the tool
                     toolbox = self.get_toolbox()
-                    tool = toolbox.get_tool( job.tool_id )
+                    tool = toolbox.get_tool( job.tool_id, user=trans.user )
                     assert tool is not None, 'Requested tool has not been loaded.'
                     #Load parameter objects, if a parameter type has changed, its possible for the value to no longer be valid
                     try:
diff -r c458a0fe1ba8 lib/galaxy/webapps/galaxy/controllers/history.py
--- a/lib/galaxy/webapps/galaxy/controllers/history.py	Mon Nov 04 14:56:57 2013 -0500
+++ b/lib/galaxy/webapps/galaxy/controllers/history.py	Wed Nov 06 11:18:10 2013 -0600
@@ -669,7 +669,7 @@
                     #.add_input( "file", "Archived History File", "archive_file", value=None, error=None )
                                 )
         # Run job to do import.
-        history_imp_tool = trans.app.toolbox.get_tool( '__IMPORT_HISTORY__' )
+        history_imp_tool = trans.app.toolbox.get_tool( '__IMPORT_HISTORY__', user=trans.user )
         incoming = { '__ARCHIVE_SOURCE__' : archive_source, '__ARCHIVE_TYPE__' : archive_type }
         history_imp_tool.execute( trans, incoming=incoming )
         return trans.show_message( "Importing history from '%s'. \
@@ -726,7 +726,7 @@
                         % ( { 'n' : history.name, 's' : url_for( controller='history', action="export_archive", id=id, qualified=True ) } ) )
 
         # Run job to do export.
-        history_exp_tool = trans.app.toolbox.get_tool( '__EXPORT_HISTORY__' )
+        history_exp_tool = trans.app.toolbox.get_tool( '__EXPORT_HISTORY__',user=trans.user )
         params = {
             'history_to_export' : history,
             'compress' : gzip,
diff -r c458a0fe1ba8 lib/galaxy/webapps/galaxy/controllers/library_common.py
--- a/lib/galaxy/webapps/galaxy/controllers/library_common.py	Mon Nov 04 14:56:57 2013 -0500
+++ b/lib/galaxy/webapps/galaxy/controllers/library_common.py	Wed Nov 06 11:18:10 2013 -0600
@@ -1020,7 +1020,7 @@
     def upload_dataset( self, trans, cntrller, library_id, folder_id, replace_dataset=None, **kwd ):
         # Set up the traditional tool state/params
         tool_id = 'upload1'
-        tool = trans.app.toolbox.get_tool( tool_id )
+        tool = trans.app.toolbox.get_tool( tool_id, user=trans.user )
         state = tool.new_state( trans )
         errors = tool.update_state( trans, tool.inputs_by_page[0], state.inputs, kwd )
         tool_params = state.inputs
diff -r c458a0fe1ba8 lib/galaxy/webapps/galaxy/controllers/root.py
--- a/lib/galaxy/webapps/galaxy/controllers/root.py	Mon Nov 04 14:56:57 2013 -0500
+++ b/lib/galaxy/webapps/galaxy/controllers/root.py	Wed Nov 06 11:18:10 2013 -0600
@@ -77,7 +77,7 @@
         """Return help page for tool identified by 'id' if available
         """
         toolbox = self.get_toolbox()
-        tool = toolbox.get_tool( id )
+        tool = toolbox.get_tool( id, user=trans.user )
         yield "<html><body>"
         if not tool:
             #TODO: arent tool ids strings now?
diff -r c458a0fe1ba8 lib/galaxy/webapps/galaxy/controllers/tool_runner.py
--- a/lib/galaxy/webapps/galaxy/controllers/tool_runner.py	Mon Nov 04 14:56:57 2013 -0500
+++ b/lib/galaxy/webapps/galaxy/controllers/tool_runner.py	Wed Nov 06 11:18:10 2013 -0600
@@ -270,7 +270,7 @@
             else:
                 library_bunch = None
             return upload_common.new_upload( trans, cntrller, ud, library_bunch=library_bunch, state=trans.app.model.HistoryDatasetAssociation.states.UPLOAD )
-        tool = self.get_toolbox().get_tool( tool_id )
+        tool = self.get_toolbox().get_tool( tool_id, user=trans.user )
         if not tool:
             return False # bad tool_id
         nonfile_params = galaxy.util.Params( kwd, sanitize=False )
diff -r c458a0fe1ba8 lib/galaxy/webapps/galaxy/controllers/user.py
--- a/lib/galaxy/webapps/galaxy/controllers/user.py	Mon Nov 04 14:56:57 2013 -0500
+++ b/lib/galaxy/webapps/galaxy/controllers/user.py	Wed Nov 06 11:18:10 2013 -0600
@@ -1301,7 +1301,7 @@
                                         filter( self.app.model.History.user==trans.user ). \
                                         order_by( self.app.model.Job.create_time.desc() ).limit(1)
         tool_id = query[0][0] # Get first element in first row of query.
-        tool = self.get_toolbox().get_tool( tool_id )
+        tool = self.get_toolbox().get_tool( tool_id, user=trans.user )
 
         # Return tool info.
         tool_info = {
diff -r c458a0fe1ba8 lib/galaxy/webapps/galaxy/controllers/visualization.py
--- a/lib/galaxy/webapps/galaxy/controllers/visualization.py	Mon Nov 04 14:56:57 2013 -0500
+++ b/lib/galaxy/webapps/galaxy/controllers/visualization.py	Wed Nov 06 11:18:10 2013 -0600
@@ -872,7 +872,7 @@
             }
 
         # Add tool, dataset attributes to config based on id.
-        tool = trans.app.toolbox.get_tool( viz_config[ 'tool_id' ] )
+        tool = trans.app.toolbox.get_tool( viz_config[ 'tool_id' ], user=trans.user )
         viz_config[ 'tool' ] = tool.to_dict( trans, io_details=True )
         viz_config[ 'dataset' ] = trans.security.encode_dict_ids( dataset.to_dict() )
 
diff -r c458a0fe1ba8 lib/galaxy/webapps/galaxy/controllers/workflow.py
--- a/lib/galaxy/webapps/galaxy/controllers/workflow.py	Mon Nov 04 14:56:57 2013 -0500
+++ b/lib/galaxy/webapps/galaxy/controllers/workflow.py	Wed Nov 06 11:18:10 2013 -0600
@@ -1229,7 +1229,7 @@
             for job_id in job_ids:
                 assert job_id in jobs_by_id, "Attempt to create workflow with job not connected to current history"
                 job = jobs_by_id[ job_id ]
-                tool = trans.app.toolbox.get_tool( job.tool_id )
+                tool = trans.app.toolbox.get_tool( job.tool_id, user=trans.user )
                 param_values = job.get_param_values( trans.app, ignore_errors=True ) #If a tool was updated and e.g. had a text value changed to an integer, we don't want a traceback here
                 associations = cleanup_param_values( tool.inputs, param_values )
                 step = model.WorkflowStep()
@@ -1389,7 +1389,7 @@
                             # Execute module
                             job = None
                             if step.type == 'tool' or step.type is None:
-                                tool = trans.app.toolbox.get_tool( step.tool_id )
+                                tool = trans.app.toolbox.get_tool( step.tool_id, user=trans.user )
                                 # Connect up
                                 def callback( input, value, prefixed_name, prefixed_label ):
                                     replacement = None
diff -r c458a0fe1ba8 lib/galaxy/workflow/modules.py
--- a/lib/galaxy/workflow/modules.py	Mon Nov 04 14:56:57 2013 -0500
+++ b/lib/galaxy/workflow/modules.py	Wed Nov 06 11:18:10 2013 -0600
@@ -197,7 +197,7 @@
     def __init__( self, trans, tool_id ):
         self.trans = trans
         self.tool_id = tool_id
-        self.tool = trans.app.toolbox.get_tool( tool_id )
+        self.tool = trans.app.toolbox.get_tool( tool_id, user=trans.user )
         self.post_job_actions = {}
         self.workflow_outputs = []
         self.state = None
@@ -236,7 +236,7 @@
             # TODO: If workflows are ever enhanced to use tool version
             # in addition to tool id, enhance the selection process here
             # to retrieve the correct version of the tool.
-            tool = trans.app.toolbox.get_tool( tool_id )
+            tool = trans.app.toolbox.get_tool( tool_id, user=trans.user )
             if tool:
                 tool_id = tool.id
         if ( trans.app.toolbox and tool_id in trans.app.toolbox.tools_by_id ):
@@ -246,9 +246,10 @@
                 return module_factory.from_dict(trans, from_json_string(step.config), secure=False)
             module = Class( trans, tool_id )
             module.state = galaxy.tools.DefaultToolState()
-            if step.tool_version and (step.tool_version != module.tool.version):
-                module.version_changes.append("%s: using version '%s' instead of version '%s' indicated in this workflow." % (tool_id, module.tool.version, step.tool_version))
-            module.state.inputs = module.tool.params_from_strings( step.tool_inputs, trans.app, ignore_errors=True )
+	    if module.tool is not None:
+                if step.tool_version and (step.tool_version != module.tool.version):
+                    module.version_changes.append("%s: using version '%s' instead of version '%s' indicated in this workflow." % (tool_id, module.tool.version, step.tool_version))
+                module.state.inputs = module.tool.params_from_strings( step.tool_inputs, trans.app, ignore_errors=True )
             module.errors = step.tool_errors
             module.workflow_outputs = step.workflow_outputs
             pjadict = {}
@@ -305,7 +306,7 @@
         return self.errors
 
     def get_tooltip( self, static_path='' ):
-        if self.tool.help:
+        if self.tool and self.tool.help:
             return self.tool.help.render( static_path=static_path )
         else:
             return None
@@ -320,13 +321,17 @@
                     label=prefixed_label,
                     multiple=input.multiple,
                     extensions=input.extensions ) )
-
-        visit_input_values( self.tool.inputs, self.state.inputs, callback )
-        return data_inputs
+        if self.tool:
+            visit_input_values( self.tool.inputs, self.state.inputs, callback )
+            return data_inputs
+        else:
+            return None
 
     def get_data_outputs( self ):
         data_outputs = []
         data_inputs = None
+	if self.tool is None:
+            return None
         for name, tool_output in self.tool.outputs.iteritems():
             if tool_output.format_source != None:
                 formats = [ 'input' ] # default to special name "input" which remove restrictions on connections
@@ -353,8 +358,11 @@
 
     def get_config_form( self ):
         self.add_dummy_datasets()
-        return self.trans.fill_template( "workflow/editor_tool_form.mako",
-            tool=self.tool, values=self.state.inputs, errors=( self.errors or {} ) )
+	if self.tool:
+            return self.trans.fill_template( "workflow/editor_tool_form.mako",
+                tool=self.tool, values=self.state.inputs, errors=( self.errors or {} ) )
+	else:
+            return self.trans.fill_template( "workflow/editor_tool_form.mako" )
 
     def update_state( self, incoming ):
         # Build a callback that handles setting an input to be required at
@@ -389,7 +397,9 @@
         self.errors = errors or None
 
     def check_and_update_state( self ):
-        return self.tool.check_and_update_param_values( self.state.inputs, self.trans, allow_workflow_parameters=True )
+	if self.tool:
+            return self.tool.check_and_update_param_values( self.state.inputs, self.trans, allow_workflow_parameters=True )
+        return None
 
     def add_dummy_datasets( self, connections=None):
         if connections:
@@ -411,7 +421,8 @@
                         replacement = DummyDataset()
             return replacement
 
-        visit_input_values( self.tool.inputs, self.state.inputs, callback )
+        if self.tool is not None:
+            visit_input_values( self.tool.inputs, self.state.inputs, callback )
 
 
 class WorkflowModuleFactory( object ):
diff -r c458a0fe1ba8 templates/webapps/galaxy/workflow/editor_tool_form.mako
--- a/templates/webapps/galaxy/workflow/editor_tool_form.mako	Mon Nov 04 14:56:57 2013 -0500
+++ b/templates/webapps/galaxy/workflow/editor_tool_form.mako	Wed Nov 06 11:18:10 2013 -0600
@@ -95,7 +95,7 @@
 </%def>
 
 <form method="post" action="${h.url_for(controller='workflow', action='editor_form_post' )}">
-
+  %if tool:
     <div class="toolForm">
         <div class="toolFormTitle">Tool: ${tool.name}</div>
         %if tool.version:
@@ -111,6 +111,6 @@
             %endfor
         </div>
     </div>
-    
+  %endif 
 
 </form>

Attachment: signature.asc
Description: OpenPGP digital signature

___________________________________________________________
Please keep all replies on the list by using "reply all"
in your mail client.  To manage your subscriptions to this
and other Galaxy lists, please use the interface at:
  http://lists.bx.psu.edu/

To search Galaxy mailing lists use the unified search at:
  http://galaxyproject.org/search/mailinglists/

Reply via email to