Internal api key gql #181

This commit is contained in:
2025-09-29 19:51:59 +02:00
parent 76c568be81
commit d0947ee29b
8 changed files with 170 additions and 17 deletions

View File

@@ -1,20 +1,21 @@
import socket
from enum import Enum from enum import Enum
from typing import Self from typing import Self
from cpl.api.application import WebApp from cpl.api.application import WebApp
from cpl.api.model.validation_match import ValidationMatch from cpl.api.model.validation_match import ValidationMatch
from cpl.auth.schema import UserDao
from cpl.core.configuration import Configuration
from cpl.core.environment import Environment
from cpl.dependency.service_provider import ServiceProvider from cpl.dependency.service_provider import ServiceProvider
from cpl.dependency.typing import Modules from cpl.dependency.typing import Modules
from queries.user import UserGraphType, UserFilter, UserSort from queries.user import UserGraphType, UserFilter, UserSort
from .._endpoints.graphiql import graphiql_endpoint from cpl.graphql._endpoints.graphiql import graphiql_endpoint
from .._endpoints.graphql import graphql_endpoint from cpl.graphql._endpoints.graphql import graphql_endpoint
from .._endpoints.playground import playground_endpoint from cpl.graphql._endpoints.playground import playground_endpoint
from ..auth.administration.user.user_mutation import UserMutation from cpl.graphql.auth.administration.user.user_mutation import UserMutation
from ..graphql_module import GraphQLModule from cpl.graphql.graphql_module import GraphQLModule
from ..service.schema import Schema from cpl.graphql.service.schema import Schema
from ...application.abc.application_abc import __not_implemented__
from ...auth.schema import UserDao
from ...core.configuration import Configuration
class GraphQLApp(WebApp): class GraphQLApp(WebApp):
@@ -104,8 +105,14 @@ class GraphQLApp(WebApp):
schema.mutation.with_mutation("user", UserMutation).with_public(public) schema.mutation.with_mutation("user", UserMutation).with_public(public)
async def _log_before_startup(self): async def _log_before_startup(self):
self._logger.info(f"Start API on {self._api_settings.host}:{self._api_settings.port}") host = self._api_settings.host
if host == "0.0.0.0" and Environment.get_environment() == "development":
host = "localhost"
elif host == "0.0.0.0":
host = socket.gethostbyname(socket.gethostname())
self._logger.info(f"Start API on {host}:{self._api_settings.port}")
if self._with_graphiql: if self._with_graphiql:
self._logger.warning(f"GraphiQL available at http://{self._api_settings.host}:{self._api_settings.port}/api/graphiql") self._logger.warning(f"GraphiQL available at http://{host}:{self._api_settings.port}/api/graphiql")
if self._with_playground: if self._with_playground:
self._logger.warning(f"GraphQL Playground available at http://{self._api_settings.host}:{self._api_settings.port}/api/playground") self._logger.warning(f"GraphQL Playground available at http://{host}:{self._api_settings.port}/api/playground")

View File

@@ -0,0 +1,10 @@
from cpl.auth.schema import ApiKey
from cpl.graphql.schema.filter.db_model_filter import DbModelFilter
from cpl.graphql.schema.filter.string_filter import StringFilter
class ApiKeyFilter(DbModelFilter[ApiKey]):
def __init__(self, public: bool = False):
DbModelFilter.__init__(self, public)
self.field("identifier", StringFilter).with_public(public)

View File

@@ -0,0 +1,14 @@
from cpl.auth.schema import ApiKey, RolePermissionDao
from cpl.graphql.schema.db_model_graph_type import DbModelGraphType
class ApiKeyGraphType(DbModelGraphType):
def __init__(self, role_permission_dao: RolePermissionDao):
DbModelGraphType.__init__(self)
self.string_field(ApiKey.identifier, lambda root: root.identifier)
self.string_field(ApiKey.key, lambda root: root.key)
self.string_field(ApiKey.permissions, lambda root: root.permissions)
self.set_history_reference_dao(role_permission_dao, "apikeyid")

View File

@@ -0,0 +1,25 @@
from cpl.auth.schema import ApiKey
from cpl.core.typing import SerialId
from cpl.graphql.schema.input import Input
class ApiKeyCreateInput(Input[ApiKey]):
identifier: str
permissions: list[SerialId]
def __init__(self):
Input.__init__(self)
self.string_field("identifier").with_required()
self.list_field("permissions", SerialId)
class ApiKeyUpdateInput(Input[ApiKey]):
id: SerialId
identifier: str | None
permissions: list[SerialId] | None
def __init__(self):
Input.__init__(self)
self.int_field("id").with_required()
self.string_field("identifier").with_required()
self.list_field("permissions", SerialId)

View File

