Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Issue session token for usage with API #1710

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 12 additions & 10 deletions backend/capellacollab/sessions/hooks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
pure_variants,
read_only_workspace,
session_preparation,
session_token,
t4c,
)

Expand All @@ -23,15 +24,16 @@
"pure_variants": pure_variants.PureVariantsIntegration(),
}

REGISTER_HOOKS_AUTO_USE: dict[str, interface.HookRegistration] = {
"persistent_workspace": persistent_workspace.PersistentWorkspaceHook(),
"guacamole": guacamole.GuacamoleIntegration(),
"http": http.HTTPIntegration(),
"read_only_hook": read_only_workspace.ReadOnlyWorkspaceHook(),
"provisioning": provisioning.ProvisionWorkspaceHook(),
"session_preparation": session_preparation.GitRepositoryCloningHook(),
"networking": networking.NetworkingIntegration(),
}
REGISTER_HOOKS_AUTO_USE: list[interface.HookRegistration] = [
persistent_workspace.PersistentWorkspaceHook(),
session_token.SessionTokenIntegration(),
guacamole.GuacamoleIntegration(),
http.HTTPIntegration(),
read_only_workspace.ReadOnlyWorkspaceHook(),
provisioning.ProvisionWorkspaceHook(),
session_preparation.GitRepositoryCloningHook(),
networking.NetworkingIntegration(),
]


def get_activated_integration_hooks(
Expand All @@ -42,4 +44,4 @@ def get_activated_integration_hooks(
hook
for integration, hook in REGISTERED_HOOKS.items()
if getattr(tool.integrations, integration, False)
] + list(REGISTER_HOOKS_AUTO_USE.values())
] + list(REGISTER_HOOKS_AUTO_USE)
1 change: 1 addition & 0 deletions backend/capellacollab/sessions/hooks/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class ConfigurationHookResult(t.TypedDict):
warnings: t.NotRequired[list[core_models.Message]]
init_volumes: t.NotRequired[list[operators_models.Volume]]
init_environment: t.NotRequired[t.Mapping]
config: t.NotRequired[t.Mapping]


class PostSessionCreationHookResult(t.TypedDict):
Expand Down
59 changes: 59 additions & 0 deletions backend/capellacollab/sessions/hooks/session_token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors
# SPDX-License-Identifier: Apache-2.0


import datetime

from sqlalchemy import orm

from capellacollab.users import models as users_models
from capellacollab.users.tokens import crud as tokens_crud

from .. import models as sessions_models
from . import interface


class SessionTokenIntegration(interface.HookRegistration):
"""Create a PAT valid for the duration of the session."""

def configuration_hook( # type: ignore
self,
db: orm.Session,
user: users_models.DatabaseUser,
session_id: str,
**kwargs,
) -> interface.ConfigurationHookResult:
token, password = tokens_crud.create_token(

Check warning on line 26 in backend/capellacollab/sessions/hooks/session_token.py

View check run for this annotation

Codecov / codecov/patch

backend/capellacollab/sessions/hooks/session_token.py#L26

Added line #L26 was not covered by tests
db,
user,
f"Session token for session {session_id}. Will be revoked when the session is terminated.",
datetime.date.today()
+ datetime.timedelta(
days=1
), # Maximum duration is until end of the next day.
"SessionTokenIssuer",
)

return interface.ConfigurationHookResult(

Check warning on line 37 in backend/capellacollab/sessions/hooks/session_token.py

View check run for this annotation

Codecov / codecov/patch

backend/capellacollab/sessions/hooks/session_token.py#L37

Added line #L37 was not covered by tests
environment={"CAPELLACOLLAB_SESSION_TOKEN": password},
config={"session_token_id": token.id},
)

def pre_session_termination_hook( # type: ignore
self,
db: orm.Session,
session: sessions_models.DatabaseSession,
**kwargs,
) -> interface.PreSessionTerminationHookResult:
token_id = session.config.get("session_token_id")

Check warning on line 48 in backend/capellacollab/sessions/hooks/session_token.py

View check run for this annotation

Codecov / codecov/patch

backend/capellacollab/sessions/hooks/session_token.py#L48

Added line #L48 was not covered by tests
if not token_id:
return interface.PreSessionTerminationHookResult()

