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

Feature/486 token permissions configuration #497

Merged
merged 8 commits into from
Dec 19, 2024
Merged
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
21 changes: 17 additions & 4 deletions docker/setup_configuration/data.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,33 @@ objecttypes:
name: Object Type 1
service_identifier: objecttypes-api

- uuid: b0e8553f-8b1a-4d55-ab90-6d02f1bcf2c2
name: Object Type 2
service_identifier: objecttypes-api


tokenauth_config_enable: true
tokenauth:
items:
- identifier: token-1
token: 18b2b74ef994314b84021d47b9422e82b685d82f
token: ba9d233e95e04c4a8a661a27daffe7c9bd019067
contact_person: Person 1
email: person-1@example.com
organization: Organization 1
application: Application 1
administration: Administration 1
is_superuser: true


permissions:
- object_type: b427ef84-189d-43aa-9efd-7bb2c459e281
mode: read_and_write
- object_type: b0e8553f-8b1a-4d55-ab90-6d02f1bcf2c2
mode: read_only
use_fields: true
fields:
'1':
- record__data__leeftijd
- record__data__kiemjaar


oidc_db_config_enable: true
oidc_db_config_admin_auth:
items:
Expand Down
45 changes: 39 additions & 6 deletions docs/installation/config_cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Objecttypes configuration
To configure objecttypes the following configuration could be used:

.. code-block:: yaml

...
zgw_consumers_config_enable: true
zgw_consumers:
Expand Down Expand Up @@ -68,6 +69,7 @@ To configure objecttypes the following configuration could be used:
name: Object Type 2
service_identifier: objecttypen-bar
...

.. note:: The ``uuid`` field will be used to lookup existing ``ObjectType``'s.

Objecttypes require a corresponding ``Service`` to work correctly. Creating
Expand All @@ -81,8 +83,8 @@ In order to be able to retrieve objecttypes, a corresponding ``Service`` should
created. An example of a configuration could be seen below:

.. code-block:: yaml
...

...
zgw_consumers_config_enable: true
zgw_consumers:
services:
Expand All @@ -102,7 +104,8 @@ created. An example of a configuration could be seen below:
auth_type: api_key
header_key: Authorization
header_value: Token b9f100590925b529664ed9d370f5f8da124b2c20
....
...


Tokens configuration
--------------------
Expand All @@ -121,14 +124,28 @@ Create or update the (single) YAML configuration file with your settings:
organization: Organization XYZ # optional
application: Application XYZ # optional
administration: Administration XYZ # optional
is_superuser: true # optional
permissions:
- object_type: b427ef84-189d-43aa-9efd-7bb2c459e281
mode: read_and_write

- identifier: token-2
token: 7b2b212d9f16d171a70a1d927cdcfbd5ca7a4799
contact_person: Person 2
email: person-2@example.com
permissions:
- object_type: b0e8553f-8b1a-4d55-ab90-6d02f1bcf2c2
mode: read_only
use_fields: true
fields:
'1':
- record__data__leeftijd
- record__data__kiemjaar
...

.. note:: To ensure the proper functioning of the tokens, it is essential to first configure the ``objecttypes``.
Then, the token configuration must be completed to guarantee the correct configuration of the ``Permissions``.


Mozilla-django-oidc-db
----------------------

Expand Down Expand Up @@ -158,16 +175,32 @@ can be found at the _`documentation`: https://mozilla-django-oidc-db.readthedocs
Sites configuration
-------------------

.. code-block:: yaml

...
sites_config_enable: true
sites_config:
items:
- domain: example.com
name: Example site
- domain: test.example.com
name: Test site
...

More details about sites configuration through ``setup_configuration``
can be found at the _`site documentation`: https://github.com/maykinmedia/django-setup-configuration/blob/main/docs/sites_config.rst


Notifications configuration
-------------------------
---------------------------

To configure sending notifications for the application ensure there is a ``services``
item present that matches the ``notifications_api_service_identifier`` in the
``notifications_config`` namespace:

.. code-block:: yaml
...

...
zgw_consumers_config_enable: true
zgw_consumers:
services:
Expand All @@ -184,7 +217,7 @@ item present that matches the ``notifications_api_service_identifier`` in the
notification_delivery_max_retries: 1
notification_delivery_retry_backoff: 2
notification_delivery_retry_backoff_max: 3
....
...


Execution
Expand Down
1 change: 1 addition & 0 deletions src/objects/conf/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,6 @@
"zgw_consumers.contrib.setup_configuration.steps.ServiceConfigurationStep",
"notifications_api_common.contrib.setup_configuration.steps.NotificationConfigurationStep",
"mozilla_django_oidc_db.setup_configuration.steps.AdminOIDCConfigurationStep",
"objects.setup_configuration.steps.objecttypes.ObjectTypesConfigurationStep",
"objects.setup_configuration.steps.token_auth.TokenAuthConfigurationStep",
)
23 changes: 22 additions & 1 deletion src/objects/setup_configuration/models/token_auth.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
from django_setup_configuration.fields import DjangoModelRef
from django_setup_configuration.models import ConfigurationModel
from pydantic import UUID4, Field

from objects.token.models import TokenAuth
from objects.token.models import Permission, TokenAuth


class TokenAuthPermissionConfigurationModel(ConfigurationModel):
object_type: UUID4
fields: dict[str, list[str]] | None = DjangoModelRef(
Permission, "fields", default=None
)

class Meta:
django_model_refs = {
Permission: (
"mode",
"use_fields",
),
}


