Skip to content

Commit de90902

Browse files
authored
Merge pull request #27 from nebulabroadcast/develop
Nebula 6.0.0-RC1
2 parents d571098 + 5136edf commit de90902

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

101 files changed

+3362
-1424
lines changed

.github/workflows/codeql.yml

Lines changed: 0 additions & 76 deletions
This file was deleted.

.github/workflows/docker.yml renamed to .github/workflows/release.yml

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Publish docker image
1+
name: Publish a new version
22

33
on:
44
push:
@@ -10,6 +10,12 @@ jobs:
1010

1111
steps:
1212
- uses: actions/checkout@v3
13+
- uses: docker/setup-buildx-action@v2
14+
- name: Login to Docker Hub
15+
uses: docker/login-action@v2
16+
with:
17+
username: ${{ secrets.DOCKERHUB_USERNAME }}
18+
password: ${{ secrets.DOCKERHUB_TOKEN }}
1319

1420
- name: Get the version
1521
id: get_version
@@ -18,14 +24,6 @@ jobs:
1824
file: 'backend/pyproject.toml'
1925
field: 'tool.poetry.version'
2026

21-
- uses: docker/setup-buildx-action@v2
22-
23-
- name: Login to Docker Hub
24-
uses: docker/login-action@v2
25-
with:
26-
username: ${{ secrets.DOCKERHUB_USERNAME }}
27-
password: ${{ secrets.DOCKERHUB_TOKEN }}
28-
2927
- name: Build docker image
3028
uses: docker/build-push-action@v4
3129
with:

Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
IMAGE_NAME=nebulabroadcast/nebula-server:latest
2+
VERSION=$(shell cd backend && poetry run python -c 'import nebula' --version)
23

3-
test:
4+
check: check_version
45
cd frontend && yarn format
56

67
cd backend && \
@@ -9,6 +10,10 @@ test:
910
poetry run flake8 . && \
1011
poetry run mypy .
1112

13+
check_version:
14+
echo $(VERSION)
15+
sed -i "s/^version = \".*\"/version = \"$(VERSION)\"/" backend/pyproject.toml
16+
1217
build:
1318
docker build -t $(IMAGE_NAME) .
1419

backend/api/auth.py

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from fastapi import Header
1+
from fastapi import Depends, Header, Response
22
from pydantic import Field
33

44
import nebula
5-
from nebula.exceptions import UnauthorizedException
5+
from server.dependencies import current_user
66
from server.models import RequestModel, ResponseModel
77
from server.request import APIRequest
88
from server.session import Session
@@ -37,6 +37,11 @@ class LoginResponseModel(ResponseModel):
3737
)
3838

3939

40+
class PasswordRequestModel(RequestModel):
41+
login: str | None = Field(None, title="Login", example="admin")
42+
password: str = Field(..., title="Password", example="Password.123")
43+
44+
4045
#
4146
# Request
4247
#
@@ -62,12 +67,49 @@ class LogoutRequest(APIRequest):
6267

6368
async def handle(self, authorization: str | None = Header(None)):
6469
if not authorization:
65-
raise UnauthorizedException("No authorization header provided")
70+
raise nebula.UnauthorizedException("No authorization header provided")
6671

6772
access_token = parse_access_token(authorization)
6873
if not access_token:
69-
raise UnauthorizedException("Invalid authorization header provided")
74+
raise nebula.UnauthorizedException("Invalid authorization header provided")
7075

7176
await Session.delete(access_token)
7277

73-
raise UnauthorizedException("Logged out")
78+
raise nebula.UnauthorizedException("Logged out")
79+
80+
81+
class SetPassword(APIRequest):
82+
"""Set a new password for the current (or a given) user.
83+
84+
In order to set a password for another user, the current user must be an admin.
85+
"""
86+
87+
name: str = "password"
88+
title: str = "Set password"
89+
90+
async def handle(
91+
self,
92+
request: PasswordRequestModel,
93+
user: nebula.User = Depends(current_user),
94+
):
95+
if request.login:
96+
if not user.is_admin:
97+
raise nebula.UnauthorizedException(
98+
"Only admin can change other user's password"
99+
)
100+
query = "SELECT meta FROM users WHERE login = $1"
101+
async for row in nebula.db.iterate(query, request.login):
102+
target_user = nebula.User.from_row(row)
103+
break
104+
else:
105+
raise nebula.NotFoundException(f"User {request.login} not found")
106+
else:
107+
target_user = user
108+
109+
if len(request.password) < 8:
110+
raise nebula.BadRequestException("Password is too short")
111+
112+
target_user.set_password(request.password)
113+
await target_user.save()
114+
115+
return Response(status_code=204)

backend/api/browse.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from typing import Any, Literal
22

3-
from fastapi import Depends
43
from nxtools import slugify
54
from pydantic import Field
65

