Skip to content

Commit 6b3bd46

Browse files
authored
šŸ› Fixes on studies dispatcher: file-picker download, extension mapping and study naming (#4390)
1 parent 869a08d commit 6b3bd46

File tree

9 files changed

+73
-38
lines changed

9 files changed

+73
-38
lines changed

ā€Žpackages/postgres-database/src/simcore_postgres_database/models/services_consume_filetypes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"service_version",
3131
sa.String,
3232
nullable=False,
33-
doc="Version part of a $key:$version service resource name",
33+
doc="Defines the minimum version (included) of this version from which this information applies",
3434
),
3535
sa.Column(
3636
"service_display_name",

ā€Žservices/static-webserver/client/source/class/osparc/file/FilePicker.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ qx.Class.define("osparc.file.FilePicker", {
237237
} else if (osparc.file.FilePicker.isOutputDownloadLink(node.getOutputs())) {
238238
const outFileValue = osparc.file.FilePicker.getOutput(node.getOutputs());
239239
if (osparc.utils.Utils.isObject(outFileValue) && "downloadLink" in outFileValue) {
240-
osparc.utils.Utils.downloadLink(outFileValue["downloadLink"], "GET", null, progressCb, loadedCb);
240+
osparc.utils.Utils.downloadLink(outFileValue["downloadLink"], "GET", outFileValue["label"], progressCb, loadedCb);
241241
}
242242
}
243243
},

ā€Žservices/web/server/src/simcore_service_webserver/projects/_nodes_previews.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import urllib.parse
44

55
from aiohttp import web
6+
from aiohttp.client import ClientError
67
from models_library.projects_nodes import Node, NodeID
78
from models_library.projects_nodes_io import SimCoreFileLink
89
from pydantic import (
@@ -79,9 +80,9 @@ async def fake_screenshots_factory(
7980
file_url=file_url,
8081
)
8182
)
82-
except (KeyError, ValidationError) as err:
83-
_logger.debug(
84-
"Failed to create link from file-picker %s: %s",
83+
except (KeyError, ValidationError, ClientError) as err:
84+
_logger.warning(
85+
"Skipping fake node. Unable to create link from file-picker %s: %s",
8586
node.json(indent=1),
8687
err,
8788
)

ā€Žservices/web/server/src/simcore_service_webserver/storage/api.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44
import asyncio
55
import logging
6+
import urllib.parse
67
from typing import Any, AsyncGenerator
78

89
from aiohttp import ClientError, ClientSession, ClientTimeout, web
@@ -194,9 +195,14 @@ async def get_download_link(
194195
"""
195196
session, api_endpoint = _get_storage_client(app)
196197
url = (
197-
api_endpoint / f"locations/{filelink.store}/files" / filelink.path
198+
api_endpoint
199+
/ f"locations/{filelink.store}/files"
200+
/ urllib.parse.quote(filelink.path, safe="")
198201
).with_query(user_id=user_id)
202+
199203
async with session.get(f"{url}") as response:
200204
response.raise_for_status()
201-
download = PresignedLink.parse_obj(await response.json())
205+
download: PresignedLink = (
206+
Envelope[PresignedLink].parse_obj(await response.json()).data
207+
)
202208
return parse_obj_as(HttpUrl, download.link)

ā€Žservices/web/server/src/simcore_service_webserver/studies_dispatcher/_core.py

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@
33
from collections import deque
44
from functools import lru_cache
55

6+
import sqlalchemy as sa
67
from aiohttp import web
8+
from models_library.services import ServiceVersion
79
from models_library.utils.pydantic_tools_extension import parse_obj_or_none
8-
from pydantic import ByteSize, ValidationError
10+
from pydantic import ByteSize, ValidationError, parse_obj_as
911
from servicelib.logging_utils import log_decorator
1012
from simcore_postgres_database.models.services_consume_filetypes import (
1113
services_consume_filetypes,
1214
)
15+
from sqlalchemy.dialects.postgresql import ARRAY, INTEGER
1316

1417
from .._constants import APP_DB_ENGINE_KEY
1518
from ._errors import FileToLarge, IncompatibleService
@@ -39,21 +42,20 @@ async def list_viewers_info(
3942
consumers: deque = deque()
4043

4144
async with app[APP_DB_ENGINE_KEY].acquire() as conn:
42-
4345
# FIXME: ADD CONDITION: service MUST be shared with EVERYBODY!
44-
stmt = services_consume_filetypes.select()
46+
query = services_consume_filetypes.select()
4547
if file_type:
46-
stmt = stmt.where(services_consume_filetypes.c.filetype == file_type)
48+
query = query.where(services_consume_filetypes.c.filetype == file_type)
4749

48-
stmt = stmt.order_by("filetype", "preference_order")
50+
query = query.order_by("filetype", "preference_order")
4951

5052
if file_type and only_default:
51-
stmt = stmt.limit(1)
53+
query = query.limit(1)
5254

53-
_logger.debug("Listing viewers:\n%s", stmt)
55+
_logger.debug("Listing viewers:\n%s", query)
5456

5557
listed_filetype = set()
56-
async for row in await conn.execute(stmt):
58+
async for row in await conn.execute(query):
5759
try:
5860
# TODO: filter in database (see test_list_default_compatible_services )
5961
if only_default:
@@ -109,20 +111,35 @@ async def validate_requested_viewer(
109111
110112
"""
111113

114+
def _version(column_or_value):
115+
# converts version value string to array[integer] that can be compared
116+
return sa.func.string_to_array(column_or_value, ".").cast(ARRAY(INTEGER))
117+
112118
if not service_key and not service_version:
113119
return await get_default_viewer(app, file_type, file_size)
114120

115121
if service_key and service_version:
116122
async with app[APP_DB_ENGINE_KEY].acquire() as conn:
117-
stmt = services_consume_filetypes.select().where(
118-
(services_consume_filetypes.c.filetype == file_type)
119-
& (services_consume_filetypes.c.service_key == service_key)
120-
& (services_consume_filetypes.c.service_version == service_version)
123+
query = (
124+
services_consume_filetypes.select()
125+
.where(
126+
(services_consume_filetypes.c.filetype == file_type)
127+
& (services_consume_filetypes.c.service_key == service_key)
128+
& (
129+
_version(services_consume_filetypes.c.service_version)
130+
<= _version(service_version)
131+
)
132+
)
133+
.order_by(_version(services_consume_filetypes.c.service_version).desc())
134+
.limit(1)
121135
)
122-
result = await conn.execute(stmt)
136+
137+
result = await conn.execute(query)
123138
row = await result.first()
124139
if row:
125-
return ViewerInfo.create_from_db(row)
140+
view = ViewerInfo.create_from_db(row)
141+
view.version = parse_obj_as(ServiceVersion, service_version)
142+
return view
126143

127144
raise IncompatibleService(file_type=file_type)
128145

ā€Žservices/web/server/src/simcore_service_webserver/studies_dispatcher/_models.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,5 @@ class FileParams(BaseModel):
7070

7171
@property
7272
def footprint(self) -> str:
73-
return f"{self.file_name}.{self.file_type}:{self.file_size}"
73+
"""Identifier used to create UUID"""
74+
return f"{self.file_name}:{self.file_type}:{self.file_size}"

ā€Žservices/web/server/src/simcore_service_webserver/studies_dispatcher/_projects.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -340,8 +340,8 @@ async def get_or_create_project_with_file(
340340
# project
341341
project = _create_project(
342342
project_id=project_uid,
343-
name=f"File picker {file_params.file_name}.{file_params.file_type}",
344-
description=f"Autogenerated study with a file-picker {file_params.footprint}",
343+
name=f"File {file_params.file_name[-20:]}",
344+
description=f"Autogenerated study with a file-picker for {file_params.file_name} [{file_params.file_type} file]",
345345
thumbnail=project_thumbnail,
346346
owner=user,
347347
workbench={

ā€Žservices/web/server/src/simcore_service_webserver/studies_dispatcher/_redirects_handlers.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
get_or_create_project_with_file_and_service,
2929
get_or_create_project_with_service,
3030
)
31-
from ._users import UserInfo, ensure_authentication, get_or_create_user
31+
from ._users import UserInfo, ensure_authentication, get_or_create_guest_user
3232
from .settings import get_plugin_settings
3333

3434
_logger = logging.getLogger(__name__)
@@ -239,8 +239,8 @@ async def get_redirection_to_viewer(request: web.Request):
239239
)
240240

241241
# Retrieve user or create a temporary guest
242-
user = await get_or_create_user(
243-
request, allow_anonymous_users=viewer.is_guest_allowed
242+
user = await get_or_create_guest_user(
243+
request, allow_anonymous_or_guest_users=viewer.is_guest_allowed
244244
)
245245

246246
# Generate one project per user + download_link + viewer
@@ -269,8 +269,8 @@ async def get_redirection_to_viewer(request: web.Request):
269269
service_version=service_params_.viewer_version,
270270
)
271271

272-
user = await get_or_create_user(
273-
request, allow_anonymous_users=valid_service.is_public
272+
user = await get_or_create_guest_user(
273+
request, allow_anonymous_or_guest_users=valid_service.is_public
274274
)
275275

276276
project_id, viewer_id = await get_or_create_project_with_service(
@@ -301,7 +301,9 @@ async def get_redirection_to_viewer(request: web.Request):
301301
# - Anonymous user rights associated with services, not files
302302
# - Front-end viewer for anonymous users cannot render a single file-picker. SEE https://github.com/ITISFoundation/osparc-simcore/issues/4342
303303
# - Risk of anonymous users to polute platform with data
304-
user = await get_or_create_user(request, allow_anonymous_users=False)
304+
user = await get_or_create_guest_user(
305+
request, allow_anonymous_or_guest_users=False
306+
)
305307

306308
project_id, file_picker_id = await get_or_create_project_with_file(
307309
request.app,

ā€Žservices/web/server/src/simcore_service_webserver/studies_dispatcher/_users.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ async def _get_authorized_user(request: web.Request) -> dict:
5050
return {}
5151

5252

53-
async def _create_temporary_user(request: web.Request):
53+
async def _create_temporary_guest_user(request: web.Request):
5454
db: AsyncpgStorage = get_plugin_storage(request.app)
5555
redis_locks_client: aioredis.Redis = get_redis_lock_manager_client(request.app)
5656
settings: StudiesDispatcherSettings = get_plugin_settings(app=request.app)
@@ -115,15 +115,23 @@ async def _create_temporary_user(request: web.Request):
115115

116116

117117
@log_decorator(_logger, level=logging.DEBUG)
118-
async def get_or_create_user(
119-
request: web.Request, *, allow_anonymous_users: bool
118+
async def get_or_create_guest_user(
119+
request: web.Request, *, allow_anonymous_or_guest_users: bool
120120
) -> UserInfo:
121121
"""
122+
A user w/o authentication is denoted ANONYMOUS. If allow_anonymous_or_guest_users=True, then
123+
these users can be automatically promoted to GUEST. For that, a temporary guest account
124+
is created and associated to this user.
125+
126+
GUEST users are therefore a special user that is un-identified to us (no email/name, etc)
127+
128+
NOTE that if allow_anonymous_or_guest_users=False, GUEST users are NOT allowed in the system either.
129+
122130
Arguments:
123-
allow_anonymous_users -- if True, it will create a temporary GUEST account
131+
allow_anonymous_or_guest_users -- if True, it will create a temporary GUEST account
124132
125133
Raises:
126-
web.HTTPUnauthorized
134+
web.HTTPUnauthorized if ANONYMOUS users are not allowed (either w/o auth or as GUEST)
127135
128136
"""
129137
user = None
@@ -134,12 +142,12 @@ async def get_or_create_user(
134142
# NOTE: covers valid cookie with unauthorized user (e.g. expired guest/banned)
135143
user = await _get_authorized_user(request)
136144

137-
if not user and allow_anonymous_users:
145+
if not user and allow_anonymous_or_guest_users:
138146
_logger.debug("Anonymous user is accepted as guest...")
139-
user = await _create_temporary_user(request)
147+
user = await _create_temporary_guest_user(request)
140148
is_anonymous_user = True
141149

142-
if not allow_anonymous_users and (not user or user.get("role") == GUEST):
150+
if not allow_anonymous_or_guest_users and (not user or user.get("role") == GUEST):
143151
# NOTE: if allow_anonymous_users=False then GUEST users are NOT allowed!
144152
raise web.HTTPUnauthorized(reason=MSG_GUESTS_NOT_ALLOWED)
145153

0 commit comments

Comments
Ā (0)