class TokenAuthConfigurationModel(ConfigurationModel):
permissions: list[TokenAuthPermissionConfigurationModel] | None = Field(
default_factory=list,
)

class Meta:
django_model_refs = {
TokenAuth: (
Expand Down
90 changes: 66 additions & 24 deletions src/objects/setup_configuration/steps/token_auth.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import logging
from typing import Any

from django.core.exceptions import ValidationError
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.db import IntegrityError

from django_setup_configuration.configuration import BaseConfigurationStep
from django_setup_configuration.exceptions import ConfigurationRunFailed

from objects.core.models import ObjectType
from objects.setup_configuration.models.token_auth import (
TokenAuthGroupConfigurationModel,
)
from objects.token.models import TokenAuth
from objects.token.models import Permission, TokenAuth

logger = logging.getLogger(__name__)

Expand All @@ -18,7 +20,7 @@ class TokenAuthConfigurationStep(
BaseConfigurationStep[TokenAuthGroupConfigurationModel]
):
"""
Configure tokens for other applications to access Objects API
Configure tokens with permissions for other applications to access Objects API
"""

namespace = "tokenauth"
Expand All @@ -27,14 +29,61 @@ class TokenAuthConfigurationStep(
verbose_name = "Configuration to set up authentication tokens for objects"
config_model = TokenAuthGroupConfigurationModel

def _full_clean(self, instance: Any) -> None:
try:
instance.full_clean(exclude=("id",), validate_unique=False)
except ValidationError as exception:
raise ConfigurationRunFailed(
("Validation error(s) during instance cleaning: %s" % type(instance))
) from exception

def _configure_permissions(self, token: TokenAuth, permissions: list) -> None:
if len(permissions) == 0:
logger.warning("No permissions provided for %s", token.identifier)

for permission in permissions:
try:
permission_kwargs = {
"token_auth": token,
"object_type": ObjectType.objects.get(uuid=permission.object_type),
"mode": permission.mode,
"use_fields": permission.use_fields,
"fields": permission.fields,
}
except ObjectDoesNotExist as exception:
raise ConfigurationRunFailed(
("Object type with %s does not exist" % permission.object_type)
) from exception

permission_instance = Permission(**permission_kwargs)
self._full_clean(permission_instance)
stevenbal marked this conversation as resolved.
Show resolved Hide resolved

try:
Permission.objects.update_or_create(
token_auth=permission_kwargs["token_auth"],
object_type=permission_kwargs["object_type"],
defaults={
"mode": permission_kwargs["mode"],
"use_fields": permission_kwargs["use_fields"],
"fields": permission_kwargs["fields"],
},
)
except IntegrityError as exception:
raise ConfigurationRunFailed(
(
"Failed configuring permission for token %s and object type %s"
% (token.identifier, permission.object_type)
)
) from exception

def execute(self, model: TokenAuthGroupConfigurationModel) -> None:
if len(model.items) == 0:
logger.warning("No tokens provided for configuration")

for item in model.items:
logger.info(f"Configuring {item.identifier}")
logger.info("Configuring %s", item.identifier)

model_kwargs = {
token_kwargs = {
"identifier": item.identifier,
"token": item.token,
"contact_person": item.contact_person,
Expand All @@ -45,31 +94,24 @@ def execute(self, model: TokenAuthGroupConfigurationModel) -> None:
"is_superuser": item.is_superuser,
}

token_instance = TokenAuth(**model_kwargs)

token_instance = TokenAuth(**token_kwargs)
self._full_clean(token_instance)
try:
token_instance.full_clean(exclude=("id",), validate_unique=False)
except ValidationError as exception:
exception_message = (
f"Validation error(s) occured for {item.identifier}."
)
raise ConfigurationRunFailed(exception_message) from exception

logger.debug(f"No validation errors found for {item.identifier}")

try:
logger.debug(f"Saving {item.identifier}")

TokenAuth.objects.update_or_create(
logger.debug("Saving %s", item.identifier)
token, _ = TokenAuth.objects.update_or_create(
identifier=item.identifier,
defaults={
key: value
for key, value in model_kwargs.items()
for key, value in token_kwargs.items()
if key != "identifier"
},
)

self._configure_permissions(token, item.permissions)

except IntegrityError as exception:
exception_message = f"Failed configuring token {item.identifier}."
raise ConfigurationRunFailed(exception_message) from exception
raise ConfigurationRunFailed(
"Failed configuring token %s" % item.identifier
) from exception

logger.info(f"Configured {item.identifier}")
logger.info("Configured %s", item.identifier)
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,17 @@ tokenauth:
organization: Organization 1
application: Application 1
administration: Administration 1
is_superuser: True
permissions:
- object_type: 3a82fb7f-fc9b-4104-9804-993f639d6d0d
mode: read_only
use_fields: true
fields:
'1':
- record__data__leeftijd
- record__data__kiemjaar

- object_type: ca754b52-3f37-4c49-837c-130e8149e337
mode: read_and_write

- identifier: token-2
token: e882642bd0ec2482adcdc97258c2e6f98cb06d85
Expand All @@ -17,4 +27,15 @@ tokenauth:
organization: Organization 2
application: Application 2
administration: Administration 2
permissions:
- object_type: feeaa795-d212-4fa2-bb38-2c34996e5702
mode: read_only

- identifier: token-3
token: ff835859ecf8df4d541aab09f2d0854d17b41a77
contact_person: Person 3
email: person-3@example.com
organization: Organization 3
application: Application 3
administration: Administration 3
is_superuser: True
Loading
Loading