Skip to content

Commit

Permalink
Merge pull request #18986 from arash77/fix-none-alphanumeric-characte…
Browse files Browse the repository at this point in the history
…rs-in-slug

Fix issue with generating slug for sharing
  • Loading branch information
jdavcs authored Nov 5, 2024
2 parents 3523f02 + 3c601ff commit 9834ecd
Show file tree
Hide file tree
Showing 16 changed files with 34 additions and 46 deletions.
2 changes: 1 addition & 1 deletion client/src/components/Sharing/SlugInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const initialSlug = props.slug;
function onChange(value: string) {
const slugFormatted = value
.replace(/\s+/g, "-")
.replace(/[^a-zA-Z0-9-]/g, "")
.replace(/[/:?#]/g, "")
.toLowerCase();
emit("change", slugFormatted);
Expand Down
1 change: 1 addition & 0 deletions lib/galaxy/dependencies/pinned-typecheck-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ types-html5lib==1.1.11.20241018
types-markdown==3.7.0.20240822
types-paramiko==3.5.0.20240928
types-python-dateutil==2.9.0.20241003
types-python-slugify==8.0.2.20240310
types-pyyaml==6.0.12.20240917
types-requests==2.31.0.6
types-s3transfer==0.10.3
Expand Down
7 changes: 0 additions & 7 deletions lib/galaxy/managers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1294,13 +1294,6 @@ def raise_filter_err(attr, op, val, msg):
raise exceptions.RequestParameterInvalidException(msg, column=attr, operation=op, val=val)


def is_valid_slug(slug):
"""Returns true iff slug is valid."""

VALID_SLUG_RE = re.compile(r"^[a-z0-9\-]+$")
return VALID_SLUG_RE.match(slug)


class SortableManager:
"""A manager interface for parsing order_by strings into actual 'order by' queries."""

Expand Down
2 changes: 1 addition & 1 deletion lib/galaxy/managers/pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ def create_page(self, trans, payload: CreatePagePayload):
raise exceptions.ObjectAttributeMissingException("Page name is required")
elif not payload.slug:
raise exceptions.ObjectAttributeMissingException("Page id is required")
elif not base.is_valid_slug(payload.slug):
elif not sharable.SlugBuilder.is_valid_slug(payload.slug):
raise exceptions.ObjectAttributeInvalidException(
"Page identifier must consist of only lowercase letters, numbers, and the '-' character"
)
Expand Down
28 changes: 8 additions & 20 deletions lib/galaxy/managers/sharable.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
"""

import logging
import re
from typing import (
Any,
List,
Expand All @@ -20,6 +19,7 @@
Type,
)

from slugify import slugify
from sqlalchemy import (
exists,
false,
Expand Down Expand Up @@ -291,7 +291,7 @@ def set_slug(self, item, new_slug, user, flush=True):
Validate and set the new slug for `item`.
"""
# precondition: has been validated
if not self.is_valid_slug(new_slug):
if not SlugBuilder.is_valid_slug(new_slug):
raise exceptions.RequestParameterInvalidException("Invalid slug", slug=new_slug)

if item.slug == new_slug:
Expand All @@ -309,23 +309,6 @@ def set_slug(self, item, new_slug, user, flush=True):
session.commit()
return item

def is_valid_slug(self, slug):
"""
Returns true if `slug` is valid.
"""
VALID_SLUG_RE = re.compile(r"^[a-z0-9\-]+$")
return VALID_SLUG_RE.match(slug)

def _slugify(self, start_with):
# Replace whitespace with '-'
slug_base = re.sub(r"\s+", "-", start_with)
# Remove all non-alphanumeric characters.
slug_base = re.sub(r"[^a-zA-Z0-9\-]", "", slug_base)
# Remove trailing '-'.
if slug_base.endswith("-"):
slug_base = slug_base[:-1]
return slug_base

def _default_slug_base(self, item):
# override in subclasses
if hasattr(item, "title"):
Expand All @@ -341,7 +324,7 @@ def get_unique_slug(self, item):

# Setup slug base.
if cur_slug is None or cur_slug == "":
slug_base = self._slugify(self._default_slug_base(item))
slug_base = slugify(self._default_slug_base(item), allow_unicode=True)
else:
slug_base = cur_slug

Expand Down Expand Up @@ -580,6 +563,11 @@ def create_item_slug(self, sa_session, item) -> bool:
item.slug = new_slug
return item.slug == cur_slug

@classmethod
def is_valid_slug(self, slug):
"""Returns true if slug is valid."""
return slugify(slug, allow_unicode=True) == slug


def slug_exists(session, model_class, user, slug, ignore_deleted=False):
stmt = select(exists().where(model_class.user == user).where(model_class.slug == slug))
Expand Down
2 changes: 1 addition & 1 deletion lib/galaxy/schema/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -3678,7 +3678,7 @@ class PageSummaryBase(Model):
..., # Required
title="Identifier",
description="The title slug for the page URL, must be unique.",
pattern=r"^[a-z0-9\-]+$",
pattern=r"^[^/:?#]+$",
)


Expand Down
7 changes: 6 additions & 1 deletion lib/galaxy/webapps/base/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -1090,7 +1090,7 @@ class SharableMixin:

def _is_valid_slug(self, slug):
"""Returns true if slug is valid."""
return managers_base.is_valid_slug(slug)
return SlugBuilder.is_valid_slug(slug)

@web.expose
@web.require_login("modify Galaxy items")
Expand Down Expand Up @@ -1123,6 +1123,11 @@ def share(self, trans, id=None, email="", **kwd):
@web.expose
def display_by_username_and_slug(self, trans, username, slug, **kwargs):
"""Display item by username and slug."""
# Ensure slug is in the correct format.
slug = slug.encode("latin1").decode("utf-8")
self._display_by_username_and_slug(trans, username, slug, **kwargs)

def _display_by_username_and_slug(self, trans, username, slug, **kwargs):
raise NotImplementedError()

def get_item(self, trans, id):
Expand Down
3 changes: 1 addition & 2 deletions lib/galaxy/webapps/galaxy/controllers/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,8 +431,7 @@ def _get_dataset_for_edit(self, trans, dataset_id):
trans.app.security_agent.set_dataset_permission(data.dataset, permissions)
return data, None

@web.expose
def display_by_username_and_slug(self, trans, username, slug, filename=None, preview=True, **kwargs):
def _display_by_username_and_slug(self, trans, username, slug, filename=None, preview=True, **kwargs):
"""Display dataset by username and slug; because datasets do not yet have slugs, the slug is the dataset's id."""
dataset = self._check_dataset(trans, slug)
# Filename used for composite types.
Expand Down
3 changes: 1 addition & 2 deletions lib/galaxy/webapps/galaxy/controllers/history.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,7 @@ def view(self, trans, id=None, show_deleted=False, show_hidden=False, use_panels
"allow_user_dataset_purge": trans.app.config.allow_user_dataset_purge,
}

@web.expose
def display_by_username_and_slug(self, trans, username, slug, **kwargs):
def _display_by_username_and_slug(self, trans, username, slug, **kwargs):
"""
Display history based on a username and slug.
"""
Expand Down
3 changes: 1 addition & 2 deletions lib/galaxy/webapps/galaxy/controllers/page.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,7 @@ def display(self, trans, id, **kwargs):
raise web.httpexceptions.HTTPNotFound()
return self.display_by_username_and_slug(trans, page.user.username, page.slug)

@web.expose
def display_by_username_and_slug(self, trans, username, slug, **kwargs):
def _display_by_username_and_slug(self, trans, username, slug, **kwargs):
"""Display page based on a username and slug."""

# Get page.
Expand Down
3 changes: 1 addition & 2 deletions lib/galaxy/webapps/galaxy/controllers/visualization.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,7 @@ def imp(self, trans, id, **kwargs):
use_panels=True,
)

@web.expose
def display_by_username_and_slug(self, trans, username, slug, **kwargs):
def _display_by_username_and_slug(self, trans, username, slug, **kwargs):
"""Display visualization based on a username and slug."""

# Get visualization.
Expand Down
3 changes: 1 addition & 2 deletions lib/galaxy/webapps/galaxy/controllers/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ class WorkflowController(BaseUIController, SharableMixin, UsesStoredWorkflowMixi
def __init__(self, app: StructuredApp):
super().__init__(app)

@web.expose
def display_by_username_and_slug(self, trans, username, slug, format="html", **kwargs):
def _display_by_username_and_slug(self, trans, username, slug, format="html", **kwargs):
"""
Display workflow based on a username and slug. Format can be html, json, or json-download.
"""
Expand Down
7 changes: 2 additions & 5 deletions lib/galaxy/webapps/galaxy/services/visualizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@
)

from galaxy import exceptions
from galaxy.managers.base import (
is_valid_slug,
security_check,
)
from galaxy.managers.base import security_check
from galaxy.managers.context import ProvidesUserContext
from galaxy.managers.sharable import (
slug_exists,
Expand Down Expand Up @@ -302,7 +299,7 @@ def _create_visualization(
# Error checking.
if slug:
slug_err = ""
if not is_valid_slug(slug):
if not SlugBuilder.is_valid_slug(slug):
slug_err = (
"visualization identifier must consist of only lowercase letters, numbers, and the '-' character"
)
Expand Down
6 changes: 6 additions & 0 deletions lib/galaxy_test/api/test_histories.py
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,12 @@ def test_anonymous_can_import_published(self):
}
self.dataset_populator.import_history(import_data)

def test_publish_non_alphanumeric(self):
history_name = "تاریخچه"
history_id = self.dataset_populator.new_history(name=history_name)
response = self.dataset_populator.make_public(history_id)
assert history_name in response["username_and_slug"]

def test_immutable_history_update_fails(self):
history_id = self._create_history("TestHistoryForImmutability")["id"]

Expand Down
1 change: 1 addition & 0 deletions packages/app/setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ install_requires =
pulsar-galaxy-lib>=0.15.0.dev0
pydantic>=2.7.4
pysam>=0.21
python-slugify
PyJWT
PyYAML
refgenconf>=0.12.0
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ dependencies = [
"python-dateutil",
"python-magic",
"python-multipart", # required to support form parsing in FastAPI/Starlette
"python-slugify",
"PyYAML",
"refgenconf>=0.12.0",
"regex",
Expand Down Expand Up @@ -172,6 +173,7 @@ typecheck = [
"types-Markdown",
"types-paramiko",
"types-python-dateutil",
"types-python-slugify",
"types-PyYAML",
"types-requests",
"types-setuptools",
Expand Down

0 comments on commit 9834ecd

Please sign in to comment.