Skip to content

Commit 738a63c

Browse files
sampsapennaJoonatan Mäkinen
authored and
Joonatan Mäkinen
committed
refactor database clients to use a common base
1 parent ea3dd26 commit 738a63c

File tree

11 files changed

+404
-473
lines changed

11 files changed

+404
-473
lines changed

.github/config/.wordlist.txt

+3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ configs
1919
containerport
2020
cp
2121
cryptfiles
22+
cryptupload
2223
csc
2324
cscfi
2425
decrypt
@@ -54,6 +55,7 @@ lt
5455
macos
5556
matchlabels
5657
maxdepth
58+
middleware
5759
middlewares
5860
namespace
5961
navbar
@@ -106,6 +108,7 @@ ui
106108
untrusted
107109
url
108110
usr
111+
util
109112
venv
110113
veryfast
111114
vm

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ docs/source/swift_browser_ui.ui
7070
docs/source/swift_browser_ui.sharing
7171
docs/source/swift_browser_ui.request
7272
docs/source/swift_browser_ui.upload
73+
docs/source/swift_browser_ui.common
7374

7475
# PyBuilder
7576
target/

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
116116
- (GL #1074) Updated `/api/{project}` endpoint to check for `last_modified` values before returning the container list
117117
- (GL #1105) Upload modal restricted to uploading to current container, or creating a new one in main container view
118118
- (GL #1028) Disallow uploading files with size 0
119+
- Use common base class for database connections
119120

120121
### Fixed
121122

docs/source/code.rst

+15-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ Python Modules
3131

3232
swift_browser_ui.sharing.bindings.bind
3333
swift_browser_ui.sharing.api
34-
swift_browser_ui.sharing.db
3534
swift_browser_ui.sharing.server
3635
swift_browser_ui.sharing.shared
3736

@@ -44,7 +43,6 @@ Python Modules
4443

4544
swift_browser_ui.request.bindings.bind
4645
swift_browser_ui.request.api
47-
swift_browser_ui.request.db
4846
swift_browser_ui.request.server
4947

5048
.. automodule:: swift_browser_ui.upload
@@ -61,6 +59,21 @@ Python Modules
6159
swift_browser_ui.upload.replicate
6260
swift_browser_ui.upload.server
6361
swift_browser_ui.upload.upload
62+
swift_browser_ui.upload.cryptupload
6463

64+
.. automodule:: swift_browser_ui.common
65+
:synopsis: The ``swift_browser_ui.common`` package contains code shared
66+
between the different services.
67+
68+
.. autosummary::
69+
:toctree: swift_browser_ui.common
70+
71+
swift_browser_ui.common.common_handlers
72+
swift_browser_ui.common.common_middleware
73+
swift_browser_ui.common.common_util
74+
swift_browser_ui.common.db
75+
swift_browser_ui.common.signature
76+
swift_browser_ui.common.types
77+
swift_browser_ui.common.vault_client
6578

6679
:ref:`genindex` | :ref:`modindex`

swift_browser_ui/common/common_middleware.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ async def handle_validate_authentication(
9292
except KeyError:
9393
project = None
9494
finally:
95-
if project:
95+
if project and "db_conn" in request.app:
9696
try:
9797
project_tokens = [
9898
rec["token"].encode("utf-8")
@@ -102,8 +102,10 @@ async def handle_validate_authentication(
102102
pass
103103
else:
104104
if request.path != "/health":
105-
LOGGER.debug(f"No project ID found in request {request}")
106-
raise aiohttp.web.HTTPUnauthorized(reason="No project ID in request")
105+
LOGGER.debug(
106+
f"No project ID found in request {request}, "
107+
"assuming scopeless authentication and skipping tokens."
108+
)
107109

108110
await swift_browser_ui.common.signature.test_signature(
109111
request.app["tokens"] + project_tokens, signature, validity + path, validity

swift_browser_ui/sharing/db.py swift_browser_ui/common/db.py

+199-40
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
"""Sharing backend database implementation."""
1+
"""Module for database interfaces using postgres."""
22

33

44
import logging
55
import os
66
import typing
77

8+
import aiohttp.web
89
import asyncpg
910

1011
from swift_browser_ui.common.common_util import sleep_random
@@ -13,8 +14,20 @@
1314
MODULE_LOGGER.setLevel(os.environ.get("LOG_LEVEL", "INFO"))
1415

1516

16-
class DBConn:
17-
"""Class for the account sharing database functionality."""
17+
async def db_graceful_start(app: aiohttp.web.Application) -> None:
18+
"""Gracefully start the database."""
19+
app["db_conn"] = app["db_class"]()
20+
await app["db_conn"].open()
21+
22+
23+
async def db_graceful_close(app: aiohttp.web.Application) -> None:
24+
"""Gracefully close the database."""
25+
if app["db_conn"] is not None:
26+
await app["db_conn"].close()
27+
28+
29+
class BaseDBConn:
30+
"""Class for base database connection."""
1831

1932
def __init__(self) -> None:
2033
"""Initialize connection variable."""
@@ -27,27 +40,16 @@ def erase(self) -> None:
2740
self.pool.terminate()
2841
self.pool = None
2942

30-
async def open(self) -> None:
43+
async def close(self) -> None:
44+
"""Safely close the database connection."""
45+
if self.pool is not None:
46+
await self.pool.close()
47+
48+
async def _open(self, **kwargs) -> None:
3149
"""Initialize the database connection."""
3250
while self.pool is None:
3351
try:
34-
self.pool = await asyncpg.create_pool(
35-
password=os.environ.get("SHARING_DB_PASSWORD", None),
36-
user=os.environ.get("SHARING_DB_USER", "sharing"),
37-
host=os.environ.get("SHARING_DB_HOST", "localhost"),
38-
port=int(os.environ.get("SHARING_DB_PORT", 5432)),
39-
ssl=os.environ.get("SHARING_DB_SSL", "prefer"),
40-
database=os.environ.get("SHARING_DB_NAME", "swiftbrowserdb"),
41-
min_size=int(os.environ.get("SHARING_DB_MIN_CONNECTIONS", 0)),
42-
max_size=int(os.environ.get("SHARING_DB_MAX_CONNECTIONS", 2)),
43-
timeout=int(os.environ.get("SHARING_DB_TIMEOUT", 120)),
44-
command_timeout=int(
45-
os.environ.get("SHARING_DB_COMMAND_TIMEOUT", 180)
46-
),
47-
max_inactive_connection_lifetime=int(
48-
os.environ.get("SHARING_DB_MAX_INACTIVE_CONN_LIFETIME", 10)
49-
),
50-
)
52+
self.pool = await asyncpg.create_pool(**kwargs)
5153
except (ConnectionError, OSError):
5254
self.log.error(
5355
"Failed to establish connection. "
@@ -61,10 +63,47 @@ async def open(self) -> None:
6163
self.log.error("Database is not ready yet.")
6264
await sleep_random()
6365

64-
async def close(self) -> None:
65-
"""Safely close the database connection."""
66+
async def get_tokens(
67+
self, token_owner: str
68+
) -> typing.List[typing.Dict[str, typing.Any]]:
69+
"""Get tokens created for a project."""
6670
if self.pool is not None:
67-
await self.pool.close()
71+
query = await self.pool.fetch(
72+
"""SELECT *
73+
FROM Tokens
74+
WHERE token_owner = $1
75+
;
76+
""",
77+
token_owner,
78+
)
79+
return list(query)
80+
return []
81+
82+
83+
class SharingDBConn(BaseDBConn):
84+
"""Class for the account sharing database functionality."""
85+
86+
def __init__(self) -> None:
87+
"""Initialize connection variable."""
88+
super().__init__()
89+
90+
async def open(self) -> None:
91+
"""Initialize the database connection."""
92+
await super()._open(
93+
password=os.environ.get("SHARING_DB_PASSWORD", None),
94+
user=os.environ.get("SHARING_DB_USER", "sharing"),
95+
host=os.environ.get("SHARING_DB_HOST", "localhost"),
96+
port=int(os.environ.get("SHARING_DB_PORT", 5432)),
97+
ssl=os.environ.get("SHARING_DB_SSL", "prefer"),
98+
database=os.environ.get("SHARING_DB_NAME", "swiftbrowserdb"),
99+
min_size=int(os.environ.get("SHARING_DB_MIN_CONNECTIONS", 0)),
100+
max_size=int(os.environ.get("SHARING_DB_MAX_CONNECTIONS", 2)),
101+
timeout=int(os.environ.get("SHARING_DB_TIMEOUT", 120)),
102+
command_timeout=int(os.environ.get("SHARING_DB_COMMAND_TIMEOUT", 180)),
103+
max_inactive_connection_lifetime=int(
104+
os.environ.get("SHARING_DB_MAX_INACTIVE_CONN_LIFETIME", 10)
105+
),
106+
)
68107

69108
async def add_share(
70109
self,
@@ -311,22 +350,6 @@ async def get_shared_container_details(
311350
return ret
312351
return []
313352

314-
async def get_tokens(
315-
self, token_owner: str
316-
) -> typing.List[typing.Dict[str, typing.Any]]:
317-
"""Get tokens created for a project."""
318-
if self.pool is not None:
319-
query = await self.pool.fetch(
320-
"""SELECT *
321-
FROM Tokens
322-
WHERE token_owner = $1
323-
;
324-
""",
325-
token_owner,
326-
)
327-
return list(query)
328-
return []
329-
330353
async def revoke_token(self, token_owner: str, token_identifier: str) -> None:
331354
"""Remove a token from the database."""
332355
if self.pool is not None:
@@ -415,3 +438,139 @@ async def match_name_id(self, name: str) -> list:
415438
return list(query)
416439

417440
return []
441+
442+
443+
class RequestDBConn(BaseDBConn):
444+
"""Class for handling sharing request database connection."""
445+
446+
def __init__(self) -> None:
447+
"""."""
448+
super().__init__()
449+
450+
async def open(self) -> None:
451+
"""Gracefully open the database."""
452+
await super()._open(
453+
password=os.environ.get("REQUEST_DB_PASSWORD", None),
454+
user=os.environ.get("REQUEST_DB_USER", "request"),
455+
host=os.environ.get("REQUEST_DB_HOST", "localhost"),
456+
port=int(os.environ.get("REQUEST_DB_PORT", 5432)),
457+
ssl=os.environ.get("REQUEST_DB_SSL", "prefer"),
458+
database=os.environ.get("REQUEST_DB_NAME", "swiftbrowserdb"),
459+
min_size=int(os.environ.get("REQUEST_DB_MIN_CONNECTIONS", 0)),
460+
max_size=int(os.environ.get("REQUEST_DB_MAX_CONNECTIONS", 49)),
461+
timeout=int(os.environ.get("REQUEST_DB_TIMEOUT", 120)),
462+
command_timeout=int(os.environ.get("REQUEST_DB_COMMAND_TIMEOUT", 180)),
463+
max_inactive_connection_lifetime=int(
464+
os.environ.get("REQUEST_DB_MAX_INACTIVE_CONN_LIFETIME", 0)
465+
),
466+
)
467+
468+
@staticmethod
469+
async def parse_query(
470+
query: typing.List[asyncpg.Record],
471+
) -> typing.List[typing.Dict[str, typing.Any]]:
472+
"""Parse a database query list to JSON serializable form."""
473+
return [
474+
{
475+
"container": rec["container"],
476+
"user": rec["recipient"],
477+
"owner": rec["container_owner"],
478+
"date": rec["created"].isoformat(),
479+
}
480+
for rec in query
481+
]
482+
483+
async def add_request(self, user: str, container: str, owner: str) -> bool:
484+
"""Add an access request to the database."""
485+
if self.pool is not None:
486+
async with self.pool.acquire() as conn:
487+
async with conn.transaction():
488+
await conn.execute(
489+
"""
490+
INSERT INTO Requests(
491+
container,
492+
container_owner,
493+
recipient,
494+
created
495+
) VALUES (
496+
$1, $2, $3, NOW()
497+
);
498+
""",
499+
container,
500+
owner,
501+
user,
502+
)
503+
return True
504+
return False
505+
506+
async def get_request_owned(
507+
self, user: str
508+
) -> typing.List[typing.Dict[str, typing.Any]]:
509+
"""Get the requests owned by the getter."""
510+
if self.pool is not None:
511+
query = await self.pool.fetch(
512+
"""
513+
SELECT *
514+
FROM Requests
515+
WHERE container_owner = $1
516+
;
517+
""",
518+
user,
519+
)
520+
return await self.parse_query(query)
521+
return []
522+
523+
async def get_request_made(
524+
self, user: str
525+
) -> typing.List[typing.Dict[str, typing.Any]]:
526+
"""Get the requests made by the getter."""
527+
if self.pool is not None:
528+
query = await self.pool.fetch(
529+
"""
530+
SELECT *
531+
FROM Requests
532+
WHERE recipient = $1
533+
;
534+
""",
535+
user,
536+
)
537+
return await self.parse_query(query)
538+
return []
539+
540+
async def get_request_container(
541+
self, container: str
542+
) -> typing.List[typing.Dict[str, typing.Any]]:
543+
"""Get the requests made for a container."""
544+
if self.pool is not None:
545+
query = await self.pool.fetch(
546+
"""
547+
SELECT *
548+
FROM Requests
549+
WHERE container = $1
550+
;
551+
""",
552+
container,
553+
)
554+
return await self.parse_query(query)
555+
return []
556+
557+
async def delete_request(self, container: str, owner: str, recipient: str) -> bool:
558+
"""Delete an access request from the database."""
559+
if self.pool is not None:
560+
async with self.pool.acquire() as conn:
561+
async with conn.transaction():
562+
await conn.execute(
563+
"""
564+
DELETE FROM Requests
565+
WHERE
566+
container = $1 AND
567+
container_owner = $2 AND
568+
recipient = $3
569+
;
570+
""",
571+
container,
572+
owner,
573+
recipient,
574+
)
575+
return True
576+
return False

0 commit comments

Comments
 (0)