Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8ac3dd3
change imports to os_authlib
boehlke Sep 24, 2024
8b3b009
Cleanup saml and auth service based code
boehlke Sep 25, 2024
8abec7b
cleanup
boehlke Sep 25, 2024
54c3b2c
Use pip-auth libraries code directly from local source
boehlke Sep 25, 2024
27594ea
Implement back-channel logout endpoint
boehlke Sep 27, 2024
48ad18f
Change build scripts so that fullstack feature use-case (wiring own l…
boehlke Oct 14, 2024
f374182
Add stages for normal dev deps and fullstack dep resolution (local)
boehlke Oct 14, 2024
bd55095
KRY-149 migration script
boehlke Nov 4, 2024
efdc419
Work on deeper authlib integration
boehlke Oct 7, 2024
68549f9
Replace os_authlib with authlib
boehlke Oct 10, 2024
e0687e0
Work on deeper integration of keycloak and dev reviews
boehlke Nov 4, 2024
92bd338
WIP: Work on keycloak service for migration and other features needin…
boehlke Nov 6, 2024
7d0685c
Revert
boehlke Nov 10, 2024
d21d2e2
Scatch implementation of idp migration
boehlke Nov 10, 2024
dca8b9f
Work on actions
boehlke Nov 12, 2024
21b797c
Work on keycloak integration
boehlke Dec 10, 2024
e48e5fc
Re-add actions
boehlke Dec 12, 2024
40c373f
Merge remote-tracking branch 'refs/remotes/source/main' into KRY-149
boehlke Dec 20, 2024
699a693
Work on keycloak authenticator
boehlke Dec 23, 2024
6e1f381
Work on keycloak authenticator
boehlke Jan 8, 2025
115560f
Work on keycloak integration
boehlke Sep 25, 2024
e4e35ba
deactivate migration, merge main
boehlke Jan 10, 2025
daf7c1f
Fix merge bug
boehlke Jan 10, 2025
536cf50
Update fastjsonschema dependency
boehlke Jan 16, 2025
9c1bdff
Merge branch 'refs/heads/src-main'
boehlke Jan 21, 2025
35268e9
Work on keycloak integration
boehlke Jan 30, 2025
1b7aa09
Merge remote-tracking branch 'os/main'
boehlke Jan 30, 2025
e12f121
update migration index
boehlke Feb 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -144,5 +144,12 @@ run-dev-otel run-bash-otel: | start-dev-otel run-dev-attach-otel
build-dev:
docker build --file=dev/Dockerfile.dev . --tag=openslides-backend-dev

build-dev-fullstack:
DOCKER_BUILDKIT=1 docker build --file=dev/Dockerfile.dev . \
--build-arg=REQUIREMENTS_FILE=requirements_development_fullstack.txt \
--build-context pipauth=../openslides-auth-service/libraries/pip-auth \
--build-context datastore=../openslides-datastore-service \
--tag=openslides-backend-dev-fullstack

rebuild-dev:
docker build --file=dev/Dockerfile.dev . --tag=openslides-backend-dev --no-cache
docker build --file=dev/Dockerfile.dev . --target development --tag=openslides-backend-dev --no-cache
2 changes: 1 addition & 1 deletion data/example-data.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"_migration_index": 63,
"_migration_index": 64,
"gender":{
"1":{
"id": 1,
Expand Down
2 changes: 1 addition & 1 deletion data/initial-data.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"_migration_index": 63,
"_migration_index": 64,
"gender":{
"1":{
"id": 1,
Expand Down
20 changes: 15 additions & 5 deletions dev/Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
FROM python:3.10.13-slim-bookworm
FROM python:3.10.13-slim-bookworm as base

RUN apt-get update && apt-get install --yes make git curl ncat vim bash-completion mime-support gcc libpq-dev libmagic1

WORKDIR /app

COPY requirements/ requirements/
ARG REQUIREMENTS_FILE=requirements_development.txt
RUN . requirements/export_service_commits.sh && pip install --no-cache-dir --requirement requirements/$REQUIREMENTS_FILE

COPY dev/.bashrc .
COPY dev/cleanup.sh .

Expand Down Expand Up @@ -43,3 +39,17 @@ ENV DEFAULT_FROM_EMAIL noreply@example.com
STOPSIGNAL SIGKILL
ENTRYPOINT ["./entrypoint.sh"]
CMD exec python -m debugpy --listen 0.0.0.0:5678 openslides_backend

FROM base AS development

COPY requirements/ requirements/
ARG REQUIREMENTS_FILE=requirements_development.txt
RUN . requirements/export_service_commits.sh && pip install --no-cache-dir --requirement requirements/$REQUIREMENTS_FILE

FROM base AS development-fullstack

COPY --from=pipauth / /pip-auth
COPY --from=datastore / /openslides-datastore-service
COPY requirements/ requirements/
ARG REQUIREMENTS_FILE=requirements_development.txt
RUN . requirements/export_service_commits.sh && pip install --no-cache-dir --requirement requirements/$REQUIREMENTS_FILE
5 changes: 1 addition & 4 deletions dev/dc.local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ services:
- REQUIREMENTS_FILE=requirements_development_local.txt
volumes:
- ../../openslides-datastore-service/:/datastore-service
- ../../openslides-auth-service/auth/libraries/pip-auth/:/authlib
- ../../openslides-auth-service/libraries/pip-auth/:/authlib
environment:
- PYTHONPATH=/app:/datastore-service:/authlib
- MYPYPATH=/app:/datastore-service:/authlib
Expand All @@ -34,6 +34,3 @@ services:
vote:
build:
context: ../../openslides-vote-service
auth:
build:
context: ../../openslides-auth-service
41 changes: 21 additions & 20 deletions dev/docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ services:
- CACHE_HOST=redis
- DATABASE_HOST=postgres
- DATASTORE_LOG_LEVEL=CRITICAL
- OPENSLIDES_KEYCLOAK_URL=http://keycloak:8080/idp
- OPENSLIDES_AUTH_REALM=os
- OPENSLIDES_AUTH_CLIENT_ID=os-ui
- OPENSLIDES_TOKEN_ISSUER=http://keycloak:8080/idp/auth/realms/os
- OPENSLIDES_KEYCLOAK_ADMIN_USERNAME=admin
- OPENSLIDES_KEYCLOAK_ADMIN_PASSWORD=admin
depends_on:
- datastore-writer
datastore-reader:
Expand Down Expand Up @@ -60,26 +66,21 @@ services:
depends_on:
- postgres
- redis
auth:
build:
context: "https://github.com/OpenSlides/openslides-auth-service.git#main"
dockerfile: "Dockerfile.dev"
image: openslides-auth-dev
ports:
- "9004:9004"

keycloak:
# build:
# context: "https://github.com/OpenSlides/openslides-auth-service.git#main/keycloak"
image: openslides-keycloak-dev
environment:
- ACTION_HOST=backend
- ACTION_PORT=9002
- MESSAGE_BUS_HOST=redis
- CACHE_HOST=redis
- DATASTORE_READER_HOST=datastore-reader
- DATASTORE_READER_PORT=9010
- DATASTORE_WRITER_HOST=datastore-writer
- DATASTORE_WRITER_PORT=9011
depends_on:
- datastore-reader
- datastore-writer
- redis
- KC_BOOTSTRAP_ADMIN_USERNAME=admin
- KC_BOOTSTRAP_ADMIN_PASSWORD=admin
- JAVA_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"
- KEYCLOAK_HOSTNAME=http://keycloak:8080/idp/
- KEYCLOAK_HTTP_RELATIVE_PATH=/idp/
ports:
- 18080:8080
- 15005:5005

vote:
build:
context: "https://github.com/OpenSlides/openslides-vote-service.git#main"
Expand All @@ -98,7 +99,7 @@ services:
depends_on:
- datastore-reader
- redis
- auth
- keycloak
postgres:
image: postgres:15
environment:
Expand Down
2 changes: 1 addition & 1 deletion dev/run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ DC="docker compose -f dev/docker-compose.dev.yml"
$DC up --build --detach
$DC exec -T backend scripts/wait.sh datastore-writer 9011
$DC exec -T backend scripts/wait.sh datastore-reader 9010
$DC exec -T backend scripts/wait.sh auth 9004
$DC exec -T backend scripts/wait.sh keycloak 8080
$DC exec -T backend ./entrypoint.sh pytest --cov
error=$?
$DC down --volumes
Expand Down
11 changes: 8 additions & 3 deletions openslides_backend/action/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from ..models.fields import BaseRelationField
from ..permissions.management_levels import (
CommitteeManagementLevel,
OrganizationManagementLevel,
OrganizationManagementLevel, SystemManagementLevel,
)
from ..permissions.permission_helper import has_organization_management_level, has_perm
from ..permissions.permissions import Permission
Expand Down Expand Up @@ -85,7 +85,7 @@ class Action(BaseServiceProvider, metaclass=SchemaProvider):

is_singular: bool = False
action_type: ActionType = ActionType.PUBLIC
permission: Permission | OrganizationManagementLevel | None = None
permission: Permission | OrganizationManagementLevel | SystemManagementLevel | None = None
permission_model: Model | None = None
permission_id: str | None = None
skip_archived_meeting_check: bool = False
Expand Down Expand Up @@ -197,8 +197,12 @@ def check_permissions(self, instance: dict[str, Any]) -> None:
"""
Checks permission by requesting permission service or using internal check.
"""
print("check_permissions " + str(self.permission))
if self.permission:
if isinstance(self.permission, OrganizationManagementLevel):
if isinstance(self.permission, SystemManagementLevel):
if self.user_id == -1:
return
elif isinstance(self.permission, OrganizationManagementLevel):
if has_organization_management_level(
self.datastore,
self.user_id,
Expand Down Expand Up @@ -228,6 +232,7 @@ def check_permissions(self, instance: dict[str, Any]) -> None:

def check_for_archived_meeting(self, instance: dict[str, Any]) -> None:
"""Do not allow changing any data in an archived meeting"""
print("check_for_archived_meeting " + str(self.skip_archived_meeting_check))
if self.skip_archived_meeting_check:
return
try:
Expand Down
8 changes: 5 additions & 3 deletions openslides_backend/action/action_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import Any, TypeVar, cast

import fastjsonschema
from authlib.jose import JWTClaims

from ..shared.exceptions import (
ActionException,
Expand All @@ -29,6 +30,7 @@
Payload,
PayloadElement,
)
from ..http.auth_context import AuthContext

T = TypeVar("T")

Expand Down Expand Up @@ -94,7 +96,7 @@ def get_health_info(cls) -> Iterable[tuple[str, dict[str, Any]]]:
def handle_request(
self,
payload: Payload,
user_id: int,
auth_context: AuthContext,
atomic: bool = True,
internal: bool = False,
) -> ActionsResponse:
Expand All @@ -103,7 +105,7 @@ def handle_request(
parsing all actions. In the end it sends everything to the event store.
"""
with make_span(self.env, "handle request"):
self.user_id = user_id
self.user_id = auth_context.user_id
self.internal = internal

try:
Expand Down Expand Up @@ -154,7 +156,7 @@ def execute_internal_action(self, action: str, data: dict[str, Any]) -> None:
"data": [data],
}
],
-1,
AuthContext(-1, "", JWTClaims({}, {})),
internal=True,
)

Expand Down
21 changes: 14 additions & 7 deletions openslides_backend/action/action_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from gunicorn.http.wsgi import Response
from gunicorn.workers.gthread import ThreadWorker

from openslides_backend.shared.patterns import fqid_from_collection_and_id
from ..shared.patterns import fqid_from_collection_and_id

from ..services.datastore.interface import DatastoreService
from ..shared.exceptions import ActionException, DatastoreException
Expand All @@ -18,7 +18,8 @@
from ..shared.interfaces.write_request import WriteRequest
from .action_handler import ActionHandler
from .util.typing import ActionsResponse, Payload

from ..http.token_storage import token_storage, TokenStorageUpdate
from ..http.auth_context import AuthContext

class ActionWorkerState(str, Enum):
RUNNING = "running"
Expand All @@ -44,7 +45,7 @@ def handle_action_in_worker_thread(
)
action_worker_thread = ActionWorker(
payload,
user_id,
AuthContext(user_id, token_storage.access_token, token_storage.claims),
is_atomic,
handler,
lock,
Expand Down Expand Up @@ -221,7 +222,7 @@ class ActionWorker(threading.Thread):
def __init__(
self,
payload: Payload,
user_id: int,
auth_context: AuthContext,
is_atomic: bool,
handler: ActionHandler,
lock: threading.Lock,
Expand All @@ -230,7 +231,7 @@ def __init__(
super().__init__(name="action_worker")
self.handler = handler
self.payload = payload
self.user_id = user_id
self.auth_context = auth_context
self.is_atomic = is_atomic
self.lock = lock
self.internal = internal
Expand All @@ -239,12 +240,18 @@ def __init__(
def run(self): # type: ignore
with self.lock:
self.started = True
# set global werkzeug context
token_storage.update(TokenStorageUpdate(access_token= self.auth_context.access_token,
claims= self.auth_context.claims))
try:
self.response = self.handler.handle_request(
self.payload, self.user_id, self.is_atomic, self.internal
)
self.payload, self.auth_context, self.is_atomic, self.internal
)
except Exception as exception:
self.exception = exception
finally:
token_storage.clear()



class OSGunicornThread(threading.Thread):
Expand Down
1 change: 1 addition & 0 deletions openslides_backend/action/actions/user/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@
toggle_presence_by_number,
update,
update_self,
backchannel_login
)
53 changes: 53 additions & 0 deletions openslides_backend/action/actions/user/backchannel_login.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import time
from typing import Any, Iterable

from datastore.shared.util import FilterOperator
from openslides_backend.permissions.permissions import Permissions
from ...action import Action
from ...util.register import register_action
from ...util.typing import ActionResultElement
from ....models.models import User
from ....permissions.management_levels import SystemManagementLevel
from ....shared.exceptions import ActionException
from ....shared.interfaces.event import Event
from ....shared.patterns import fqid_from_collection_and_id
from ....shared.schema import schema_version


@register_action("user.backchannel_login")
class UserBackchannelLogin(Action):
"""
Action to login a user via back-channel.
"""

model = User()
# must contain an object with a string attribute "idp_id"
schema = {
"$schema": schema_version,
"title": "User login hook schema",
"type": "object",
"properties": {
"os_uid": {"type": "integer"}
},
"required": ["os_uid"],
"additionalProperties": False
}

permission = SystemManagementLevel(Permissions.System.CAN_LOGIN)
history_information = "User back-channel login"
skip_archived_meeting_check = True

def create_action_result_element(self, instance: dict[str, Any]) -> ActionResultElement | None:
return instance

def update_instance(self, instance: dict[str, Any]) -> dict[str, Any]:
os_uid = instance.get("os_uid")
user = self.datastore.get(fqid_from_collection_and_id(self.model.collection, os_uid), ["id"])
if not user:
raise ActionException(f"User with id {os_uid} not found.")
instance["last_login"] = int(time.time())
instance["id"] = user["id"]
return instance

def create_events(self, instance: dict[str, Any]) -> Iterable[Event]:
return []
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing import Any
from urllib.parse import unquote

from authlib.exceptions import InvalidCredentialsException
from os_authlib.exceptions import InvalidCredentialsException

from openslides_backend.action.util.typing import ActionData

Expand Down
11 changes: 11 additions & 0 deletions openslides_backend/http/auth_context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from authlib.jose import JWTClaims

class AuthContext:
user_id: int
access_token: str
claims: JWTClaims

def __init__(self, user_id: int, access_token: str, claims: JWTClaims):
self.user_id = user_id
self.access_token = access_token
self.claims = claims
Loading