mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merge pull request #14576 from jmchilton/compute_dataset_hash
Implement API endpoint for calculating dataset hashes asynchronously
This commit is contained in:
@@ -22,7 +22,10 @@ from galaxy.datatypes import sniff
|
||||
from galaxy.datatypes.registry import Registry as DatatypesRegistry
|
||||
from galaxy.jobs import MinimalJobWrapper
|
||||
from galaxy.managers.collections import DatasetCollectionManager
|
||||
from galaxy.managers.datasets import DatasetAssociationManager
|
||||
from galaxy.managers.datasets import (
|
||||
DatasetAssociationManager,
|
||||
DatasetManager,
|
||||
)
|
||||
from galaxy.managers.hdas import HDAManager
|
||||
from galaxy.managers.lddas import LDDAManager
|
||||
from galaxy.managers.markdown_util import generate_branded_pdf
|
||||
@@ -30,6 +33,7 @@ from galaxy.managers.model_stores import ModelStoreManager
|
||||
from galaxy.metadata.set_metadata import set_metadata_portable
|
||||
from galaxy.model.scoped_session import galaxy_scoped_session
|
||||
from galaxy.schema.tasks import (
|
||||
ComputeDatasetHashTaskRequest,
|
||||
GenerateHistoryContentDownload,
|
||||
GenerateHistoryDownload,
|
||||
GenerateInvocationDownload,
|
||||
@@ -344,6 +348,14 @@ def import_model_store(
|
||||
model_store_manager.import_model_store(request)
|
||||
|
||||
|
||||
@galaxy_task(action="compute dataset hash and store in database")
|
||||
def compute_dataset_hash(
|
||||
dataset_manager: DatasetManager,
|
||||
request: ComputeDatasetHashTaskRequest,
|
||||
):
|
||||
dataset_manager.compute_hash(request)
|
||||
|
||||
|
||||
@galaxy_task(action="pruning history audit table")
|
||||
def prune_history_audit_table(sa_session: galaxy_scoped_session):
|
||||
"""Prune ever growing history_audit table."""
|
||||
|
||||
@@ -206,14 +206,6 @@ class Data(metaclass=DataMeta):
|
||||
return cls.allow_datatype_change
|
||||
return cls.composite_type is None
|
||||
|
||||
def get_raw_data(self, dataset):
|
||||
"""Returns the full data. To stream it open the file_name and read/write as needed"""
|
||||
try:
|
||||
return open(dataset.file_name, "rb").read(-1)
|
||||
except OSError:
|
||||
log.exception("%s reading a file that does not exist %s", self.__class__.__name__, dataset.file_name)
|
||||
return ""
|
||||
|
||||
def dataset_content_needs_grooming(self, file_name):
|
||||
"""This function is called on an output dataset file after the content is initially generated."""
|
||||
return False
|
||||
|
||||
@@ -25,7 +25,9 @@ from galaxy.managers import (
|
||||
secured,
|
||||
users,
|
||||
)
|
||||
from galaxy.schema.tasks import ComputeDatasetHashTaskRequest
|
||||
from galaxy.structured_app import MinimalManagerApp
|
||||
from galaxy.util.hash_util import memory_bound_hexdigest
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -110,6 +112,48 @@ class DatasetManager(base.ModelManager[model.Dataset], secured.AccessibleManager
|
||||
roles = user.all_roles_exploiting_cache() if user else []
|
||||
return self.app.security_agent.can_access_dataset(roles, dataset)
|
||||
|
||||
def compute_hash(self, request: ComputeDatasetHashTaskRequest):
|
||||
# For files in extra_files_path
|
||||
dataset = self.by_id(request.dataset_id)
|
||||
extra_files_path = request.extra_files_path
|
||||
if extra_files_path:
|
||||
extra_dir = dataset.extra_files_path_name
|
||||
file_path = self.app.object_store.get_filename(dataset, extra_dir=extra_dir, alt_name=extra_files_path)
|
||||
else:
|
||||
file_path = dataset.file_name
|
||||
hash_function = request.hash_function
|
||||
calculated_hash_value = memory_bound_hexdigest(hash_func_name=hash_function, path=file_path)
|
||||
extra_files_path = request.extra_files_path
|
||||
dataset_hash = model.DatasetHash(
|
||||
hash_function=hash_function.value,
|
||||
hash_value=calculated_hash_value,
|
||||
extra_files_path=extra_files_path,
|
||||
)
|
||||
dataset_hash.dataset = dataset
|
||||
# TODO: replace/update if the combination of dataset_id/hash_function has already
|
||||
# been stored.
|
||||
sa_session = self.session()
|
||||
hash = (
|
||||
sa_session.query(model.DatasetHash)
|
||||
.filter(
|
||||
model.DatasetHash.dataset_id == dataset.id,
|
||||
model.DatasetHash.hash_function == hash_function,
|
||||
model.DatasetHash.extra_files_path == extra_files_path,
|
||||
)
|
||||
.one_or_none()
|
||||
)
|
||||
if hash is None:
|
||||
sa_session.add(dataset_hash)
|
||||
sa_session.flush()
|
||||
else:
|
||||
old_hash_value = hash.hash_value
|
||||
if old_hash_value != calculated_hash_value:
|
||||
log.warning(
|
||||
f"Re-calculated dataset hash for dataset [{dataset.id}] and new hash value [{calculated_hash_value}] does not equal previous hash value [{old_hash_value}]."
|
||||
)
|
||||
else:
|
||||
log.debug("Duplicated dataset hash request, no update to the database.")
|
||||
|
||||
# TODO: implement above for groups
|
||||
# TODO: datatypes?
|
||||
# .... data, object_store
|
||||
|
||||
@@ -40,7 +40,7 @@ from galaxy.structured_app import (
|
||||
BasicSharedApp,
|
||||
MinimalManagerApp,
|
||||
)
|
||||
from galaxy.util.hash_util import new_secure_hash
|
||||
from galaxy.util.hash_util import new_secure_hash_v2
|
||||
from galaxy.web import url_for
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -197,8 +197,8 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin):
|
||||
# to identify if it is needed for some reason.
|
||||
#
|
||||
# Deleting multiple times will re-hash the username/email
|
||||
email_hash = new_secure_hash(user.email + pseudorandom_value)
|
||||
uname_hash = new_secure_hash(user.username + pseudorandom_value)
|
||||
email_hash = new_secure_hash_v2(user.email + pseudorandom_value)
|
||||
uname_hash = new_secure_hash_v2(user.username + pseudorandom_value)
|
||||
# We must also redact username
|
||||
for role in user.all_roles():
|
||||
if self.app.config.redact_username_during_deletion:
|
||||
@@ -219,15 +219,15 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin):
|
||||
.all()
|
||||
)
|
||||
for addr in user_addresses:
|
||||
addr.desc = new_secure_hash(addr.desc + pseudorandom_value)
|
||||
addr.name = new_secure_hash(addr.name + pseudorandom_value)
|
||||
addr.institution = new_secure_hash(addr.institution + pseudorandom_value)
|
||||
addr.address = new_secure_hash(addr.address + pseudorandom_value)
|
||||
addr.city = new_secure_hash(addr.city + pseudorandom_value)
|
||||
addr.state = new_secure_hash(addr.state + pseudorandom_value)
|
||||
addr.postal_code = new_secure_hash(addr.postal_code + pseudorandom_value)
|
||||
addr.country = new_secure_hash(addr.country + pseudorandom_value)
|
||||
addr.phone = new_secure_hash(addr.phone + pseudorandom_value)
|
||||
addr.desc = new_secure_hash_v2(addr.desc + pseudorandom_value)
|
||||
addr.name = new_secure_hash_v2(addr.name + pseudorandom_value)
|
||||
addr.institution = new_secure_hash_v2(addr.institution + pseudorandom_value)
|
||||
addr.address = new_secure_hash_v2(addr.address + pseudorandom_value)
|
||||
addr.city = new_secure_hash_v2(addr.city + pseudorandom_value)
|
||||
addr.state = new_secure_hash_v2(addr.state + pseudorandom_value)
|
||||
addr.postal_code = new_secure_hash_v2(addr.postal_code + pseudorandom_value)
|
||||
addr.country = new_secure_hash_v2(addr.country + pseudorandom_value)
|
||||
addr.phone = new_secure_hash_v2(addr.phone + pseudorandom_value)
|
||||
self.session().add(addr)
|
||||
# Purge the user
|
||||
super().purge(user, flush=flush)
|
||||
@@ -563,7 +563,7 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin):
|
||||
user = trans.sa_session.query(self.app.model.User).filter(self.app.model.User.table.c.email == email).first()
|
||||
activation_token = user.activation_token
|
||||
if activation_token is None:
|
||||
activation_token = util.hash_util.new_secure_hash(str(random.getrandbits(256)))
|
||||
activation_token = util.hash_util.new_secure_hash_v2(str(random.getrandbits(256)))
|
||||
user.activation_token = activation_token
|
||||
trans.sa_session.add(user)
|
||||
trans.sa_session.flush()
|
||||
|
||||
@@ -146,7 +146,7 @@ from galaxy.util.form_builder import (
|
||||
WorkflowField,
|
||||
WorkflowMappingField,
|
||||
)
|
||||
from galaxy.util.hash_util import new_secure_hash
|
||||
from galaxy.util.hash_util import new_insecure_hash
|
||||
from galaxy.util.json import safe_loads
|
||||
from galaxy.util.sanitize_html import sanitize_html
|
||||
|
||||
@@ -638,7 +638,7 @@ class User(Base, Dictifiable, RepresentById):
|
||||
if User.use_pbkdf2:
|
||||
self.password = galaxy.security.passwords.hash_password(cleartext)
|
||||
else:
|
||||
self.password = new_secure_hash(text_type=cleartext)
|
||||
self.password = new_insecure_hash(text_type=cleartext)
|
||||
self.last_password_change = now()
|
||||
|
||||
def set_random_password(self, length=16):
|
||||
@@ -4063,10 +4063,6 @@ class DatasetInstance(UsesCreateAndUpdateTime, _HasTable):
|
||||
def hashes(self):
|
||||
return self.dataset.hashes
|
||||
|
||||
def get_raw_data(self):
|
||||
"""Returns the full data. To stream it open the file_name and read/write as needed"""
|
||||
return self.datatype.get_raw_data(self)
|
||||
|
||||
def get_mime(self):
|
||||
"""Returns the mime type of the data"""
|
||||
try:
|
||||
|
||||
@@ -5,6 +5,7 @@ from pydantic import (
|
||||
Field,
|
||||
)
|
||||
|
||||
from galaxy.util.hash_util import HashFunctionNameEnum
|
||||
from .schema import (
|
||||
DatasetSourceType,
|
||||
HistoryContentType,
|
||||
@@ -101,3 +102,10 @@ class MaterializeDatasetInstanceTaskRequest(BaseModel):
|
||||
"- The encoded id of the the HDA\n"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class ComputeDatasetHashTaskRequest(BaseModel):
|
||||
dataset_id: int
|
||||
extra_files_path: Optional[str]
|
||||
hash_function: HashFunctionNameEnum
|
||||
user: RequestUser
|
||||
|
||||
@@ -431,7 +431,7 @@ class CachedDependencyManager(DependencyManager):
|
||||
(dep.name, dep.version, dep.exact, dep.dependency_type) for dep in resolved_dependencies
|
||||
]
|
||||
hash_str = json.dumps(sorted(resolved_dependencies))
|
||||
return hash_util.new_secure_hash(hash_str)[:8] # short hash
|
||||
return hash_util.new_insecure_hash(hash_str)[:8] # short hash
|
||||
|
||||
def get_hashed_dependencies_path(self, resolved_dependencies):
|
||||
"""
|
||||
|
||||
@@ -6,6 +6,16 @@ introduced hashlib which replaced sha in Python 2.4 and previous versions.
|
||||
import hashlib
|
||||
import hmac
|
||||
import logging
|
||||
import os
|
||||
from enum import Enum
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
List,
|
||||
Optional,
|
||||
Union,
|
||||
)
|
||||
|
||||
from . import smart_str
|
||||
|
||||
@@ -13,22 +23,39 @@ log = logging.getLogger(__name__)
|
||||
|
||||
BLOCK_SIZE = 1024 * 1024
|
||||
|
||||
HashFunctionT = Callable[[], "hashlib._Hash"]
|
||||
|
||||
sha1 = hashlib.sha1
|
||||
sha256 = hashlib.sha256
|
||||
sha512 = hashlib.sha512
|
||||
sha = sha1
|
||||
md5 = hashlib.md5
|
||||
|
||||
HASH_NAME_MAP = {
|
||||
"MD5": md5,
|
||||
"SHA-1": sha1,
|
||||
"SHA-256": sha256,
|
||||
"SHA-512": sha512,
|
||||
|
||||
class HashFunctionNameEnum(str, Enum):
|
||||
"""Particular pieces of information that can be requested for a dataset."""
|
||||
|
||||
md5 = "MD5"
|
||||
sha1 = "SHA-1"
|
||||
sha256 = "SHA-256"
|
||||
sha512 = "SHA-512"
|
||||
|
||||
|
||||
HASH_NAME_MAP: Dict[HashFunctionNameEnum, HashFunctionT] = {
|
||||
HashFunctionNameEnum.md5: md5,
|
||||
HashFunctionNameEnum.sha1: sha1,
|
||||
HashFunctionNameEnum.sha256: sha256,
|
||||
HashFunctionNameEnum.sha512: sha512,
|
||||
}
|
||||
HASH_NAMES = list(HASH_NAME_MAP.keys())
|
||||
HASH_NAMES: List[HashFunctionNameEnum] = list(HASH_NAME_MAP.keys())
|
||||
|
||||
|
||||
def memory_bound_hexdigest(hash_func=None, hash_func_name=None, path=None, file=None):
|
||||
def memory_bound_hexdigest(
|
||||
hash_func: Optional[HashFunctionT] = None,
|
||||
hash_func_name: Optional[HashFunctionNameEnum] = None,
|
||||
path: Optional[str] = None,
|
||||
file=None,
|
||||
):
|
||||
if hash_func is None:
|
||||
assert hash_func_name is not None
|
||||
hash_func = HASH_NAME_MAP[hash_func_name]
|
||||
@@ -48,7 +75,7 @@ def memory_bound_hexdigest(hash_func=None, hash_func_name=None, path=None, file=
|
||||
file.close()
|
||||
|
||||
|
||||
def md5_hash_file(path):
|
||||
def md5_hash_file(path: Union[str, os.PathLike]) -> Optional[str]:
|
||||
"""
|
||||
Return a md5 hashdigest for a file or None if path could not be read.
|
||||
"""
|
||||
@@ -63,19 +90,35 @@ def md5_hash_file(path):
|
||||
return None
|
||||
|
||||
|
||||
def new_secure_hash(text_type):
|
||||
def new_secure_hash_v2(text_type: Union[bytes, str]) -> str:
|
||||
"""More modern version of new_secure_hash.
|
||||
|
||||
Certain passwords are set via new_insecure_hash (previously new_secure_hash),
|
||||
so that needs to remain for legacy purposes.
|
||||
"""
|
||||
Returns the hexdigest of the sha1 hash of the argument `text_type`.
|
||||
assert text_type is not None
|
||||
return sha512(smart_str(text_type)).hexdigest()
|
||||
|
||||
|
||||
def new_insecure_hash(text_type: Union[bytes, str]) -> str:
|
||||
"""Returns the hexdigest of the sha1 hash of the argument `text_type`.
|
||||
|
||||
Previously called new_secure_hash, but this should not be considered
|
||||
secure - SHA1 is no longer considered a secure hash and has been broken
|
||||
since the early 2000s.
|
||||
|
||||
use_pbkdf2 should be set by default and galaxy.security.passwords should
|
||||
be the default used for passwords in Galaxy.
|
||||
"""
|
||||
assert text_type is not None
|
||||
return sha1(smart_str(text_type)).hexdigest()
|
||||
|
||||
|
||||
def hmac_new(key, value):
|
||||
def hmac_new(key: Union[bytes, str], value: Union[bytes, str]) -> str:
|
||||
return hmac.new(smart_str(key), smart_str(value), sha).hexdigest()
|
||||
|
||||
|
||||
def is_hashable(value):
|
||||
def is_hashable(value: Any) -> bool:
|
||||
try:
|
||||
hash(value)
|
||||
except Exception:
|
||||
@@ -83,4 +126,4 @@ def is_hashable(value):
|
||||
return True
|
||||
|
||||
|
||||
__all__ = ("md5", "hashlib", "sha1", "sha", "new_secure_hash", "hmac_new", "is_hashable")
|
||||
__all__ = ("md5", "hashlib", "sha1", "sha", "new_insecure_hash", "new_secure_hash_v2", "hmac_new", "is_hashable")
|
||||
|
||||
@@ -34,6 +34,7 @@ from galaxy.schema.fields import DecodedDatabaseIdField
|
||||
from galaxy.schema.schema import (
|
||||
AnyHDA,
|
||||
AnyHistoryContentItem,
|
||||
AsyncTaskResultSummary,
|
||||
DatasetAssociationRoles,
|
||||
DatasetSourceType,
|
||||
UpdateDatasetPermissionsPayload,
|
||||
@@ -51,6 +52,7 @@ from galaxy.webapps.galaxy.api.common import (
|
||||
query_serialization_params,
|
||||
)
|
||||
from galaxy.webapps.galaxy.services.datasets import (
|
||||
ComputeDatasetHashPayload,
|
||||
ConvertedDatasetsMap,
|
||||
DatasetInheritanceChain,
|
||||
DatasetsService,
|
||||
@@ -338,3 +340,16 @@ class FastAPIDatasets:
|
||||
no other checks or restrictions are made.
|
||||
"""
|
||||
return self.service.delete_batch(trans, payload)
|
||||
|
||||
@router.put(
|
||||
"/api/datasets/{dataset_id}/hash",
|
||||
summary="Compute dataset hash for dataset and update model",
|
||||
)
|
||||
def compute_hash(
|
||||
self,
|
||||
trans=DependsOnTrans,
|
||||
dataset_id: DecodedDatabaseIdField = DatasetIDPathParam,
|
||||
hda_ldda: DatasetSourceType = DatasetSourceQueryParam,
|
||||
payload: ComputeDatasetHashPayload = Body(...),
|
||||
) -> AsyncTaskResultSummary:
|
||||
return self.service.compute_hash(trans, dataset_id, payload, hda_ldda=hda_ldda)
|
||||
|
||||
@@ -20,6 +20,7 @@ from galaxy import (
|
||||
util,
|
||||
web,
|
||||
)
|
||||
from galaxy.celery.tasks import compute_dataset_hash
|
||||
from galaxy.datatypes import dataproviders
|
||||
from galaxy.managers.base import ModelSerializer
|
||||
from galaxy.managers.context import ProvidesHistoryContext
|
||||
@@ -43,14 +44,17 @@ from galaxy.schema.fields import DecodedDatabaseIdField
|
||||
from galaxy.schema.schema import (
|
||||
AnyHDA,
|
||||
AnyHistoryContentItem,
|
||||
AsyncTaskResultSummary,
|
||||
DatasetAssociationRoles,
|
||||
DatasetSourceId,
|
||||
DatasetSourceType,
|
||||
Model,
|
||||
UpdateDatasetPermissionsPayload,
|
||||
)
|
||||
from galaxy.schema.tasks import ComputeDatasetHashTaskRequest
|
||||
from galaxy.schema.types import RelativeUrl
|
||||
from galaxy.security.idencoding import IdEncodingHelper
|
||||
from galaxy.util.hash_util import HashFunctionNameEnum
|
||||
from galaxy.util.path import safe_walk
|
||||
from galaxy.visualization.data_providers.genome import (
|
||||
BamDataProvider,
|
||||
@@ -59,7 +63,10 @@ from galaxy.visualization.data_providers.genome import (
|
||||
)
|
||||
from galaxy.visualization.data_providers.registry import DataProviderRegistry
|
||||
from galaxy.webapps.base.controller import UsesVisualizationMixin
|
||||
from galaxy.webapps.galaxy.services.base import ServiceBase
|
||||
from galaxy.webapps.galaxy.services.base import (
|
||||
async_task_summary,
|
||||
ServiceBase,
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -185,6 +192,16 @@ class DeleteDatasetBatchPayload(Model):
|
||||
)
|
||||
|
||||
|
||||
class ComputeDatasetHashPayload(Model):
|
||||
hash_function: Optional[HashFunctionNameEnum] = Field(
|
||||
default=HashFunctionNameEnum.md5, description="Hash function name to use to compute dataset hashes."
|
||||
)
|
||||
extra_files_path: Optional[str] = Field(default=None, description="If set, extra files path to compute a hash for.")
|
||||
|
||||
class Config:
|
||||
use_enum_values = True # When using .dict()
|
||||
|
||||
|
||||
class DatasetErrorMessage(Model):
|
||||
dataset: DatasetSourceId = Field(
|
||||
description="The encoded ID of the dataset and its source.",
|
||||
@@ -374,6 +391,23 @@ class DatasetsService(ServiceBase, UsesVisualizationMixin):
|
||||
|
||||
return DatasetInheritanceChain(__root__=result)
|
||||
|
||||
def compute_hash(
|
||||
self,
|
||||
trans: ProvidesHistoryContext,
|
||||
dataset_id: DecodedDatabaseIdField,
|
||||
payload: ComputeDatasetHashPayload,
|
||||
hda_ldda: DatasetSourceType = DatasetSourceType.hda,
|
||||
) -> AsyncTaskResultSummary:
|
||||
dataset_instance = self.dataset_manager_by_type[hda_ldda].get_accessible(dataset_id, trans.user)
|
||||
request = ComputeDatasetHashTaskRequest(
|
||||
dataset_id=dataset_instance.dataset.id,
|
||||
extra_files_path=payload.extra_files_path,
|
||||
hash_function=payload.hash_function,
|
||||
user=trans.async_request_user,
|
||||
)
|
||||
result = compute_dataset_hash.delay(request=request)
|
||||
return async_task_summary(result)
|
||||
|
||||
def update_permissions(
|
||||
self,
|
||||
trans: ProvidesHistoryContext,
|
||||
|
||||
@@ -21,6 +21,18 @@ from galaxy_test.base.populators import (
|
||||
)
|
||||
from ._framework import ApiTestCase
|
||||
|
||||
COMPOSITE_DATA_FETCH_REQUEST_1 = {
|
||||
"src": "composite",
|
||||
"ext": "velvet",
|
||||
"composite": {
|
||||
"items": [
|
||||
{"src": "pasted", "paste_content": "sequences content"},
|
||||
{"src": "pasted", "paste_content": "roadmaps content"},
|
||||
{"src": "pasted", "paste_content": "log content"},
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class DatasetsApiTestCase(ApiTestCase):
|
||||
history_id: str
|
||||
@@ -481,25 +493,96 @@ class DatasetsApiTestCase(ApiTestCase):
|
||||
|
||||
@skip_without_datatype("velvet")
|
||||
def test_composite_datatype_download(self):
|
||||
item = {
|
||||
"src": "composite",
|
||||
"ext": "velvet",
|
||||
"composite": {
|
||||
"items": [
|
||||
{"src": "pasted", "paste_content": "sequences content"},
|
||||
{"src": "pasted", "paste_content": "roadmaps content"},
|
||||
{"src": "pasted", "paste_content": "log content"},
|
||||
]
|
||||
},
|
||||
}
|
||||
output = self.dataset_populator.fetch_hda(self.history_id, item, wait=True)
|
||||
print(output)
|
||||
output = self.dataset_populator.fetch_hda(self.history_id, COMPOSITE_DATA_FETCH_REQUEST_1, wait=True)
|
||||
response = self._get(f"histories/{self.history_id}/contents/{output['id']}/display?to_ext=zip")
|
||||
self._assert_status_code_is(response, 200)
|
||||
archive = zipfile.ZipFile(BytesIO(response.content))
|
||||
namelist = archive.namelist()
|
||||
assert len(namelist) == 4, f"Expected 3 elements in [{namelist}]"
|
||||
|
||||
def test_compute_md5_on_primary_dataset(self):
|
||||
hda = self.dataset_populator.new_dataset(self.history_id, wait=True)
|
||||
hda_details = self.dataset_populator.get_history_dataset_details(self.history_id, dataset=hda)
|
||||
assert "hashes" in hda_details, str(hda_details.keys())
|
||||
hashes = hda_details["hashes"]
|
||||
assert len(hashes) == 0
|
||||
|
||||
self.dataset_populator.compute_hash(hda["id"])
|
||||
hda_details = self.dataset_populator.get_history_dataset_details(self.history_id, dataset=hda)
|
||||
self.assert_hash_value(hda_details, "940cbe15c94d7e339dc15550f6bdcf4d", "MD5")
|
||||
|
||||
def test_compute_sha1_on_composite_dataset(self):
|
||||
output = self.dataset_populator.fetch_hda(self.history_id, COMPOSITE_DATA_FETCH_REQUEST_1, wait=True)
|
||||
hda_details = self.dataset_populator.get_history_dataset_details(self.history_id, dataset=output)
|
||||
assert "hashes" in hda_details, str(hda_details.keys())
|
||||
hashes = hda_details["hashes"]
|
||||
assert len(hashes) == 0
|
||||
|
||||
self.dataset_populator.compute_hash(hda_details["id"], hash_function="SHA-256", extra_files_path="Roadmaps")
|
||||
hda_details = self.dataset_populator.get_history_dataset_details(self.history_id, dataset=output)
|
||||
self.assert_hash_value(
|
||||
hda_details,
|
||||
"3cbd311889963528954fe03b28b68a09685ea7a75660bd2268d5b44cafbe0d22",
|
||||
"SHA-256",
|
||||
extra_files_path="Roadmaps",
|
||||
)
|
||||
|
||||
def test_duplicated_hash_requests_on_primary(self):
|
||||
hda = self.dataset_populator.new_dataset(self.history_id, wait=True)
|
||||
hda_details = self.dataset_populator.get_history_dataset_details(self.history_id, dataset=hda)
|
||||
assert "hashes" in hda_details, str(hda_details.keys())
|
||||
hashes = hda_details["hashes"]
|
||||
assert len(hashes) == 0
|
||||
|
||||
self.dataset_populator.compute_hash(hda["id"])
|
||||
self.dataset_populator.compute_hash(hda["id"])
|
||||
hda_details = self.dataset_populator.get_history_dataset_details(self.history_id, dataset=hda)
|
||||
self.assert_hash_value(hda_details, "940cbe15c94d7e339dc15550f6bdcf4d", "MD5")
|
||||
|
||||
def test_duplicated_hash_requests_on_extra_files(self):
|
||||
output = self.dataset_populator.fetch_hda(self.history_id, COMPOSITE_DATA_FETCH_REQUEST_1, wait=True)
|
||||
hda_details = self.dataset_populator.get_history_dataset_details(self.history_id, dataset=output)
|
||||
assert "hashes" in hda_details, str(hda_details.keys())
|
||||
hashes = hda_details["hashes"]
|
||||
assert len(hashes) == 0
|
||||
|
||||
# 4 unique requests, but make them twice...
|
||||
for _ in range(2):
|
||||
self.dataset_populator.compute_hash(hda_details["id"], hash_function="SHA-256", extra_files_path="Roadmaps")
|
||||
self.dataset_populator.compute_hash(hda_details["id"], hash_function="SHA-1", extra_files_path="Roadmaps")
|
||||
self.dataset_populator.compute_hash(hda_details["id"], hash_function="MD5", extra_files_path="Roadmaps")
|
||||
self.dataset_populator.compute_hash(
|
||||
hda_details["id"], hash_function="SHA-256", extra_files_path="Sequences"
|
||||
)
|
||||
|
||||
hda_details = self.dataset_populator.get_history_dataset_details(self.history_id, dataset=output)
|
||||
self.assert_hash_value(hda_details, "ce0c0ef1073317ff96c896c249b002dc", "MD5", extra_files_path="Roadmaps")
|
||||
self.assert_hash_value(
|
||||
hda_details, "fe2e06cdd03922a1ddf3fe6c7e0d299c8044fc8e", "SHA-1", extra_files_path="Roadmaps"
|
||||
)
|
||||
self.assert_hash_value(
|
||||
hda_details,
|
||||
"3cbd311889963528954fe03b28b68a09685ea7a75660bd2268d5b44cafbe0d22",
|
||||
"SHA-256",
|
||||
extra_files_path="Roadmaps",
|
||||
)
|
||||
self.assert_hash_value(
|
||||
hda_details,
|
||||
"4688dca47fe3214516c35acd284a79d97bd6df2bc1c55981b556d995495b91b6",
|
||||
"SHA-256",
|
||||
extra_files_path="Sequences",
|
||||
)
|
||||
|
||||
def assert_hash_value(self, dataset_details, expected_hash_value, hash_function, extra_files_path=None):
|
||||
assert "hashes" in dataset_details, str(dataset_details.keys())
|
||||
hashes = dataset_details["hashes"]
|
||||
matching_hashes = [
|
||||
h for h in hashes if h["extra_files_path"] == extra_files_path and h["hash_function"] == hash_function
|
||||
]
|
||||
assert len(matching_hashes) == 1
|
||||
hash_value = matching_hashes[0]["hash_value"]
|
||||
assert expected_hash_value == hash_value
|
||||
|
||||
def test_storage_show(self):
|
||||
hda = self.dataset_populator.new_dataset(self.history_id, wait=True)
|
||||
hda_details = self.dataset_populator.get_history_dataset_details(self.history_id, dataset=hda)
|
||||
|
||||
@@ -640,6 +640,24 @@ class BaseDatasetPopulator(BasePopulator):
|
||||
def get_job_details(self, job_id: str, full: bool = False) -> Response:
|
||||
return self._get(f"jobs/{job_id}", {"full": full})
|
||||
|
||||
def compute_hash(
|
||||
self,
|
||||
dataset_id: str,
|
||||
hash_function: Optional[str] = "MD5",
|
||||
extra_files_path: Optional[str] = None,
|
||||
wait: bool = True,
|
||||
) -> Response:
|
||||
data: Dict[str, Any] = dict()
|
||||
if hash_function:
|
||||
data["hash_function"] = hash_function
|
||||
if extra_files_path:
|
||||
data["extra_files_path"] = extra_files_path
|
||||
put_response = self._put(f"datasets/{dataset_id}/hash", data, json=True)
|
||||
api_asserts.assert_status_code_is_ok(put_response)
|
||||
if wait:
|
||||
self.wait_on_task(put_response)
|
||||
return put_response
|
||||
|
||||
def cancel_history_jobs(self, history_id: str, wait=True) -> None:
|
||||
active_jobs = self.active_history_jobs(history_id)
|
||||
for active_job in active_jobs:
|
||||
|
||||
@@ -13,7 +13,7 @@ from galaxy import (
|
||||
)
|
||||
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.util.hash_util import new_secure_hash_v2
|
||||
from galaxy.web.form_builder import CheckboxField
|
||||
from galaxy.web.legacy_framework.grids import (
|
||||
Grid,
|
||||
@@ -773,8 +773,8 @@ class Admin:
|
||||
compliance_log.info(f"delete-user-event: {user_id}")
|
||||
# See lib/galaxy/webapps/tool_shed/controllers/admin.py
|
||||
pseudorandom_value = str(int(time.time()))
|
||||
email_hash = new_secure_hash(user.email + pseudorandom_value)
|
||||
uname_hash = new_secure_hash(user.username + pseudorandom_value)
|
||||
email_hash = new_secure_hash_v2(user.email + pseudorandom_value)
|
||||
uname_hash = new_secure_hash_v2(user.username + pseudorandom_value)
|
||||
for role in user.all_roles():
|
||||
print(
|
||||
role, self.app.config.redact_username_during_deletion, self.app.config.redact_email_during_deletion
|
||||
|
||||
@@ -48,7 +48,7 @@ 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
|
||||
from galaxy.util.hash_util import new_secure_hash
|
||||
from galaxy.util.hash_util import new_insecure_hash
|
||||
from tool_shed.dependencies.repository import relation_builder
|
||||
from tool_shed.util import (
|
||||
hg_util,
|
||||
@@ -152,7 +152,7 @@ class User(Base, Dictifiable, _HasTable):
|
||||
|
||||
def check_password(self, cleartext):
|
||||
"""Check if 'cleartext' matches 'self.password' when hashed."""
|
||||
return self.password == new_secure_hash(text_type=cleartext)
|
||||
return self.password == new_insecure_hash(text_type=cleartext)
|
||||
|
||||
def get_disk_usage(self, nice_size=False):
|
||||
return 0
|
||||
@@ -171,7 +171,7 @@ class User(Base, Dictifiable, _HasTable):
|
||||
if message:
|
||||
raise Exception(f"Invalid password: {message}")
|
||||
# Set 'self.password' to the digest of 'cleartext'.
|
||||
self.password = new_secure_hash(text_type=cleartext)
|
||||
self.password = new_insecure_hash(text_type=cleartext)
|
||||
|
||||
|
||||
class PasswordResetToken(Base, _HasTable):
|
||||
|
||||
Reference in New Issue
Block a user