Skip to content

Commit 79fea5e

Browse files
committed
FAT-26173: Add consortia shadow-user IdP linking test
1 parent b64d254 commit 79fea5e

4 files changed

Lines changed: 166 additions & 0 deletions

File tree

mod-users-keycloak/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
<artifactId>common</artifactId>
2121
<version>1.0-SNAPSHOT</version>
2222
</dependency>
23+
<dependency>
24+
<groupId>org.folio</groupId>
25+
<artifactId>common-consortia</artifactId>
26+
<version>1.0-SNAPSHOT</version>
27+
</dependency>
2328
<dependency>
2429
<groupId>org.folio</groupId>
2530
<artifactId>testrail-integration</artifactId>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Feature: Destroy consortia test data
2+
3+
Background:
4+
* print karate.info.scenarioName
5+
* url baseUrl
6+
* configure readTimeout = 90000
7+
* configure retry = { count: 5, interval: 5000 }
8+
* def deleteTenantAndEntitlement = read('classpath:common-consortia/eureka/initData.feature@DeleteTenantAndEntitlement')
9+
10+
Scenario: Destroy central and member tenants
11+
* call deleteTenantAndEntitlement { tenantId: '#(memberTenantId)' }
12+
* call deleteTenantAndEntitlement { tenantId: '#(centralTenantId)' }
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
Feature: automatic IdP linking for central shadow users
2+
3+
Background:
4+
* url baseUrl
5+
* configure cookies = null
6+
* configure readTimeout = 600000
7+
* configure retry = { count: 10, interval: 15000 }
8+
* configure afterScenario =
9+
"""
10+
function(){
11+
if (!karate.get('centralTenantId') || !karate.get('memberTenantId')) {
12+
return;
13+
}
14+
karate.call('classpath:eureka/mod-users-keycloak/features/consortia/destroy-data.feature', {
15+
centralTenantId: karate.get('centralTenantId'),
16+
memberTenantId: karate.get('memberTenantId')
17+
});
18+
}
19+
"""
20+
21+
* table modules
22+
| name |
23+
| 'mod-users-keycloak' |
24+
| 'mod-login-keycloak' |
25+
| 'mod-roles-keycloak' |
26+
| 'mod-notify' |
27+
| 'mod-consortia' |
28+
29+
* def setupTenant = read('classpath:common-consortia/eureka/tenant-and-local-admin-setup.feature@SetupTenant')
30+
* def setupConsortium = read('classpath:common-consortia/eureka/consortium.feature@SetupConsortia')
31+
* def setupTenantForConsortia = read('classpath:common-consortia/eureka/consortium.feature@SetupTenantForConsortia')
32+
* def eurekaLogin = read('classpath:common-consortia/eureka/initData.feature@Login')
33+
* def keycloakMaster = read('classpath:common/eureka/keycloak.feature@getKeycloakMasterToken')
34+
35+
@Positive
36+
Scenario: creating a staff user in member tenant automatically creates a shadow user in central tenant and links the member tenant IdP
37+
38+
* def suffix = uuid().replace(/-/g, '').substring(0, 10)
39+
40+
* def centralTenant = 'consortium' + suffix
41+
* def centralTenantId = uuid()
42+
43+
* def memberTenant = 'member' + suffix
44+
* def memberTenantId = uuid()
45+
46+
* def consortiumId = uuid()
47+
48+
* def consortiaAdmin = { id: '#(uuid())', username: 'consortia_admin', password: 'consortia_admin_password', tenant: '#(centralTenant)' }
49+
* def memberAdmin = { id: '#(uuid())', username: 'member_admin', password: 'member_admin_password', tenant: '#(memberTenant)' }
50+
51+
* def memberTenantIdpAlias = memberTenant + '-keycloak-oidc'
52+
53+
# Create the central and member tenants.
54+
* call setupTenant { tenant: '#(centralTenant)', tenantId: '#(centralTenantId)', user: '#(consortiaAdmin)' }
55+
* call setupTenant { tenant: '#(memberTenant)', tenantId: '#(memberTenantId)', user: '#(memberAdmin)' }
56+
57+
# Register both tenants in the consortium.
58+
* call eurekaLogin { username: '#(consortiaAdmin.username)', password: '#(consortiaAdmin.password)', tenant: '#(centralTenant)' }
59+
* configure cookies = null
60+
* call setupConsortium { tenant: '#(centralTenant)' }
61+
* call setupTenantForConsortia { tenant: '#(centralTenant)', isCentral: true, code: 'CON' }
62+
* call setupTenantForConsortia { tenant: '#(memberTenant)', isCentral: false, code: 'MEM' }
63+
* configure headers = null
64+
* configure cookies = null
65+
66+
# Create the member-tenant user.
67+
* def memberToken = karate.call('classpath:common-consortia/eureka/keycloak.feature@getAuthorizationToken', { tenant: memberTenant }).okapitoken
68+
* def userId = uuid()
69+
* def memberUsername = 'MemberUser' + nowMillis()
70+
* def expectedFederatedUsername = memberUsername.toLowerCase()
71+
72+
Given path 'users-keycloak', 'users'
73+
And headers { 'Content-Type': 'application/json', 'Accept': '*/*', 'x-okapi-tenant': '#(memberTenant)', 'x-okapi-token': '#(memberToken)' }
74+
And request
75+
"""
76+
{
77+
"id": "#(userId)",
78+
"username": "#(memberUsername)",
79+
"active": true,
80+
"type": "staff",
81+
"personal": {
82+
"firstName": "Member",
83+
"lastName": "User"
84+
}
85+
}
86+
"""
87+
When method post
88+
Then status 201
89+
And match response.id == userId
90+
And match response.username == memberUsername
91+
92+
# Wait for consortium processing to create the shadow user in central tenant with the same UUID.
93+
* def centralToken = karate.call('classpath:common-consortia/eureka/keycloak.feature@getAuthorizationToken', { tenant: centralTenant }).okapitoken
94+
95+
Given path 'users-keycloak', 'users', userId
96+
And headers { 'Content-Type': 'application/json', 'Accept': '*/*', 'x-okapi-tenant': '#(centralTenant)', 'x-okapi-token': '#(centralToken)' }
97+
And retry until responseStatus == 200 && response.type == 'shadow'
98+
When method get
99+
Then status 200
100+
And match response.id == userId
101+
And match response.type == 'shadow'
102+
* def originalTenantId = response.customFields.originalTenantId ? response.customFields.originalTenantId : response.customFields.originaltenantid
103+
* match originalTenantId == memberTenant
104+
105+
# Verify that the shadow user resolves to the real member-tenant user when `overrideUser=true`
106+
Given path 'users-keycloak', '_self'
107+
And headers { 'Content-Type': 'application/json', 'Accept': '*/*', 'x-okapi-tenant': '#(centralTenant)', 'x-okapi-token': '#(centralToken)', 'x-okapi-user-id': '#(userId)' }
108+
And param overrideUser = true
109+
When method get
110+
Then status 200
111+
And match response.originalTenantId == memberTenant
112+
And match response.user.id == userId
113+
And match response.user.username == memberUsername
114+
And match response.user.type == 'staff'
115+
116+
# Verify that the shadow user is linked to the member tenant IdP in keycloak
117+
* def keycloakMasterLogin = call keycloakMaster
118+
* def keycloakMasterToken = keycloakMasterLogin.response.access_token
119+
120+
Given url baseKeycloakUrl
121+
And path 'admin', 'realms', centralTenant, 'users'
122+
And header Authorization = 'Bearer ' + keycloakMasterToken
123+
And param q = 'user_id:' + userId
124+
And param briefRepresentation = true
125+
And retry until response.length == 1
126+
When method get
127+
Then status 200
128+
* def centralKeycloakUserId = response[0].id
129+
130+
Given url baseKeycloakUrl
131+
And path 'admin', 'realms', centralTenant, 'users', centralKeycloakUserId, 'federated-identity'
132+
And header Authorization = 'Bearer ' + keycloakMasterToken
133+
And retry until karate.filter(response, x => x.identityProvider == memberTenantIdpAlias).length == 1
134+
When method get
135+
Then status 200
136+
* def providerLinks = karate.filter(response, x => x.identityProvider == memberTenantIdpAlias)
137+
* match providerLinks[0] contains
138+
"""
139+
{
140+
"identityProvider": "#(memberTenantIdpAlias)",
141+
"userId": "#(expectedFederatedUsername)",
142+
"userName": "#(expectedFederatedUsername)"
143+
}
144+
"""

mod-users-keycloak/src/test/java/org/folio/ModUsersKeycloakTests.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,9 @@ void authUsers() {
4545
void passwordReset() {
4646
runFeatureTest("password-reset");
4747
}
48+
49+
@Test
50+
void userIdentityProvider() {
51+
runFeatureTest("consortia/shadow-user-idp-linking");
52+
}
4853
}

0 commit comments

Comments
 (0)