diff --git a/lib/galaxy/webapps/galaxy/controllers/admin_toolshed.py b/lib/galaxy/webapps/galaxy/controllers/admin_toolshed.py index 048ffa19cff..9756a7d63a1 100644 --- a/lib/galaxy/webapps/galaxy/controllers/admin_toolshed.py +++ b/lib/galaxy/webapps/galaxy/controllers/admin_toolshed.py @@ -442,7 +442,7 @@ class AdminToolshed( AdminGalaxy ): # Avoid caching trans.response.headers['Pragma'] = 'no-cache' trans.response.headers['Expires'] = '0' - return suc.get_repository_file_contents( trans.app, file_path, repository_id ) + return suc.get_repository_file_contents( trans.app, file_path, repository_id, is_admin=True ) @web.expose @web.require_admin @@ -973,7 +973,7 @@ class AdminToolshed( AdminGalaxy ): # Avoid caching trans.response.headers['Pragma'] = 'no-cache' trans.response.headers['Expires'] = '0' - return suc.open_repository_files_folder( trans.app, folder_path, repository_id ) + return suc.open_repository_files_folder( trans.app, folder_path, repository_id, is_admin=True ) @web.expose @web.require_admin diff --git a/lib/galaxy/webapps/tool_shed/controllers/repository.py b/lib/galaxy/webapps/tool_shed/controllers/repository.py index 646d470f823..5a26fa02d99 100644 --- a/lib/galaxy/webapps/tool_shed/controllers/repository.py +++ b/lib/galaxy/webapps/tool_shed/controllers/repository.py @@ -1471,7 +1471,8 @@ class RepositoryController( BaseUIController, ratings_util.ItemRatings ): # Avoid caching trans.response.headers['Pragma'] = 'no-cache' trans.response.headers['Expires'] = '0' - return suc.get_repository_file_contents( trans.app, file_path, repository_id ) + is_admin = trans.user_is_admin() + return suc.get_repository_file_contents( trans.app, file_path, repository_id, is_admin ) @web.expose def get_functional_test_rss( self, trans, **kwd ): @@ -2424,7 +2425,8 @@ class RepositoryController( BaseUIController, ratings_util.ItemRatings ): # Avoid caching trans.response.headers['Pragma'] = 'no-cache' trans.response.headers['Expires'] = '0' - return suc.open_repository_files_folder( trans.app, folder_path, repository_id ) + is_admin = trans.user_is_admin() + return suc.open_repository_files_folder( trans.app, folder_path, repository_id, is_admin ) @web.expose def preview_tools_in_changeset( self, trans, repository_id, **kwd ): diff --git a/lib/tool_shed/util/shed_util_common.py b/lib/tool_shed/util/shed_util_common.py index 5c0fefa7e63..51467fc6ed0 100644 --- a/lib/tool_shed/util/shed_util_common.py +++ b/lib/tool_shed/util/shed_util_common.py @@ -636,13 +636,13 @@ def get_repository_for_dependency_relationship( app, tool_shed, name, owner, cha return repository -def get_repository_file_contents( app, file_path, repository_id ): +def get_repository_file_contents( app, file_path, repository_id, is_admin=False ): """Return the display-safe contents of a repository file for display in a browser.""" safe_str = '' - if not is_path_within_repo( app, file_path, repository_id ): + if not is_path_browsable( app, file_path, repository_id, is_admin ): log.warning( 'Request tries to access a file outside of the repository location. File path: %s', file_path ) return 'Invalid file path' - # Symlink targets are checked by is_path_within_repo + # Symlink targets are checked by is_path_browsable if os.path.islink( file_path ): safe_str = 'link to: ' + basic_util.to_html_string( os.readlink( file_path ) ) return safe_str @@ -1150,14 +1150,13 @@ def is_tool_shed_client( app ): return hasattr( app, "install_model" ) -def open_repository_files_folder( app, folder_path, repository_id ): +def open_repository_files_folder( app, folder_path, repository_id, is_admin=False ): """ Return a list of dictionaries, each of which contains information for a file or directory contained within a directory in a repository file hierarchy. """ - # Symlink targets are checked by is_path_within_repo - if not is_path_within_repo( app, folder_path, repository_id ): - log.warning( 'Request tries to access a folder outside of the repository location. Folder path: %s', folder_path ) + if not is_path_browsable( app, folder_path, repository_id, is_admin ): + log.warning( 'Request tries to access a folder outside of the allowed locations. Folder path: %s', folder_path ) return [] try: files_list = get_repository_files( folder_path ) @@ -1170,11 +1169,11 @@ def open_repository_files_folder( app, folder_path, repository_id ): is_folder = False full_path = os.path.join( folder_path, filename ) is_link = os.path.islink( full_path ) - path_is_within_repo = is_path_within_repo( app, full_path, repository_id ) - if is_link and not path_is_within_repo: + path_is_browsable = is_path_browsable( app, full_path, repository_id ) + if is_link and not path_is_browsable: log.warning( 'Valid folder contains a symlink outside of the repository location. Link found in: ' + str( full_path ) ) if filename: - if os.path.isdir( full_path ) and path_is_within_repo: + if os.path.isdir( full_path ) and path_is_browsable: # Append a '/' character so that our jquery dynatree will function properly. filename = '%s/' % filename full_path = '%s/' % full_path @@ -1188,9 +1187,20 @@ def open_repository_files_folder( app, folder_path, repository_id ): return folder_contents +def is_path_browsable( app, path, repository_id, is_admin=False ): + """ + Detects whether the given path is browsable i.e. is within the + allowed repository folders. Admins can additionaly browse folders + with tool dependencies. + """ + if is_admin and is_path_within_dependency_dir( app, path ): + return True + return is_path_within_repo( app, path, repository_id) + + def is_path_within_repo( app, path, repository_id ): """ - Detect whether the given path is within the repository folde ron the disk. + Detect whether the given path is within the repository folder on the disk. Use to filter malicious symlinks targeting outside paths. """ repo_path = os.path.abspath( get_repository_by_id( app, repository_id ).repo_path( app ) ) @@ -1198,6 +1208,20 @@ def is_path_within_repo( app, path, repository_id ): return os.path.commonprefix( [ repo_path, resolved_path ] ) == repo_path +def is_path_within_dependency_dir( app, path ): + """ + Detect whether the given path is within the tool_dependency_dir folder on the disk. + (Specified by the config option). Use to filter malicious symlinks targeting outside paths. + """ + allowed = False + resolved_path = os.path.realpath( path ) + tool_dependency_dir = app.config.get( 'tool_dependency_dir', None ) + if tool_dependency_dir: + dependency_path = os.path.abspath( tool_dependency_dir ) + allowed = os.path.commonprefix( [ dependency_path, resolved_path ] ) == dependency_path + return allowed + + def repository_was_previously_installed( app, tool_shed_url, repository_name, repo_info_tuple, from_tip=False ): """ Find out if a repository is already installed into Galaxy - there are several scenarios where this