Skip to content

Commit 681fc9c

Browse files
authored
[feature] UI Fine grained access - project locking and notification
* write permissions needed to remove a user not delete permission * added test for opening a shared project 2 times * access_rights renamed to accessRights *added test for groups access rights * adding state endpoint * mypy * now check project is locked * user is automatically enters room upon successful login * project state now returns the value + owner of the lock if any
1 parent 6b02813 commit 681fc9c

File tree

28 files changed

+1118
-497
lines changed

28 files changed

+1118
-497
lines changed

api/specs/common/schemas/project.yaml

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
components:
22
schemas:
33
Project:
4-
$ref: './project-v0.0.1-converted.yaml'
4+
$ref: "./project-v0.0.1-converted.yaml"
55
ProjectEnveloped:
66
type: object
77
required:
88
- data
99
properties:
1010
data:
11-
$ref: '#/components/schemas/Project'
11+
$ref: "#/components/schemas/Project"
1212
error:
1313
nullable: true
1414
default: null
15+
1516
ProjectArrayEnveloped:
1617
type: object
1718
required:
@@ -20,7 +21,43 @@ components:
2021
data:
2122
type: array
2223
items:
23-
$ref: '#/components/schemas/Project'
24+
$ref: "#/components/schemas/Project"
25+
error:
26+
nullable: true
27+
default: null
28+
29+
ProjectState:
30+
type: object
31+
required:
32+
- locked
33+
properties:
34+
locked:
35+
type: object
36+
description: describes the project lock state
37+
required:
38+
- value
39+
properties:
40+
value:
41+
type: boolean
42+
description: true if the project is locked
43+
owner:
44+
type: object
45+
properties:
46+
first_name:
47+
type: string
48+
last_name:
49+
type: string
50+
required:
51+
- firstName
52+
- lastName
53+
54+
ProjectStateEnveloped:
55+
type: object
56+
required:
57+
- data
58+
properties:
59+
data:
60+
$ref: "#/components/schemas/ProjectState"
2461
error:
2562
nullable: true
2663
default: null

api/specs/webserver/components/schemas/group.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,13 @@ UsersGroup:
4242
description: url to the group thumbnail
4343
type: string
4444
format: uri
45-
access_rights:
45+
accessRights:
4646
$ref: "#/GroupAccessRights"
4747
required:
4848
- gid
4949
- label
5050
- description
51-
- access_rights
51+
- accessRights
5252
example:
5353
- gid: "27"
5454
label: "A user"

api/specs/webserver/openapi-groups.yaml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,15 @@ paths:
181181
content:
182182
application/json:
183183
schema:
184-
$ref: "./components/schemas/group.yaml#/GroupAccessRights"
184+
type: object
185+
properties:
186+
accessRights:
187+
$ref: "./components/schemas/group.yaml#/GroupAccessRights"
188+
required:
189+
- accessRights
190+
191+
192+
185193
responses:
186194
"200":
187195
description: modified user

api/specs/webserver/openapi-projects.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,29 @@ paths:
171171
default:
172172
$ref: "#/components/responses/DefaultErrorResponse"
173173

174+
/projects/{project_id}/state:
175+
parameters:
176+
- name: project_id
177+
in: path
178+
required: true
179+
schema:
180+
type: string
181+
get:
182+
tags:
183+
- project
184+
summary: returns the state of a project
185+
operationId: state_project
186+
responses:
187+
"200":
188+
description: returns the project current state
189+
content:
190+
application/json:
191+
schema:
192+
$ref: "#/components/schemas/ProjectStateEnveloped"
193+
194+
default:
195+
$ref: "#/components/responses/DefaultErrorResponse"
196+
174197
/projects/{project_id}/close:
175198
parameters:
176199
- name: project_id
@@ -362,6 +385,9 @@ components:
362385
ProjectArrayEnveloped:
363386
$ref: "../common/schemas/project.yaml#/components/schemas/ProjectArrayEnveloped"
364387

388+
ProjectStateEnveloped:
389+
$ref: "../common/schemas/project.yaml#/components/schemas/ProjectStateEnveloped"
390+
365391
RunningServiceEnveloped:
366392
$ref: "../common/schemas/running_service.yaml#/components/schemas/RunningServiceEnveloped"
367393

api/specs/webserver/openapi.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ paths:
159159
/projects/{project_id}:open:
160160
$ref: "./openapi-projects.yaml#/paths/~1projects~1{project_id}~1open"
161161

162+
/projects/{project_id}/state:
163+
$ref: "./openapi-projects.yaml#/paths/~1projects~1{project_id}~1state"
164+
162165
/projects/{project_id}:close:
163166
$ref: "./openapi-projects.yaml#/paths/~1projects~1{project_id}~1close"
164167