@@ -9,7 +8,7 @@
98
from nebula.enum import MetaClass
109
from nebula.exceptions import NebulaException
1110
from nebula.metadata.normalize import normalize_meta
12-
from server.dependencies import current_user
11+
from server.dependencies import CurrentUser
1312
from server.models import RequestModel, ResponseModel
1413
from server.request import APIRequest
1514

@@ -19,6 +18,8 @@
1918
REQUIRED_COLUMNS = [
2019
"id",
2120
"id_folder",
21+
"title",
22+
"subtitle",
2223
"status",
2324
"content_type",
2425
"media_type",
@@ -109,17 +110,19 @@ def sanitize_value(value: Any) -> Any:
109110
def build_conditions(conditions: list[ConditionModel]) -> list[str]:
110111
cond_list: list[str] = []
111112
for condition in conditions:
112-
assert condition.key in nebula.settings.metatypes
113+
assert (
114+
condition.key in nebula.settings.metatypes
115+
), f"Invalid meta key {condition.key}"
113116
condition.value = normalize_meta(condition.key, condition.value)
114117
if condition.operator in ["IN", "NOT IN"]:
115-
assert type(condition.value) is list
118+
assert type(condition.value) is list, "Value must be a list"
116119
values = sql_list([sanitize_value(v) for v in condition.value], t="str")
117120
cond_list.append(f"meta->>'{condition.key}' {condition.operator} {values}")
118121
elif condition.operator in ["IS NULL", "IS NOT NULL"]:
119122
cond_list.append(f"meta->>'{condition.key}' {condition.operator}")
120123
else:
121124
value = sanitize_value(condition.value)
122-
assert value
125+
assert value, "Value must not be empty"
123126
# TODO casting to numbers for <, >, <=, >=
124127
cond_list.append(f"meta->>'{condition.key}' {condition.operator} '{value}'")
125128
return cond_list
@@ -188,7 +191,7 @@ def build_query(
188191
# Process views
189192

190193
if request.view is not None and not request.ignore_view_conditions:
191-
assert type(request.view) is int
194+
assert type(request.view) is int, "View must be an integer"
192195
if (view := nebula.settings.get_view(request.view)) is not None:
193196
if view.folders:
194197
cond_list.append(f"id_folder IN {sql_list(view.folders)}")
@@ -207,14 +210,16 @@ def build_query(
207210
# Process full text
208211

209212
if request.query:
210-
for elm in slugify(request.query, make_set=True):
213+
for elm in slugify(request.query, make_set=True, min_length=3):
211214
# no need to sanitize this. slugified strings are safe
212215
cond_list.append(f"id IN (SELECT id FROM ft WHERE value LIKE '{elm}%')")
213216

214217
# Access control
215218

216219
if user.is_limited:
217-
cond_list.append(f"meta->>'created_by' = '{user.id}'")
220+
c1 = f"meta->>'created_by' = '{user.id}'"
221+
c2 = f"meta->'assignees' @> '[{user.id}]'::JSONB"
222+
cond_list.append(f"({c1} OR {c2})")
218223

219224
# Build conditions
220225

@@ -253,12 +258,12 @@ class Request(APIRequest):
253258
async def handle(
254259
self,
255260
request: BrowseRequestModel,
256-
user: nebula.User = Depends(current_user),
261+
user: CurrentUser,
257262
) -> BrowseResponseModel:
258263

259264
columns: list[str] = ["title", "duration"]
260265
if request.view is not None and not request.columns:
261-
assert type(request.view) is int
266+
assert type(request.view) is int, "View must be an integer"
262267
if (view := nebula.settings.get_view(request.view)) is not None:
263268
if view.columns is not None:
264269
columns = view.columns

backend/api/delete.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
from fastapi import Depends, Response
1+
from fastapi import Response
22
from pydantic import Field
33

44
import nebula
55
from nebula.enum import ObjectType
66
from nebula.helpers.scheduling import bin_refresh
77
from nebula.objects.utils import get_object_class_by_name
8-
from server.dependencies import current_user, request_initiator
8+
from server.dependencies import CurrentUser, RequestInitiator
99
from server.models import RequestModel
1010
from server.request import APIRequest
1111

@@ -30,8 +30,8 @@ class Request(APIRequest):
3030
async def handle(
3131
self,
3232
request: DeleteRequestModel,
33-
user: nebula.User = Depends(current_user),
34-
initiator: str | None = Depends(request_initiator),
33+
user: CurrentUser,
34+
initiator: RequestInitiator,
3535
) -> Response:
3636
"""Delete given objects."""
3737

@@ -58,10 +58,10 @@ async def handle(
5858
)
5959

6060
case ObjectType.ASSET | ObjectType.EVENT:
61-
# TODO: ACL HERE
61+
# TODO: ACL HERE?
6262
# In general, normal users don't need to
6363
# delete assets or events directly
64-
if not user["is_admin"]:
64+
if not user.is_admin:
6565
raise nebula.ForbiddenException(
6666
"You are not allowed to delete this object"
6767
)

0 commit comments

Comments
 (0)