Check warning on line 50 in backend/capellacollab/sessions/hooks/session_token.py

View check run for this annotation

Codecov / codecov/patch

backend/capellacollab/sessions/hooks/session_token.py#L50

Added line #L50 was not covered by tests

token = tokens_crud.get_token_by_user_and_id(

Check warning on line 52 in backend/capellacollab/sessions/hooks/session_token.py

View check run for this annotation

Codecov / codecov/patch

backend/capellacollab/sessions/hooks/session_token.py#L52

Added line #L52 was not covered by tests
db, session.owner.id, int(token_id)
)
if not token:
return interface.PreSessionTerminationHookResult()

Check warning on line 56 in backend/capellacollab/sessions/hooks/session_token.py

View check run for this annotation

Codecov / codecov/patch

backend/capellacollab/sessions/hooks/session_token.py#L56

Added line #L56 was not covered by tests

tokens_crud.delete_token(db, token)
return interface.PreSessionTerminationHookResult()

Check warning on line 59 in backend/capellacollab/sessions/hooks/session_token.py

View check run for this annotation

Codecov / codecov/patch

backend/capellacollab/sessions/hooks/session_token.py#L58-L59

Added lines #L58 - L59 were not covered by tests
3 changes: 2 additions & 1 deletion backend/capellacollab/sessions/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ def request_session(
init_volumes: list[operators_models.Volume] = []
init_environment: dict[str, str] = {}

hook_config: dict[str, str] = {}
for hook in hooks.get_activated_integration_hooks(tool):
hook_result = hook.configuration_hook(
db=db,
Expand All @@ -141,6 +142,7 @@ def request_session(
volumes += hook_result.get("volumes", [])
init_volumes += hook_result.get("init_volumes", [])
warnings += hook_result.get("warnings", [])
hook_config |= hook_result.get("config", {})

local_env, local_warnings = util.resolve_environment_variables(
logger,
Expand Down Expand Up @@ -219,7 +221,6 @@ def request_session(
),
)

hook_config: dict[str, str] = {}
for hook in hooks.get_activated_integration_hooks(tool):
result = hook.post_session_creation_hook(
session_id=session_id,
Expand Down
4 changes: 1 addition & 3 deletions backend/tests/sessions/test_session_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,7 @@ def pre_session_termination_hook(
def fixture_session_hook(monkeypatch: pytest.MonkeyPatch) -> TestSessionHook:
hook = TestSessionHook()

REGISTER_HOOKS_AUTO_USE: dict[str, hooks_interface.HookRegistration] = {
"test": hook,
}
REGISTER_HOOKS_AUTO_USE: list[hooks_interface.HookRegistration] = [hook]

monkeypatch.setattr(
sessions_hooks, "REGISTER_HOOKS_AUTO_USE", REGISTER_HOOKS_AUTO_USE
Expand Down
2 changes: 1 addition & 1 deletion backend/tests/sessions/test_session_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def get_mock_operator():

@pytest.fixture(autouse=True, name="session_hook")
def fixture_session_hook(monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(sessions_hooks, "REGISTER_HOOKS_AUTO_USE", {})
monkeypatch.setattr(sessions_hooks, "REGISTER_HOOKS_AUTO_USE", [])


@pytest.mark.usefixtures("user", "session")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ <h2 class="text-xl font-medium">Token overview</h2>
<div>
Description: {{ token.description }} <br />
Expiration date: {{ token.expiration_date | date }} <br />
Creation location: {{ token.source }}
Source: {{ token.source }}
@if (isTokenExpired(token.expiration_date)) {
<mat-icon color="warn" class="align-middle">warning</mat-icon>
<span class="text-red-600">This token has expired!</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,13 @@ export class BasicAuthTokenComponent implements OnInit {
}

deleteToken(token: Token) {
this.tokenService.deleteToken(token).subscribe();
this.toastService.showSuccess(
'Token deleted',
`The token ${token.description} was successfully deleted!`,
);
this.tokenService.deleteToken(token).subscribe(() => {
this.toastService.showSuccess(
'Token deleted',
`The token ${token.description} was successfully deleted!`,
);
this.password = undefined;
});
}

isTokenExpired(expirationDate: string): boolean {
Expand Down
Loading