mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-10-24 02:04:04 +08:00
Merge branch 'release_20.05' into dev
This commit is contained in:
+5
-4
@@ -96,10 +96,6 @@ class UniverseApplication(config.ConfiguresGalaxyMixin):
|
||||
check_migrate_tools = self.config.check_migrate_tools
|
||||
self._configure_models(check_migrate_databases=self.config.check_migrate_databases, check_migrate_tools=check_migrate_tools, config_file=config_file)
|
||||
|
||||
self.installed_repository_manager = InstalledRepositoryManager(self)
|
||||
self._configure_datatypes_registry(self.installed_repository_manager)
|
||||
galaxy.model.set_datatypes_registry(self.datatypes_registry)
|
||||
|
||||
# Security helper
|
||||
self._configure_security()
|
||||
# Tag handler
|
||||
@@ -139,6 +135,11 @@ class UniverseApplication(config.ConfiguresGalaxyMixin):
|
||||
self.tool_shed_repository_cache = ToolShedRepositoryCache(self)
|
||||
# Watch various config files for immediate reload
|
||||
self.watchers = ConfigWatchers(self)
|
||||
self._configure_tool_config_files()
|
||||
self.installed_repository_manager = InstalledRepositoryManager(self)
|
||||
self._configure_datatypes_registry(self.installed_repository_manager)
|
||||
galaxy.model.set_datatypes_registry(self.datatypes_registry)
|
||||
|
||||
self._configure_toolbox()
|
||||
|
||||
# Load Data Manager
|
||||
|
||||
@@ -125,7 +125,6 @@ class CustosAuthnz(IdentityProvider):
|
||||
raise exceptions.AuthenticationFailed(message)
|
||||
else:
|
||||
user = trans.app.user_manager.create(email=email, username=username)
|
||||
user.set_random_password()
|
||||
trans.sa_session.add(user)
|
||||
trans.sa_session.flush()
|
||||
custos_authnz_token = CustosAuthnzToken(user=user,
|
||||
|
||||
@@ -984,6 +984,16 @@ class ConfiguresGalaxyMixin(object):
|
||||
else:
|
||||
log.warning('Waiting for toolbox reload timed out after 60 seconds')
|
||||
|
||||
def _configure_tool_config_files(self):
|
||||
if self.config.shed_tool_config_file not in self.config.tool_configs:
|
||||
self.config.tool_configs.append(self.config.shed_tool_config_file)
|
||||
# The value of migrated_tools_config is the file reserved for containing only those tools that have been
|
||||
# eliminated from the distribution and moved to the tool shed. If migration checking is disabled, only add it if
|
||||
# it exists (since this may be an existing deployment where migrations were previously run).
|
||||
if ((self.config.check_migrate_tools or os.path.exists(self.config.migrated_tools_config))
|
||||
and self.config.migrated_tools_config not in self.config.tool_configs):
|
||||
self.config.tool_configs.append(self.config.migrated_tools_config)
|
||||
|
||||
def _configure_toolbox(self):
|
||||
from galaxy import tools
|
||||
from galaxy.managers.citations import CitationsManager
|
||||
@@ -996,14 +1006,6 @@ class ConfiguresGalaxyMixin(object):
|
||||
from galaxy.managers.tools import DynamicToolManager
|
||||
self.dynamic_tools_manager = DynamicToolManager(self)
|
||||
self._toolbox_lock = threading.RLock()
|
||||
if self.config.shed_tool_config_file not in self.config.tool_configs:
|
||||
self.config.tool_configs.append(self.config.shed_tool_config_file)
|
||||
# The value of migrated_tools_config is the file reserved for containing only those tools that have been
|
||||
# eliminated from the distribution and moved to the tool shed. If migration checking is disabled, only add it if
|
||||
# it exists (since this may be an existing deployment where migrations were previously run).
|
||||
if ((self.config.check_migrate_tools or os.path.exists(self.config.migrated_tools_config))
|
||||
and self.config.migrated_tools_config not in self.config.tool_configs):
|
||||
self.config.tool_configs.append(self.config.migrated_tools_config)
|
||||
self.toolbox = tools.ToolBox(self.config.tool_configs, self.config.tool_path, self)
|
||||
galaxy_root_dir = os.path.abspath(self.config.root)
|
||||
file_path = os.path.abspath(getattr(self.config, "file_path"))
|
||||
|
||||
@@ -57,6 +57,21 @@ class CondorJobRunner(AsynchronousJobRunner):
|
||||
self._init_monitor_thread()
|
||||
self._init_worker_threads()
|
||||
|
||||
def __old_state_paths(self, cjs):
|
||||
"""For recovery of jobs started prior to standardizing the naming of
|
||||
files in the AsychronousJobState object
|
||||
Remove this function in 21.01
|
||||
"""
|
||||
if cjs.job_wrapper is not None:
|
||||
job_file = "%s/galaxy_%s.sh" % (self.app.config.cluster_files_directory, cjs.job_wrapper.job_id)
|
||||
if not os.path.exists(cjs.job_file) and os.path.exists(job_file):
|
||||
cluster_files_dir_and_id = (self.app.config.cluster_files_directory, cjs.job_wrapper.get_id_tag())
|
||||
cjs.output_file = "%s/galaxy_%s.o" % cluster_files_dir_and_id
|
||||
cjs.error_file = "%s/galaxy_%s.e" % cluster_files_dir_and_id
|
||||
cjs.exit_code_file = "%s/galaxy_%s.ec" % cluster_files_dir_and_id
|
||||
cjs.user_log = "%s/galaxy_%s.condor.log" % cluster_files_dir_and_id
|
||||
cjs.job_file = job_file
|
||||
|
||||
def queue_job(self, job_wrapper):
|
||||
"""Create job script and submit it to the DRM"""
|
||||
|
||||
@@ -89,14 +104,13 @@ class CondorJobRunner(AsynchronousJobRunner):
|
||||
|
||||
# define job attributes
|
||||
cjs = CondorJobState(
|
||||
files_dir=self.app.config.cluster_files_directory,
|
||||
files_dir=job_wrapper.working_directory,
|
||||
job_wrapper=job_wrapper
|
||||
)
|
||||
|
||||
cluster_directory = self.app.config.cluster_files_directory
|
||||
cjs.user_log = os.path.join(cluster_directory, 'galaxy_%s.condor.log' % galaxy_id_tag)
|
||||
cjs.user_log = os.path.join(job_wrapper.working_directory, 'galaxy_%s.condor.log' % galaxy_id_tag)
|
||||
cjs.register_cleanup_file_attribute('user_log')
|
||||
submit_file = os.path.join(cluster_directory, 'galaxy_%s.condor.desc' % galaxy_id_tag)
|
||||
submit_file = os.path.join(job_wrapper.working_directory, 'galaxy_%s.condor.desc' % galaxy_id_tag)
|
||||
executable = cjs.job_file
|
||||
|
||||
build_submit_params = dict(
|
||||
@@ -175,6 +189,7 @@ class CondorJobRunner(AsynchronousJobRunner):
|
||||
for cjs in self.watched:
|
||||
job_id = cjs.job_id
|
||||
galaxy_id_tag = cjs.job_wrapper.get_id_tag()
|
||||
self.__old_state_paths(cjs) # remove in 21.01
|
||||
try:
|
||||
if cjs.job_wrapper.tool.tool_type != 'interactive' and os.stat(cjs.user_log).st_size == cjs.user_log_size:
|
||||
new_watched.append(cjs)
|
||||
@@ -269,13 +284,14 @@ class CondorJobRunner(AsynchronousJobRunner):
|
||||
if job_id is None:
|
||||
self.put(job_wrapper)
|
||||
return
|
||||
cjs = CondorJobState(job_wrapper=job_wrapper, files_dir=self.app.config.cluster_files_directory)
|
||||
cjs = CondorJobState(job_wrapper=job_wrapper, files_dir=job_wrapper.working_directory)
|
||||
cjs.job_id = str(job_id)
|
||||
cjs.command_line = job.get_command_line()
|
||||
cjs.job_wrapper = job_wrapper
|
||||
cjs.job_destination = job_wrapper.job_destination
|
||||
cjs.user_log = os.path.join(self.app.config.cluster_files_directory, 'galaxy_%s.condor.log' % galaxy_id_tag)
|
||||
cjs.user_log = os.path.join(job_wrapper.working_directory, 'galaxy_%s.condor.log' % galaxy_id_tag)
|
||||
cjs.register_cleanup_file_attribute('user_log')
|
||||
self.__old_state_paths(cjs) # remove in 21.01
|
||||
if job.state == model.Job.states.RUNNING:
|
||||
log.debug("(%s/%s) is still in running state, adding to the DRM queue" % (job.id, job.job_runner_external_id))
|
||||
cjs.running = True
|
||||
|
||||
@@ -93,7 +93,10 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin):
|
||||
"""
|
||||
self._error_on_duplicate_email(email)
|
||||
user = self.model_class(email=email)
|
||||
user.set_password_cleartext(password)
|
||||
if password:
|
||||
user.set_password_cleartext(password)
|
||||
else:
|
||||
user.set_random_password()
|
||||
user.username = username
|
||||
if self.app.config.user_activation_on:
|
||||
user.active = False
|
||||
|
||||
@@ -52,6 +52,7 @@ import galaxy.security.passwords
|
||||
import galaxy.util
|
||||
from galaxy.model.item_attrs import get_item_annotation_str, UsesAnnotations
|
||||
from galaxy.security import get_permitted_actions
|
||||
from galaxy.security.validate_user_input import validate_password_str
|
||||
from galaxy.util import (directory_hash_id, ready_name_for_url,
|
||||
unicodify, unique_id)
|
||||
from galaxy.util.bunch import Bunch
|
||||
@@ -388,6 +389,9 @@ class User(Dictifiable, RepresentById):
|
||||
"""
|
||||
Set user password to the digest of `cleartext`.
|
||||
"""
|
||||
message = validate_password_str(cleartext, cleartext)
|
||||
if message:
|
||||
raise Exception("Invalid password: %s" % message)
|
||||
if User.use_pbkdf2:
|
||||
self.password = galaxy.security.passwords.hash_password(cleartext)
|
||||
else:
|
||||
|
||||
@@ -18,6 +18,8 @@ def hash_password(password):
|
||||
"""
|
||||
Hash a password, currently will use the PBKDF2 scheme.
|
||||
"""
|
||||
if password is None:
|
||||
raise Exception("password cannot be None")
|
||||
return hash_password_PBKDF2(password)
|
||||
|
||||
|
||||
|
||||
@@ -26,17 +26,44 @@ FILL_CHAR = '-'
|
||||
PASSWORD_MIN_LEN = 6
|
||||
|
||||
|
||||
def validate_email(trans, email, user=None, check_dup=True, allow_empty=False):
|
||||
"""
|
||||
Validates the email format, also checks whether the domain is blacklisted in the disposable domains configuration.
|
||||
"""
|
||||
def validate_email_str(email):
|
||||
"""Validates a string containing an email address."""
|
||||
message = ''
|
||||
if (user and user.email == email) or (email == "" and allow_empty):
|
||||
return message
|
||||
if not(VALID_EMAIL_RE.match(email)):
|
||||
message = "The format of the email address is not correct."
|
||||
elif len(email) > EMAIL_MAX_LEN:
|
||||
message = "Email address cannot be more than %d characters in length." % EMAIL_MAX_LEN
|
||||
return message
|
||||
|
||||
|
||||
def validate_password_str(password, confirm):
|
||||
if password != confirm:
|
||||
return "Passwords do not match."
|
||||
if not password or len(password) < PASSWORD_MIN_LEN:
|
||||
return "Use a password of at least %d characters." % PASSWORD_MIN_LEN
|
||||
return ""
|
||||
|
||||
|
||||
def validate_publicname_str(publicname):
|
||||
"""Validates a string containing a public username."""
|
||||
if len(publicname) < PUBLICNAME_MIN_LEN:
|
||||
return "Public name must be at least %d characters in length." % (PUBLICNAME_MIN_LEN)
|
||||
if len(publicname) > PUBLICNAME_MAX_LEN:
|
||||
return "Public name cannot be more than %d characters in length." % (PUBLICNAME_MAX_LEN)
|
||||
if not(VALID_PUBLICNAME_RE.match(publicname)):
|
||||
return "Public name must contain only lower-case letters, numbers, '.', '_' and '-'."
|
||||
return ''
|
||||
|
||||
|
||||
def validate_email(trans, email, user=None, check_dup=True, allow_empty=False):
|
||||
"""
|
||||
Validates the email format, also checks whether the domain is blacklisted in the disposable domains configuration.
|
||||
"""
|
||||
if (user and user.email == email) or (email == "" and allow_empty):
|
||||
return ''
|
||||
message = validate_email_str(email)
|
||||
if message:
|
||||
pass
|
||||
elif check_dup and trans.sa_session.query(trans.app.model.User).filter(func.lower(trans.app.model.User.table.c.email) == email.lower()).first():
|
||||
message = "User with email '%s' already exists." % email
|
||||
# If the whitelist is not empty filter out any domain not in the list and ignore blacklist.
|
||||
@@ -63,12 +90,9 @@ def validate_publicname(trans, publicname, user=None):
|
||||
"""
|
||||
if user and user.username == publicname:
|
||||
return ''
|
||||
if len(publicname) < PUBLICNAME_MIN_LEN:
|
||||
return "Public name must be at least %d characters in length." % (PUBLICNAME_MIN_LEN)
|
||||
if len(publicname) > PUBLICNAME_MAX_LEN:
|
||||
return "Public name cannot be more than %d characters in length." % (PUBLICNAME_MAX_LEN)
|
||||
if not(VALID_PUBLICNAME_RE.match(publicname)):
|
||||
return "Public name must contain only lower-case letters, numbers, '.', '_' and '-'."
|
||||
message = validate_publicname_str(publicname)
|
||||
if message:
|
||||
return message
|
||||
if trans.sa_session.query(trans.app.model.User).filter_by(username=publicname).first():
|
||||
return "Public name is taken; please choose another."
|
||||
return ''
|
||||
@@ -89,8 +113,4 @@ def transform_publicname(publicname):
|
||||
|
||||
|
||||
def validate_password(trans, password, confirm):
|
||||
if len(password) < PASSWORD_MIN_LEN:
|
||||
return "Use a password of at least %d characters." % PASSWORD_MIN_LEN
|
||||
if password != confirm:
|
||||
return "Passwords do not match."
|
||||
return ""
|
||||
return validate_password_str(password, confirm)
|
||||
|
||||
@@ -41,12 +41,7 @@ class InstalledRepositoryManager(object):
|
||||
self.context = self.install_model.context
|
||||
self.tool_configs = self.app.config.tool_configs
|
||||
|
||||
self.tool_trees = []
|
||||
for tool_config in self.tool_configs:
|
||||
tree, error_message = parse_xml(tool_config)
|
||||
if error_message:
|
||||
log.error(error_message)
|
||||
self.tool_trees.append(tree)
|
||||
self._tool_paths = []
|
||||
|
||||
self.installed_repository_dicts = []
|
||||
# Keep an in-memory dictionary whose keys are tuples defining tool_shed_repository objects (whose status is 'Installed')
|
||||
@@ -80,6 +75,24 @@ class InstalledRepositoryManager(object):
|
||||
# at runtime. The value defines the entire tool dependency tree.
|
||||
self.installed_runtime_dependent_tool_dependencies_of_installed_tool_dependencies = {}
|
||||
|
||||
@property
|
||||
def tool_paths(self):
|
||||
"""Return all possible tool_path attributes of all tool config files."""
|
||||
if len(self._tool_paths) != len(self.tool_configs):
|
||||
# This could be happen at startup or after the creation of a new shed_tool_conf.xml file
|
||||
# before the installation of the first repository
|
||||
tool_paths = []
|
||||
for tool_config in self.tool_configs:
|
||||
tree, error_message = parse_xml(tool_config)
|
||||
if error_message:
|
||||
log.error(error_message)
|
||||
else:
|
||||
tool_path = tree.getroot().get('tool_path')
|
||||
if tool_path:
|
||||
tool_paths.append(tool_path)
|
||||
self._tool_paths = tool_paths
|
||||
return self._tool_paths
|
||||
|
||||
def activate_repository(self, repository):
|
||||
"""Activate an installed tool shed repository that has been marked as deactivated."""
|
||||
shed_tool_conf, tool_path, relative_install_dir = suc.get_tool_panel_config_tool_path_install_dir(self.app, repository)
|
||||
@@ -575,21 +588,16 @@ class InstalledRepositoryManager(object):
|
||||
str(repository.installed_changeset_revision))
|
||||
|
||||
def get_repository_install_dir(self, tool_shed_repository):
|
||||
for tree in self.tool_trees:
|
||||
if tree is None:
|
||||
return None
|
||||
root = tree.getroot()
|
||||
tool_path = root.get('tool_path', None)
|
||||
if tool_path:
|
||||
ts = common_util.remove_port_from_tool_shed_url(str(tool_shed_repository.tool_shed))
|
||||
relative_path = os.path.join(tool_path,
|
||||
ts,
|
||||
'repos',
|
||||
str(tool_shed_repository.owner),
|
||||
str(tool_shed_repository.name),
|
||||
str(tool_shed_repository.installed_changeset_revision))
|
||||
if os.path.exists(relative_path):
|
||||
return relative_path
|
||||
for tool_path in self.tool_paths:
|
||||
ts = common_util.remove_port_from_tool_shed_url(str(tool_shed_repository.tool_shed))
|
||||
relative_path = os.path.join(tool_path,
|
||||
ts,
|
||||
'repos',
|
||||
str(tool_shed_repository.owner),
|
||||
str(tool_shed_repository.name),
|
||||
str(tool_shed_repository.installed_changeset_revision))
|
||||
if os.path.exists(relative_path):
|
||||
return relative_path
|
||||
return None
|
||||
|
||||
def get_runtime_dependent_tool_dependency_tuples(self, tool_dependency, status=None):
|
||||
|
||||
@@ -34,6 +34,7 @@ from galaxy.web import (
|
||||
expose_api,
|
||||
expose_api_anonymous_and_sessionless,
|
||||
expose_api_raw,
|
||||
expose_api_raw_anonymous_and_sessionless,
|
||||
format_return_as_json,
|
||||
)
|
||||
from galaxy.webapps.base.controller import (
|
||||
@@ -443,7 +444,7 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn
|
||||
invocation_response.update(rval)
|
||||
return invocation_response
|
||||
|
||||
@expose_api_raw
|
||||
@expose_api_raw_anonymous_and_sessionless
|
||||
def workflow_dict(self, trans, workflow_id, **kwd):
|
||||
"""
|
||||
GET /api/workflows/{encoded_workflow_id}/download
|
||||
|
||||
@@ -15,6 +15,7 @@ from galaxy import (
|
||||
from galaxy.actions.admin import AdminActions
|
||||
from galaxy.exceptions import ActionInputError, MessageException
|
||||
from galaxy.model import tool_shed_install as install_model
|
||||
from galaxy.security.validate_user_input import validate_password
|
||||
from galaxy.tool_shed.util.repository_util import get_ids_of_tool_shed_repositories_being_installed
|
||||
from galaxy.util import (
|
||||
nice_size,
|
||||
@@ -1387,10 +1388,9 @@ class AdminGalaxy(controller.JSAppLauncher, AdminActions, UsesQuotaMixin, QuotaP
|
||||
else:
|
||||
password = payload.get('password')
|
||||
confirm = payload.get('confirm')
|
||||
if len(password) < 6:
|
||||
return self.message_exception(trans, 'Use a password of at least 6 characters.')
|
||||
elif password != confirm:
|
||||
return self.message_exception(trans, 'Passwords do not match.')
|
||||
message = validate_password(trans, password, confirm)
|
||||
if message:
|
||||
return self.message_exception(trans, message)
|
||||
for user in users.values():
|
||||
user.set_password_cleartext(password)
|
||||
trans.sa_session.add(user)
|
||||
|
||||
@@ -5,6 +5,7 @@ import time
|
||||
from json import dumps
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from requests import delete, get, put
|
||||
|
||||
from galaxy.exceptions import error_codes
|
||||
@@ -231,7 +232,7 @@ class WorkflowsApiTestCase(BaseWorkflowsApiTestCase):
|
||||
self._assert_status_code_is(show_response, 400)
|
||||
|
||||
def test_cannot_show_private_workflow(self):
|
||||
workflow_id = self.workflow_populator.simple_workflow("test_not_importportable")
|
||||
workflow_id = self.workflow_populator.simple_workflow("test_not_importable")
|
||||
with self._different_user():
|
||||
show_response = self._get("workflows/%s" % workflow_id)
|
||||
self._assert_status_code_is(show_response, 403)
|
||||
@@ -240,6 +241,22 @@ class WorkflowsApiTestCase(BaseWorkflowsApiTestCase):
|
||||
workflows_url = self._api_url("workflows/%s" % workflow_id)
|
||||
assert get(workflows_url).status_code == 403
|
||||
|
||||
def test_cannot_download_private_workflow(self):
|
||||
workflow_id = self.workflow_populator.simple_workflow("test_not_downloadable")
|
||||
with self._different_user():
|
||||
with pytest.raises(AssertionError) as excinfo:
|
||||
self._download_workflow(workflow_id)
|
||||
assert '403' in str(excinfo.value)
|
||||
workflows_url = self._api_url("workflows/%s/download" % workflow_id)
|
||||
assert get(workflows_url).status_code == 403
|
||||
|
||||
def test_anon_can_download_public_workflow(self):
|
||||
workflow_id = self.workflow_populator.simple_workflow("test_downloadable", publish=True)
|
||||
workflows_url = self._api_url("workflows/%s/download" % workflow_id)
|
||||
response = get(workflows_url)
|
||||
response.raise_for_status()
|
||||
assert response.json()['a_galaxy_workflow'] == 'true'
|
||||
|
||||
def test_delete(self):
|
||||
workflow_id = self.workflow_populator.simple_workflow("test_delete")
|
||||
workflow_name = "test_delete"
|
||||
|
||||
@@ -111,7 +111,7 @@ def skip_if_site_down(url):
|
||||
|
||||
def site_down():
|
||||
try:
|
||||
response = requests.get(url)
|
||||
response = requests.get(url, timeout=10)
|
||||
return response.status_code != 200
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@@ -4,12 +4,12 @@ import time
|
||||
from sqlalchemy import false, func
|
||||
|
||||
from galaxy import util, web
|
||||
from galaxy.security.validate_user_input import validate_password
|
||||
from galaxy.util import inflector
|
||||
from galaxy.util.hash_util import new_secure_hash
|
||||
from galaxy.web.form_builder import CheckboxField
|
||||
from tool_shed.util.web_util import escape
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
compliance_log = logging.getLogger('COMPLIANCE')
|
||||
|
||||
@@ -685,13 +685,8 @@ class Admin(object):
|
||||
user = get_user(trans, user_id)
|
||||
password = kwd.get('password', None)
|
||||
confirm = kwd.get('confirm', None)
|
||||
if len(password) < 6:
|
||||
message = "Use a password of at least 6 characters."
|
||||
status = 'error'
|
||||
break
|
||||
elif password != confirm:
|
||||
message = "Passwords do not match."
|
||||
status = 'error'
|
||||
message = validate_password(trans, password, confirm)
|
||||
if message:
|
||||
break
|
||||
else:
|
||||
user.set_password_cleartext(password)
|
||||
|
||||
@@ -9,6 +9,7 @@ from datetime import (
|
||||
import tool_shed.repository_types.util as rt_util
|
||||
from galaxy import util
|
||||
from galaxy.model.orm.now import now
|
||||
from galaxy.security.validate_user_input import validate_password_str
|
||||
from galaxy.util import unique_id
|
||||
from galaxy.util.bunch import Bunch
|
||||
from galaxy.util.dictifiable import Dictifiable
|
||||
@@ -67,6 +68,9 @@ class User(Dictifiable):
|
||||
total_disk_usage = property(get_disk_usage, set_disk_usage)
|
||||
|
||||
def set_password_cleartext(self, cleartext):
|
||||
message = validate_password_str(cleartext, cleartext)
|
||||
if message:
|
||||
raise Exception("Invalid password: %s" % message)
|
||||
"""Set 'self.password' to the digest of 'cleartext'."""
|
||||
self.password = new_secure_hash(text_type=cleartext)
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ from __future__ import print_function
|
||||
import logging
|
||||
import optparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from six.moves.configparser import ConfigParser
|
||||
@@ -14,16 +13,17 @@ sys.path.insert(1, os.path.join(os.path.dirname(__file__)))
|
||||
|
||||
import tool_shed.webapp.config as tool_shed_config
|
||||
from galaxy.security.idencoding import IdEncodingHelper
|
||||
from galaxy.security.validate_user_input import (
|
||||
validate_email_str,
|
||||
validate_password_str,
|
||||
validate_publicname_str
|
||||
)
|
||||
from tool_shed.webapp.model import mapping
|
||||
from bootstrap_util import admin_user_info # noqa: I100,I201
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
VALID_PUBLICNAME_RE = re.compile(r"^[a-z0-9\-]+$")
|
||||
VALID_EMAIL_RE = re.compile(r"[^@]+@[^@]+\.[^@]+")
|
||||
|
||||
|
||||
class BootstrapApplication(object):
|
||||
"""
|
||||
Creates a basic Tool Shed application in order to discover the database connection and use SQL
|
||||
@@ -84,44 +84,15 @@ def create_user(app):
|
||||
|
||||
|
||||
def validate(email, password, username):
|
||||
message = validate_email(email)
|
||||
if not message:
|
||||
message = validate_password(password)
|
||||
if not message:
|
||||
message = validate_publicname(username)
|
||||
message = "\n".join([validate_email_str(email),
|
||||
validate_password_str(password, password),
|
||||
validate_publicname_str(username)]).rstrip()
|
||||
return message
|
||||
|
||||
|
||||
def validate_email(email):
|
||||
"""Validates the email format."""
|
||||
message = ''
|
||||
if not(VALID_EMAIL_RE.match(email)):
|
||||
message = "Please enter a real email address."
|
||||
elif len(email) > 255:
|
||||
message = "Email address exceeds maximum allowable length."
|
||||
return message
|
||||
|
||||
|
||||
def validate_password(password):
|
||||
if len(password) < 6:
|
||||
return "Use a password of at least 6 characters"
|
||||
return ''
|
||||
|
||||
|
||||
def validate_publicname(username):
|
||||
"""Validates the public username."""
|
||||
if len(username) < 3:
|
||||
return "Public name must be at least 3 characters in length"
|
||||
if len(username) > 255:
|
||||
return "Public name cannot be more than 255 characters in length"
|
||||
if not(VALID_PUBLICNAME_RE.match(username)):
|
||||
return "Public name must contain only lower-case letters, numbers and '-'"
|
||||
return ''
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = optparse.OptionParser(description='Create a user with API key.')
|
||||
parser.add_option('-c', dest='config', action='store', help='.ini file to retried toolshed configuration from')
|
||||
parser.add_option('-c', dest='config', action='store', help='.ini file to retrieve toolshed configuration from')
|
||||
(args, options) = parser.parse_args()
|
||||
ini_file = args.config
|
||||
config_parser = ConfigParser({'here': os.getcwd()})
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import os
|
||||
|
||||
from galaxy_test.base.populators import skip_if_toolshed_down
|
||||
from galaxy_test.driver import integration_util
|
||||
from .uses_shed import UsesShed
|
||||
@@ -17,3 +19,29 @@ class ToolShedToolTestIntegrationTestCase(integration_util.IntegrationTestCase,
|
||||
def test_tool_test(self):
|
||||
self.install_repository("devteam", "fastqc", "ff9530579d1f")
|
||||
self._run_tool_test("toolshed.g2.bx.psu.edu/repos/devteam/fastqc/fastqc/0.71")
|
||||
|
||||
|
||||
class ToolShedDatatypeTestIntegrationTestCase(integration_util.IntegrationTestCase, UsesShed):
|
||||
|
||||
"""Test datatype installation"""
|
||||
|
||||
framework_tool_and_types = True
|
||||
|
||||
@classmethod
|
||||
def handle_galaxy_config_kwds(cls, config):
|
||||
cls.configure_shed(config)
|
||||
|
||||
def handle_reconfigure_galaxy_config_kwds(self, config):
|
||||
config["tool_shed_config_file"] = os.path.join(self.shed_tools_dir, "shed_tool_conf.xml")
|
||||
|
||||
@skip_if_toolshed_down
|
||||
def test_datatype_installation(self):
|
||||
datatypes = self._get("datatypes").json()
|
||||
assert "cond" not in datatypes
|
||||
self.install_repository("sblanck", "smagexp_datatypes", "f174dc3d2641")
|
||||
datatypes = self._get("datatypes").json()
|
||||
assert "cond" in datatypes
|
||||
# Make sure datatype survives restart
|
||||
self.restart(handle_reconfig=self.handle_reconfigure_galaxy_config_kwds)
|
||||
datatypes = self._get("datatypes").json()
|
||||
assert "cond" in datatypes
|
||||
|
||||
@@ -185,6 +185,15 @@ class UserManagerTestCase(BaseTestCase):
|
||||
response = json.loads(controller.login(self.trans, payload={"login": user2.username, "password": default_password}))
|
||||
self.assertEqual(response["message"], "Success.")
|
||||
|
||||
def test_empty_password(self):
|
||||
self.log("should be able to create a user with no password")
|
||||
user = self.user_manager.create(email='user@nopassword.com', username='nopassword')
|
||||
self.assertIsNotNone(user.id)
|
||||
self.assertIsNotNone(user.password)
|
||||
# should not be able to login with a null or empty password
|
||||
self.assertFalse(check_password("", user.password))
|
||||
self.assertFalse(check_password(None, user.password))
|
||||
|
||||
|
||||
# =============================================================================
|
||||
class UserSerializerTestCase(BaseTestCase):
|
||||
|
||||
@@ -10,9 +10,16 @@ def test_alwaysreject():
|
||||
|
||||
def test_localdb():
|
||||
user = User(email='testmail', username='tester')
|
||||
user.set_password_cleartext('test')
|
||||
user.set_password_cleartext('test123')
|
||||
t = LocalDB()
|
||||
reject = t.authenticate_user(user, 'wrong', {'redact_username_in_logs': False})
|
||||
accept = t.authenticate_user(user, 'test', {'redact_username_in_logs': False})
|
||||
accept = t.authenticate_user(user, 'test123', {'redact_username_in_logs': False})
|
||||
assert reject is False
|
||||
assert accept is True
|
||||
# Password must conform to policy (length etc)
|
||||
try:
|
||||
user.set_password_cleartext('test')
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
raise Exception("Password policy validation failed")
|
||||
|
||||
Reference in New Issue
Block a user