Skip to content

Commit 0da7533

Browse files
authored
issue-1931 nameid lookup api (#1959)
* feat: add NameID lookup API (#1931) Two new endpoints on the internal API: - POST /info/users/nameid — forward lookup (sho + uid + sp_entityid → nameid) - POST /info/users/id — reverse lookup (nameid → sho + uid + sp_entityid) Both require ROLE_API_USER_NAMEID_LOOKUP and are feature-flag gated. * test: add unit, functional and integration tests for NameID lookup API * Comments * refactor: merge api.users_nameid and api.users_id into single feature flag api.users_nameid_lookup * Remove camelCase in parametets
1 parent 0ce33db commit 0da7533

21 files changed

Lines changed: 1346 additions & 6 deletions

File tree

config/packages/ci/parameters.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
parameters:
2+
api.users.nameidlookup.username: nameid
3+
api.users.nameidlookup.password: secret
4+
feature_api_users_nameid_lookup: true
25
encryption_keys:
36
default:
47
publicFile: /config/engine/engineblock.crt

config/packages/dev/parameters.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
parameters:
2+
api.users.nameidlookup.username: nameid
3+
api.users.nameidlookup.password: secret
4+
feature_api_users_nameid_lookup: true
25
encryption_keys:
36
default:
47
publicFile: /config/engine/engineblock.crt

config/packages/engineblock_features.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ parameters:
55
api.consent_remove: "%feature_api_consent_remove%"
66
api.metadata_api: "%feature_api_metadata_api%"
77
api.deprovision: "%feature_api_deprovision%"
8+
api.users_nameid_lookup: "%feature_api_users_nameid_lookup%"
89
eb.encrypted_assertions: "%feature_eb_encrypted_assertions%"
910
eb.encrypted_assertions_require_outer_signature: "%feature_eb_encrypted_assertions_require_outer_signature%"
1011
eb.run_all_manipulations_prior_to_consent: "%feature_run_all_manipulations_prior_to_consent%"

config/packages/parameters.yml.dist

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ parameters:
7979
api.users.profile.password: secret
8080
api.users.deprovision.username: lifecycle
8181
api.users.deprovision.password: secret
82+
api.users.nameidlookup.username: nameid
83+
api.users.nameidlookup.password: secret
8284

8385
##########################################################################################
8486
## CLIENT SETTINGS
@@ -222,6 +224,7 @@ parameters:
222224
feature_api_consent_remove: true
223225
feature_api_metadata_api: true
224226
feature_api_deprovision: true
227+
feature_api_users_nameid_lookup: true
225228
feature_run_all_manipulations_prior_to_consent: false
226229
feature_block_user_on_violation: false
227230
feature_enable_consent: true

config/packages/security.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ security:
1212
"%api.users.deprovision.username%":
1313
password: "%api.users.deprovision.password%"
1414
roles: 'ROLE_API_USER_DEPROVISION'
15+
"%api.users.nameidlookup.username%":
16+
password: "%api.users.nameidlookup.password%"
17+
roles: 'ROLE_API_USER_NAMEID_LOOKUP'
1518

1619
password_hashers:
1720
Symfony\Component\Security\Core\User\InMemoryUser: plaintext
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
parameters:
2+
api.users.nameidlookup.username: nameid
3+
api.users.nameidlookup.password: secret
4+
feature_api_users_nameid_lookup: true

config/services/controllers/api.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ services:
2626
- '@OpenConext\EngineBlock\Service\DeprovisionService'
2727
- 'EngineBlock'
2828

29+
OpenConext\EngineBlockBundle\Controller\Api\UserController:
30+
arguments:
31+
- '@security.token_storage'
32+
- '@security.access.decision_manager'
33+
- '@OpenConext\EngineBlockBundle\Configuration\FeatureConfiguration'
34+
- '@OpenConext\EngineBlock\Service\NameIdLookupService'
35+
- '@logger'
36+
2937
OpenConext\EngineBlockBundle\Controller\Api\HeartbeatController:
3038

3139
OpenConext\EngineBlockBundle\Controller\Api\MetadataController:

config/services/services.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,13 @@ services:
9494
- '@OpenConext\EngineBlockBundle\Authentication\Repository\SamlPersistentIdRepository'
9595
- '@OpenConext\EngineBlockBundle\Authentication\Repository\ServiceProviderUuidRepository'
9696

97+
OpenConext\EngineBlock\Service\NameIdLookupService:
98+
arguments:
99+
- '@OpenConext\EngineBlockBundle\Authentication\Repository\UserRepository'
100+
- '@OpenConext\EngineBlockBundle\Authentication\Repository\ServiceProviderUuidRepository'
101+
- '@OpenConext\EngineBlockBundle\Authentication\Repository\SamlPersistentIdRepository'
102+
- '@logger'
103+
97104
OpenConext\EngineBlock\Service\MetadataService:
98105
arguments:
99106
- '@engineblock.compat.repository.metadata'
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
/**
4+
* Copyright 2026 SURFnet B.V.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
declare(strict_types=1);
20+
21+
namespace OpenConext\EngineBlock\Doctrine\Migrations;
22+
23+
use Doctrine\DBAL\Schema\Schema;
24+
25+
/**
26+
* Corrects the column comment on saml_persistent_id.persistent_id.
27+
*
28+
* The original comment read "SHA1 of service_provider_uuid + user_uuid", which was
29+
* inaccurate in two ways: the operand order was wrong, and the COIN: salt was omitted.
30+
* The actual value stored is sha1('COIN:' + user_uuid + service_provider_uuid), as
31+
* defined in EngineBlock_Saml2_NameIdResolver::PERSISTENT_NAMEID_SALT.
32+
*
33+
* NOTE: This migration is NOT mandatory. It only updates a database-level column comment
34+
* and has no effect on data integrity or application behaviour. It is safe to skip on
35+
* existing installations where updating the comment is not considered necessary.
36+
*/
37+
final class Version20260331000000 extends AbstractEngineBlockMigration
38+
{
39+
public function getDescription(): string
40+
{
41+
return 'Corrects the column comment on saml_persistent_id.persistent_id to accurately reflect the SHA1 formula.';
42+
}
43+
44+
public function up(Schema $schema): void
45+
{
46+
$this->addSql(
47+
"ALTER TABLE `saml_persistent_id` MODIFY COLUMN `persistent_id` CHAR(40) NOT NULL COMMENT 'SHA1 of COIN: + user_uuid + service_provider_uuid'"
48+
);
49+
}
50+
51+
public function down(Schema $schema): void
52+
{
53+
$this->addSql(
54+
"ALTER TABLE `saml_persistent_id` MODIFY COLUMN `persistent_id` CHAR(40) NOT NULL COMMENT 'SHA1 of service_provider_uuid + user_uuid'"
55+
);
56+
}
57+
}

src/OpenConext/EngineBlock/Authentication/Value/CollabPersonId.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
namespace OpenConext\EngineBlock\Authentication\Value;
2020

2121
use OpenConext\EngineBlock\Assert\Assertion;
22+
use RuntimeException;
2223

2324
final class CollabPersonId
2425
{
@@ -84,6 +85,34 @@ public function getCollabPersonId(): string
8485
return $this->collabPersonId;
8586
}
8687

88+
public function getSchacHomeOrganization(): string
89+
{
90+
$parts = explode(':', $this->collabPersonId, 5);
91+
if (!isset($parts[3])) {
92+
throw new RuntimeException(sprintf(
93+
'Cannot extract schacHomeOrganization from CollabPersonId "%s"',
94+
$this->collabPersonId
95+
));
96+
}
97+
return $parts[3];
98+
}
99+
100+
/**
101+
* Returns the uid as stored in the CollabPersonId. Note: if the original uid contained an
102+
* @-sign, it will have been replaced by an underscore (legacy LDAP module behaviour).
103+
*/
104+
public function getStoredUid(): string
105+
{
106+
$parts = explode(':', $this->collabPersonId, 5);
107+
if (!isset($parts[4])) {
108+
throw new RuntimeException(sprintf(
109+
'Cannot extract uid from CollabPersonId "%s"',
110+
$this->collabPersonId
111+
));
112+
}
113+
return $parts[4];
114+
}
115+
87116
/**
88117
* @param CollabPersonId $other
89118
* @return bool

0 commit comments

Comments
 (0)