mypy.ini

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
python_version = 3.6
44
warn_return_any = True
55
warn_unused_configs = True
6-
6+
namespace_packages = True
7+
; ignore_missing_imports = True
78
# Per-module options:
89
[mypy-aio-pika.*]
910
ignore_missing_imports = True
@@ -17,6 +18,8 @@ ignore_missing_imports = True
1718
ignore_missing_imports = True
1819
[mypy-aiosmtplib.*]
1920
ignore_missing_imports = True
21+
[mypy-aiozipkin.*]
22+
ignore_missing_imports = True
2023
[mypy-asyncpg.*]
2124
ignore_missing_imports = True
2225
[mypy-celery.*]
@@ -27,6 +30,10 @@ ignore_missing_imports = True
2730
ignore_missing_imports = True
2831
[mypy-jsondiff.*]
2932
ignore_missing_imports = True
33+
[mypy-jsonschema.*]
34+
ignore_missing_imports = True
35+
[mypy-openapi_core.*]
36+
ignore_missing_imports = True
3037
[mypy-passlib.*]
3138
ignore_missing_imports = True
3239
[mypy-prometheus_client.*]
@@ -39,3 +46,7 @@ ignore_missing_imports = True
3946
ignore_missing_imports = True
4047
[mypy-trafaret.*]
4148
ignore_missing_imports = True
49+
[mypy-trafaret_config.*]
50+
ignore_missing_imports = True
51+
[mypy-yarl.*]
52+
ignore_missing_imports = True

packages/postgres-database/src/simcore_postgres_database/cli.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@
1111
from copy import deepcopy
1212
from logging.config import fileConfig
1313
from pathlib import Path
14-
from typing import Dict
14+
from typing import Dict, Optional
1515

16-
import alembic.command
1716
import click
17+
18+
import alembic.command
1819
import docker
1920
from alembic import __version__ as __alembic_version__
2021
from alembic.config import Config as AlembicConfig
21-
2222
from simcore_postgres_database.models import *
2323
from simcore_postgres_database.utils import build_url, raise_if_not_responsive
2424

@@ -110,29 +110,28 @@ def _reset_cache():
110110
def main():
111111
""" Simplified CLI for database migration with alembic """
112112

113-
114113
@main.command()
115114
@click.option("--user", "-u")
116115
@click.option("--password", "-p")
117116
@click.option("--host")
118117
@click.option("--port", type=int)
119118
@click.option("--database", "-d")
120-
def discover(**cli_inputs) -> Dict:
119+
def discover(**cli_inputs) -> Optional[Dict]:
121120
""" Discovers databases and caches configs in ~/.simcore_postgres_database.json (except if --no-cache)"""
122121
# NOTE: Do not add defaults to user, password so we get a chance to ping urls
123122
# TODO: if multiple candidates online, then query user to select
124123

125124
click.echo("Discovering database ...")
126125
cli_cfg = {key: value for key, value in cli_inputs.items() if value is not None}
127126

128-
def _test_cached():
127+
def _test_cached() -> Dict:
129128
"""Tests cached configuration """
130129
cfg = _load_cache() or {}
131130
if cfg:
132131
cfg.update(cli_cfg) # overrides
133132
return cfg
134133