@@ -0,0 +1,96 @@
from cpl.api import APILogger
from cpl.auth.keycloak import KeycloakAdmin
from cpl.auth.permission import Permissions
from cpl.auth.schema import ApiKey, ApiKeyDao, ApiKeyPermissionDao, ApiKeyPermission
from cpl.graphql.auth.administration.api_key.api_key_input import ApiKeyUpdateInput, ApiKeyCreateInput
from cpl.graphql.schema.mutation import Mutation
class ApiKeyMutation(Mutation):
def __init__(
self,
logger: APILogger,
api_key_dao: ApiKeyDao,
api_key_permission_dao: ApiKeyPermissionDao,
permission_dao: ApiKeyPermissionDao,
keycloak_admin: KeycloakAdmin,
):
Mutation.__init__(self)
self._logger = logger
self._api_key_dao = api_key_dao
self._api_key_permission_dao = api_key_permission_dao
self._permission_dao = permission_dao
self._keycloak_admin = keycloak_admin
self.int_field(
"create",
self.resolve_create,
).with_require_any_permission(Permissions.users_create).with_argument(
"input",
ApiKeyCreateInput,
).with_required()
self.bool_field(
"update",
self.resolve_update,
).with_require_any_permission(Permissions.users_update).with_argument(
"input",
ApiKeyUpdateInput,
).with_required()
self.bool_field(
"delete",
self.resolve_delete,
).with_require_any_permission(Permissions.users_delete).with_argument(
"id",
int,
).with_required()
self.bool_field(
"restore",
self.resolve_restore,
).with_require_any_permission(Permissions.users_delete).with_argument(
"id",
int,
).with_required()
async def resolve_create(self, obj: ApiKeyCreateInput):
self._logger.debug(f"create api key: {obj.__dict__}")
api_key = ApiKey.new(obj.identifier)
await self._api_key_dao.create(api_key)
api_key = await self._api_key_dao.get_single_by([{ApiKey.identifier: obj.identifier}])
await self._api_key_permission_dao.create_many(
[ApiKeyPermission(0, api_key.id, x) for x in obj.permissions]
)
return api_key
async def resolve_update(self, input: ApiKeyUpdateInput):
self._logger.debug(f"update api key: {input}")
api_key = await self._api_key_dao.get_by_id(input.id)
await self._resolve_assignments(
input.permissions or [],
api_key,
ApiKeyPermission.api_key_id,
ApiKeyPermission.permission_id,
self._api_key_dao,
self._api_key_permission_dao,
ApiKeyPermission,
self._permission_dao,
)
return api_key
async def resolve_delete(self, id: str):
self._logger.debug(f"delete api key: {id}")
api_key = await self._api_key_dao.get_by_id(id)
await self._api_key_dao.delete(api_key)
return True
async def resolve_restore(self, id: str):
self._logger.debug(f"restore api key: {id}")
api_key = await self._api_key_dao.get_by_id(id)
await self._api_key_dao.restore(api_key)
return True

View File

@@ -5,7 +5,7 @@ from cpl.graphql.schema.input import Input
class UserCreateInput(Input[User]): class UserCreateInput(Input[User]):
keycloak_id: str keycloak_id: str
roles: list[SerialId] roles: list[SerialId] | None
def __init__(self): def __init__(self):
Input.__init__(self) Input.__init__(self)
@@ -15,7 +15,7 @@ class UserCreateInput(Input[User]):
class UserUpdateInput(Input[User]): class UserUpdateInput(Input[User]):
id: SerialId id: SerialId
roles: list[SerialId] roles: list[SerialId] | None
def __init__(self): def __init__(self):
Input.__init__(self) Input.__init__(self)

View File

@@ -2,6 +2,9 @@ from cpl.core.configuration import Configuration
from cpl.dependency import ServiceProvider from cpl.dependency import ServiceProvider
from cpl.dependency.module.module import Module from cpl.dependency.module.module import Module
from cpl.dependency.service_collection import ServiceCollection from cpl.dependency.service_collection import ServiceCollection
from cpl.graphql.auth.administration.api_key.api_key_filter import ApiKeyFilter
from cpl.graphql.auth.administration.api_key.api_key_graph_type import ApiKeyGraphType
from cpl.graphql.auth.administration.api_key.api_key_mutation import ApiKeyMutation
from cpl.graphql.auth.administration.user.user_filter import UserFilter from cpl.graphql.auth.administration.user.user_filter import UserFilter
from cpl.graphql.auth.administration.user.user_graph_type import UserGraphType from cpl.graphql.auth.administration.user.user_graph_type import UserGraphType
from cpl.graphql.auth.administration.user.user_mutation import UserMutation from cpl.graphql.auth.administration.user.user_mutation import UserMutation
@@ -11,7 +14,7 @@ from cpl.graphql.service.schema import Schema
class GraphQLAuthModule(Module): class GraphQLAuthModule(Module):
dependencies = [GraphQLModule] dependencies = [GraphQLModule]
transient = [UserGraphType, UserMutation, UserFilter] transient = [UserGraphType, UserMutation, UserFilter, ApiKeyGraphType, ApiKeyMutation, ApiKeyFilter]
@staticmethod @staticmethod
def register(collection: ServiceCollection): def register(collection: ServiceCollection):
@@ -21,5 +24,3 @@ class GraphQLAuthModule(Module):
def configure(provider: ServiceProvider): def configure(provider: ServiceProvider):
schema = provider.get_service(Schema) schema = provider.get_service(Schema)
schema.with_type(UserGraphType) schema.with_type(UserGraphType)