Skip to content

Commit

Permalink
Merge pull request #27 from nebulabroadcast/develop
Browse files Browse the repository at this point in the history
Nebula 6.0.0-RC1
  • Loading branch information
martastain committed Apr 26, 2023
2 parents d571098 + 5136edf commit de90902
Show file tree
Hide file tree
Showing 101 changed files with 3,362 additions and 1,424 deletions.
76 changes: 0 additions & 76 deletions .github/workflows/codeql.yml

This file was deleted.

16 changes: 7 additions & 9 deletions .github/workflows/docker.yml → .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Publish docker image
name: Publish a new version

on:
push:
Expand All @@ -10,6 +10,12 @@ jobs:

steps:
- uses: actions/checkout@v3
- uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Get the version
id: get_version
Expand All @@ -18,14 +24,6 @@ jobs:
file: 'backend/pyproject.toml'
field: 'tool.poetry.version'

- uses: docker/setup-buildx-action@v2

- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build docker image
uses: docker/build-push-action@v4
with:
Expand Down
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
IMAGE_NAME=nebulabroadcast/nebula-server:latest
VERSION=$(shell cd backend && poetry run python -c 'import nebula' --version)

test:
check: check_version
cd frontend && yarn format

cd backend && \
Expand All @@ -9,6 +10,10 @@ test:
poetry run flake8 . && \
poetry run mypy .

check_version:
echo $(VERSION)
sed -i "s/^version = \".*\"/version = \"$(VERSION)\"/" backend/pyproject.toml

build:
docker build -t $(IMAGE_NAME) .

Expand Down
52 changes: 47 additions & 5 deletions backend/api/auth.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from fastapi import Header
from fastapi import Depends, Header, Response
from pydantic import Field

import nebula
from nebula.exceptions import UnauthorizedException
from server.dependencies import current_user
from server.models import RequestModel, ResponseModel
from server.request import APIRequest
from server.session import Session
Expand Down Expand Up @@ -37,6 +37,11 @@ class LoginResponseModel(ResponseModel):
)


class PasswordRequestModel(RequestModel):
login: str | None = Field(None, title="Login", example="admin")
password: str = Field(..., title="Password", example="Password.123")


#
# Request
#
Expand All @@ -62,12 +67,49 @@ class LogoutRequest(APIRequest):

async def handle(self, authorization: str | None = Header(None)):
if not authorization:
raise UnauthorizedException("No authorization header provided")
raise nebula.UnauthorizedException("No authorization header provided")

access_token = parse_access_token(authorization)
if not access_token:
raise UnauthorizedException("Invalid authorization header provided")
raise nebula.UnauthorizedException("Invalid authorization header provided")

await Session.delete(access_token)

raise UnauthorizedException("Logged out")
raise nebula.UnauthorizedException("Logged out")


class SetPassword(APIRequest):
"""Set a new password for the current (or a given) user.
In order to set a password for another user, the current user must be an admin.
"""

name: str = "password"
title: str = "Set password"

async def handle(
self,
request: PasswordRequestModel,
user: nebula.User = Depends(current_user),
):
if request.login:
if not user.is_admin:
raise nebula.UnauthorizedException(
"Only admin can change other user's password"
)
query = "SELECT meta FROM users WHERE login = $1"
async for row in nebula.db.iterate(query, request.login):
target_user = nebula.User.from_row(row)
break
else:
raise nebula.NotFoundException(f"User {request.login} not found")
else:
target_user = user

if len(request.password) < 8:
raise nebula.BadRequestException("Password is too short")

target_user.set_password(request.password)
await target_user.save()

return Response(status_code=204)
25 changes: 15 additions & 10 deletions backend/api/browse.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from typing import Any, Literal

from fastapi import Depends
from nxtools import slugify
from pydantic import Field

Expand All @@ -9,7 +8,7 @@
from nebula.enum import MetaClass
from nebula.exceptions import NebulaException
from nebula.metadata.normalize import normalize_meta
from server.dependencies import current_user
from server.dependencies import CurrentUser
from server.models import RequestModel, ResponseModel
from server.request import APIRequest

Expand All @@ -19,6 +18,8 @@
REQUIRED_COLUMNS = [
"id",
"id_folder",
"title",
"subtitle",
"status",
"content_type",
"media_type",
Expand Down Expand Up @@ -109,17 +110,19 @@ def sanitize_value(value: Any) -> Any:
def build_conditions(conditions: list[ConditionModel]) -> list[str]:
cond_list: list[str] = []
for condition in conditions:
assert condition.key in nebula.settings.metatypes
assert (
condition.key in nebula.settings.metatypes
), f"Invalid meta key {condition.key}"
condition.value = normalize_meta(condition.key, condition.value)
if condition.operator in ["IN", "NOT IN"]:
assert type(condition.value) is list
assert type(condition.value) is list, "Value must be a list"
values = sql_list([sanitize_value(v) for v in condition.value], t="str")
cond_list.append(f"meta->>'{condition.key}' {condition.operator} {values}")
elif condition.operator in ["IS NULL", "IS NOT NULL"]:
cond_list.append(f"meta->>'{condition.key}' {condition.operator}")
else:
value = sanitize_value(condition.value)
assert value
assert value, "Value must not be empty"
# TODO casting to numbers for <, >, <=, >=
cond_list.append(f"meta->>'{condition.key}' {condition.operator} '{value}'")
return cond_list
Expand Down Expand Up @@ -188,7 +191,7 @@ def build_query(
# Process views

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

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

# Access control

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

# Build conditions

Expand Down Expand Up @@ -253,12 +258,12 @@ class Request(APIRequest):
async def handle(
self,
request: BrowseRequestModel,
user: nebula.User = Depends(current_user),
user: CurrentUser,
) -> BrowseResponseModel:

columns: list[str] = ["title", "duration"]
if request.view is not None and not request.columns:
assert type(request.view) is int
assert type(request.view) is int, "View must be an integer"
if (view := nebula.settings.get_view(request.view)) is not None:
if view.columns is not None:
columns = view.columns
Expand Down
12 changes: 6 additions & 6 deletions backend/api/delete.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from fastapi import Depends, Response
from fastapi import Response
from pydantic import Field

import nebula
from nebula.enum import ObjectType
from nebula.helpers.scheduling import bin_refresh
from nebula.objects.utils import get_object_class_by_name
from server.dependencies import current_user, request_initiator
from server.dependencies import CurrentUser, RequestInitiator
from server.models import RequestModel
from server.request import APIRequest

Expand All @@ -30,8 +30,8 @@ class Request(APIRequest):
async def handle(
self,
request: DeleteRequestModel,
user: nebula.User = Depends(current_user),
initiator: str | None = Depends(request_initiator),
user: CurrentUser,
initiator: RequestInitiator,
) -> Response:
"""Delete given objects."""

Expand All @@ -58,10 +58,10 @@ async def handle(
)

case ObjectType.ASSET | ObjectType.EVENT:
# TODO: ACL HERE
# TODO: ACL HERE?
# In general, normal users don't need to
# delete assets or events directly
if not user["is_admin"]:
if not user.is_admin:
raise nebula.ForbiddenException(
"You are not allowed to delete this object"
)
Expand Down
Loading

0 comments on commit de90902

Please sign in to comment.