135-
def _test_env():
134+
def _test_env() -> Dict:
136135
"""Tests environ variables """
137136
cfg = {
138137
"user": os.getenv("POSTGRES_USER"),
@@ -144,7 +143,7 @@ def _test_env():
144143
cfg.update(cli_cfg)
145144
return cfg
146145

147-
def _test_swarm():
146+
def _test_swarm() -> Dict:
148147
"""Tests published port in swarm from host """
149148
cfg = _test_env()
150149
cfg["host"] = "127.0.0.1"
@@ -182,6 +181,7 @@ def _test_swarm():
182181

183182
_reset_cache()
184183
click.secho("Sorry, database not found !!", blink=False, bold=True, fg="red")
184+
return None
185185

186186

187187
@main.command()

packages/pytest-simcore/src/pytest_simcore/websocket_client.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111

1212
@pytest.fixture()
13-
async def security_cookie(loop, client) -> str:
13+
async def security_cookie_factory(loop, client) -> str:
1414
# get the cookie by calling the root entrypoint
1515
resp = await client.get("/v0/")
1616
payload = await resp.json()
@@ -32,17 +32,23 @@ async def socketio_url(loop, client) -> str:
3232

3333

3434
@pytest.fixture()
35-
async def socketio_client(socketio_url: str, security_cookie: str):
35+
async def socketio_client(
36+
socketio_url: str, security_cookie_factory: str
37+
) -> socketio.AsyncClient:
3638
clients = []
3739

3840
async def connect(client_session_id) -> socketio.AsyncClient:
39-
sio = socketio.AsyncClient(ssl_verify=False) # enginio 3.10.0 introduced ssl verification
40-
url = str(URL(socketio_url).with_query({'client_session_id': client_session_id}))
41+
sio = socketio.AsyncClient(
42+
ssl_verify=False
43+
) # enginio 3.10.0 introduced ssl verification
44+
url = str(
45+
URL(socketio_url).with_query({"client_session_id": client_session_id})
46+
)
4147

4248
headers = {}
43-
if security_cookie:
49+
if security_cookie_factory:
4450
# WARNING: engineio fails with empty cookies. Expects "key=value"
45-
headers.update({'Cookie': security_cookie})
51+
headers.update({"Cookie": security_cookie_factory})
4652

4753
await sio.connect(url, headers=headers)
4854
assert sio.sid

packages/service-library/src/servicelib/utils.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88
import logging
99
import os
1010
from pathlib import Path
11-
from typing import Any, Coroutine, List, Optional, Union
11+
from typing import Any, Awaitable, Coroutine, List, Optional, Union
1212

1313
logger = logging.getLogger(__name__)
1414

1515

1616
def is_production_environ() -> bool:
17-
"""
18-
If True, this code most probably
19-
runs in a production container of one of the
17+
"""
18+
If True, this code most probably
19+
runs in a production container of one of the
2020
osparc-simcore services.
2121
"""
2222
# WARNING: based on a convention that is not constantly verified
@@ -46,7 +46,9 @@ def search_osparc_repo_dir(start: Union[str, Path], max_iterations=8) -> Optiona
4646

4747

4848
# FUTURES
49-
def fire_and_forget_task(obj: Union[Coroutine, asyncio.Future]) -> asyncio.Future:
49+
def fire_and_forget_task(
50+
obj: Union[Coroutine, asyncio.Future, Awaitable]
51+
) -> asyncio.Future:
5052
future = asyncio.ensure_future(obj)
5153

5254
def log_exception_callback(fut: asyncio.Future):
@@ -63,7 +65,7 @@ def log_exception_callback(fut: asyncio.Future):
6365
async def logged_gather(
6466
*tasks, reraise: bool = True, log: logging.Logger = logger
6567
) -> List[Any]:
66-
"""
68+
"""
6769
*all* coroutine passed are executed concurrently and once they are all
6870
completed, the first error (if any) is reraised or all returned
6971

scripts/common.Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ autoformat: ## runs black python formatter on this service's code. Use AFTER mak
100100
--exclude "/(\.eggs|\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|\.svn|_build|buck-out|build|dist|migration|client-sdk|generated_code)/" \
101101
$(CURDIR)
102102

103+
.PHONY: mypy
104+
mypy: $(REPO_BASE_DIR)/scripts/mypy.bash $(REPO_BASE_DIR)/mypy.ini ## runs mypy python static type checker on this services's code. Use AFTER make install-*
105+
@$(REPO_BASE_DIR)/scripts/mypy.bash src
103106

104107
.PHONY: version-patch version-minor version-major
105108
version-patch: ## commits version with bug fixes not affecting the cookiecuter config

scripts/mypy.bash

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/bin/bash
2+
# http://redsymbol.net/articles/unofficial-bash-strict-mode/
3+
set -o errexit
4+
set -o nounset
5+
set -o pipefail
6+
IFS=$'\n\t'
7+
8+
target_path=$(realpath ${1:-Please give target path as argument})
9+
cd "$(dirname "$0")"
10+
default_mypy_config="$(dirname ${PWD})/mypy.ini"
11+
mypy_config=$(realpath ${2:-${default_mypy_config}})
12+
13+
echo mypying ${target_path} using config in ${mypy_config}...
14+
15+
echo $default_mypy_config
16+
docker run --rm \
17+
-v ${mypy_config}:/config/mypy.ini \
18+
-v ${target_path}:/src \
19+
--workdir=/src \
20+
kiwicom/mypy mypy \
21+
--config-file /config/mypy.ini \
22+
/src

services/web/client/source/class/osparc/component/export/Permissions.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ qx.Class.define("osparc.component.export.Permissions", {
157157
ctrl.bindProperty("login", "subtitle", null, item, id); // user
158158
ctrl.bindProperty("description", "subtitle", null, item, id); // organization
159159
ctrl.bindProperty("isOrg", "isOrganization", null, item, id);
160-
ctrl.bindProperty("access_rights", "accessRights", null, item, id);
160+
ctrl.bindProperty("accessRights", "accessRights", null, item, id);
161161
ctrl.bindProperty("showOptions", "showOptions", null, item, id);
162162
},
163163
configureItem: item => {
@@ -229,7 +229,7 @@ qx.Class.define("osparc.component.export.Permissions", {
229229
collaborator["thumbnail"] = osparc.utils.Avatar.getUrl(collaborator["login"], 32);
230230
collaborator["name"] = osparc.utils.Utils.firstsUp(collaborator["first_name"], collaborator["last_name"]);
231231
}
232-
collaborator["access_rights"] = aceessRights[gid];
232+
collaborator["accessRights"] = aceessRights[gid];
233233
if (this.__isUserOwner()) {
234234
collaborator["showOptions"] = true;
235235
}

0 commit comments

Comments
 (0)