From 886c166abb49a676c879f4e8c11003ae0b2efdc6 Mon Sep 17 00:00:00 2001 From: Christian Monch Date: Fri, 3 Jul 2026 16:15:56 +0200 Subject: [PATCH 1/5] chore: update changelog --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e78b17d..8be3962 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,29 @@ +# 6.3.1 (2026-07-03) + +## Changes + +- Use canonical storage representation of records. This reduces "noise" in + audit-backends by minimizing the differences between record versions. This + also reduces the noise in change set in the `dtc`-subcommand `auto-curate`. + +- `GET /tokens`-endpoint now returns full token information, including the + token representation. This allows to fully reproduce a configuration on + a different server via API-calls only. + +- `gitaudit`-audit backends now use a fixed user-id. This prevents start up + errors in environments where no git-user id is configured. + + +# 6.3.0 (2026-06-29) + +## Changes + +- Automated annotation adding in write-record-endpoints has been removed. + The new query parameter `add_submission_tag` is added to write-record-endpoints. + If set to True, the server will add an automated annotation with the annotation + tags that are defined in the server-configuration (the default is False). + + # 6.3.0 (2026-06-29) ## Changes -- 2.52.0 From 04e5644c36d874010b506b1ca61c0a0447b4e6c5 Mon Sep 17 00:00:00 2001 From: Christian Monch Date: Mon, 6 Jul 2026 10:15:55 +0200 Subject: [PATCH 2/5] fix: fix collection deletion Delete all collection-related objects from instance state, when a collection is deleted via the administration API. This fixes a problem where PUT /collection would not modify, e.g., configurations of authentication sources. --- dump_things_service/manifest.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/dump_things_service/manifest.py b/dump_things_service/manifest.py index 6d4e6de..163d462 100644 --- a/dump_things_service/manifest.py +++ b/dump_things_service/manifest.py @@ -115,7 +115,7 @@ def manifest_configuration( # because token-objects are all re-created below). for collection_name in deleted_collection_names: delete_endpoints_for_collection(instance_state, collection_name) - delete_collection(instance_state, collection_name) + delete_collection_from_instance_state(instance_state, collection_name) # Create the internal representation objects for collections that have been # added to the configuration. @@ -172,17 +172,24 @@ def delete_token( instance_state.tokens.pop(token_name) -def delete_collection( +def delete_collection_from_instance_state( instance_state: InstanceState, collection_name: str, ): - instance_state.collections.pop(collection_name) - - # TODO: remove further collection-related information from - # instance_state. Maybe all collection-specific information + # TODO: Maybe all collection-specific information # should go into the instance_state.collection[x]-object!? # That would allow to remove it easily. + # Remove objects that exist for every collection + instance_state.collections.pop(collection_name) + instance_state.curated_stores.pop(collection_name) + instance_state.validators.pop(collection_name) + instance_state.auth_sources.pop(collection_name) + + # Remove optional objects + instance_state.audit_backends.pop(collection_name, None) + instance_state.incoming_stores.pop(collection_name, None) + def create_openapi_tags( instance_state: InstanceState, -- 2.52.0 From c4c322e6d6ed00d7616fd20e72fd25fa3996d47f Mon Sep 17 00:00:00 2001 From: Christian Monch Date: Tue, 7 Jul 2026 10:16:02 +0200 Subject: [PATCH 3/5] tests: add test for instance-state updates Add a test that ensures that instance-state is updated when a collection is `PUT` or `DELETE`-ed. --- .../tests/test_collection_administration.py | 67 ++++++++++++++++++- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/dump_things_service/tests/test_collection_administration.py b/dump_things_service/tests/test_collection_administration.py index 6e9a943..f517604 100644 --- a/dump_things_service/tests/test_collection_administration.py +++ b/dump_things_service/tests/test_collection_administration.py @@ -12,12 +12,14 @@ from dump_things_service import ( HTTP_404_NOT_FOUND, ) from dump_things_service.abstract_config import ( + ForgejoAuthSpec, GitAuditBackendConfig, TokenCollectionConfig, TokenModes, hash_token_representation, ) from dump_things_service.collection_endpoints import CollectionRequest +from dump_things_service.instance_state import get_instance_state from dump_things_service.token_endpoints import ( AdminTokenRequest, TokenRequest, @@ -82,15 +84,18 @@ def test_collection_adding(fastapi_client_simple): assert response.status_code == HTTP_404_NOT_FOUND assert not _name_in_openapi_paths(test_client, new_collection_name) - # Add a new collection + # Add a new collection to the configuration response = test_client.post( '/collections', headers={'x-dumpthings-token': admin_token}, json=new_collection_request.model_dump(mode='json', by_alias=True), ) assert response.status_code == HTTP_201_CREATED + + # Check that the collection endpoints exist in the open API document assert _name_in_openapi_paths(test_client, new_collection_name) + # Read back the new collection and compare it response = test_client.get( f'/collections/{new_collection_name}', headers={'x-dumpthings-token': admin_token}, @@ -124,13 +129,12 @@ def test_collection_adding(fastapi_client_simple): 'representation': new_token_request.representation, } + # Add a new record to the collection to verify that the token works new_record = { 'pid': 'http://example.com/admin-test-1', 'given_name': 'Admin Test 1', 'schema_type': 'abc:Person', } - - # Add a record to the collection response = test_client.post( f'/{new_collection_name}/record/Person', headers={'x-dumpthings-token': new_token_representation}, @@ -313,3 +317,60 @@ def test_admin_token_management(fastapi_client_simple): ) assert response.status_code == HTTP_200_OK assert new_admin_token_name not in response.json() + + +def test_instance_state_updates(fastapi_client_simple): + test_client, _, admin_token = fastapi_client_simple + + instance_state = get_instance_state() + + forgejo_auth_spec = ForgejoAuthSpec( + type='forgejo', + url='http://localhost', + organization='orga-1', + team='team-1', + label_type='user', + ) + + forgejo_audit_collection_name = 'test_forgejo_audit_collection' + forgejo_audit_collection_request = CollectionRequest( + name=forgejo_audit_collection_name, + default_token='test_default_token', + curated=PurePosixPath(f'{curated}/{forgejo_audit_collection_name}'), + schema=test_schema_location, + incoming=PurePosixPath(f'{incoming}/{forgejo_audit_collection_name}'), + auth_sources=[forgejo_auth_spec], + ) + + # Post a collection with forgejo audit-backend without an instance_id + response = test_client.post( + '/collections', + headers={'x-dumpthings-token': admin_token}, + json=forgejo_audit_collection_request.model_dump(mode='json', by_alias=True), + ) + assert response.status_code == HTTP_201_CREATED + forgejo_instance = instance_state.auth_sources[forgejo_audit_collection_name][0] + assert forgejo_instance.instance_id is None + + # Put a collection with updated forgejo audit-backend, containing an instance_id + forgejo_auth_spec.instance_id = 'instance-id' + response = test_client.put( + '/collections', + headers={'x-dumpthings-token': admin_token}, + json=forgejo_audit_collection_request.model_dump(mode='json', by_alias=True), + ) + assert response.status_code == HTTP_201_CREATED + auth_sources = instance_state.auth_sources[forgejo_audit_collection_name] + assert len(auth_sources) == 1 + new_forgejo_instance = auth_sources[0] + + assert new_forgejo_instance is not forgejo_instance + assert new_forgejo_instance.instance_id == 'instance-id' + + # Delete the collection again because we check for a known number of + # collections in other tests. + response = test_client.delete( + f'/collections/{forgejo_audit_collection_name}', + headers={'x-dumpthings-token': admin_token}, + ) + assert response.status_code == HTTP_200_OK -- 2.52.0 From 2da9def92239ca4059c209a3d81510ef57658263 Mon Sep 17 00:00:00 2001 From: Christian Monch Date: Tue, 7 Jul 2026 10:43:34 +0200 Subject: [PATCH 4/5] chore: fix type-annotation errors --- dump_things_service/manifest.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/dump_things_service/manifest.py b/dump_things_service/manifest.py index 163d462..591d00f 100644 --- a/dump_things_service/manifest.py +++ b/dump_things_service/manifest.py @@ -1,6 +1,7 @@ import logging from fastapi_pagination import add_pagination +from typing import cast from dump_things_service.abstract_config import ( Configuration, @@ -21,7 +22,7 @@ tag_groups = [ 'incoming_write', ] -openapi_tags_template = [ +openapi_tags_template: list[dict | str] = [ { 'name': 'Server management', 'description': 'General server operations', @@ -193,7 +194,7 @@ def delete_collection_from_instance_state( def create_openapi_tags( instance_state: InstanceState, - openapi_tags_template: list[dict | str], + openapi_tags_template_: list[dict | str], ) -> list[dict]: # Collect tag name lists for all tag groups that we have defined. tag_group_info = { @@ -206,8 +207,8 @@ def create_openapi_tags( ) for tag_group in tag_groups } - result = openapi_tags_template.copy() + result = openapi_tags_template_.copy() for tag_group, tag_list in tag_group_info.items(): index = result.index(tag_group) result[index : index + 1] = tag_list - return result + return cast(list[dict], result) -- 2.52.0 From 3218a720f3bf7b3ab38cfcc8a4b73cd3a9931410 Mon Sep 17 00:00:00 2001 From: Christian Monch Date: Tue, 7 Jul 2026 10:46:01 +0200 Subject: [PATCH 5/5] chore: adapt linter suggestions --- dump_things_service/manifest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dump_things_service/manifest.py b/dump_things_service/manifest.py index 591d00f..bef6bbf 100644 --- a/dump_things_service/manifest.py +++ b/dump_things_service/manifest.py @@ -1,7 +1,7 @@ import logging +from typing import cast from fastapi_pagination import add_pagination -from typing import cast from dump_things_service.abstract_config import ( Configuration, @@ -211,4 +211,4 @@ def create_openapi_tags( for tag_group, tag_list in tag_group_info.items(): index = result.index(tag_group) result[index : index + 1] = tag_list - return cast(list[dict], result) + return cast('list[dict]', result) -- 2.52.0