Skip to content

Commit ed6af61

Browse files
committed
Fixes #319
1 parent 814b1e9 commit ed6af61

File tree

7 files changed

+219
-16
lines changed

7 files changed

+219
-16
lines changed

Changelog.md

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,64 @@
11
# Changelog
22

3+
## 8.5.0
4+
5+
- Only replace the `eduId` in the `/introspect` during attribute pseudonymisation when the result from
6+
`/attribute-manipulation` contains an eduID ([#319](https://github.com/OpenConext/OpenConext-oidcng/issues/319))
7+
38
## 8.4.0
4-
- Only call the `/attribute-manipulation` in the `/introspect` endpoint when the Resource Server has an institutionGUID ([#1050](https://github.com/OpenConext/OpenConext-myconext/issues/1050))
9+
10+
- Only call the `/attribute-manipulation` in the `/introspect` endpoint when the Resource Server has an
11+
institutionGUID ([#1050](https://github.com/OpenConext/OpenConext-myconext/issues/1050))
512
- Upgrade to Spring Boot 3.5.8
613
- Avoid `NullPointerException` in comparing redirect URI's with defensive comparisons
714
- Support for native applications with IPV6 [::1] as the hostname
815
- Upped dependencies for logstash, zxing, commons-io, jakarta.xml, actions/cache and codecov-action
916

1017
## 8.3.0
11-
- Only call the `/attribute-manipulation` in the `/introspect` endpoint when entityID's and institutionGUID's are different ([#1041](https://github.com/OpenConext/OpenConext-myconext/issues/1041))
18+
19+
- Only call the `/attribute-manipulation` in the `/introspect` endpoint when entityID's and institutionGUID's are
20+
different ([#1041](https://github.com/OpenConext/OpenConext-myconext/issues/1041))
1221

1322
## 8.2.0
23+
1424
- Added the authenticating authority to the user_info endpoint
15-
- Upped dependencies for nimbusds, bouncycastle and opensaml
25+
- Upped dependencies for nimbusds, bouncycastle and opensaml
1626

1727
## 8.1.1
28+
1829
- Bugfix for Spring Crypto not accepting BCrypt secrets longer than 72 bytes
1930

2031
## 8.1.0
21-
- Allow POST requests to `/oidc/authorize` (enables form_post submissions) ([#263](https://github.com/OpenConext/OpenConext-oidcng/issues/263))
22-
- Ensure URIs passed in the `login_hint` are absolute (PR [#271](https://github.com/OpenConext/OpenConext-oidcng/pull/271))
32+
33+
- Allow POST requests to `/oidc/authorize` (enables form_post
34+
submissions) ([#263](https://github.com/OpenConext/OpenConext-oidcng/issues/263))
35+
- Ensure URIs passed in the `login_hint` are absolute (
36+
PR [#271](https://github.com/OpenConext/OpenConext-oidcng/pull/271))
2337
- Prevent duplicate keys ([#239](https://github.com/OpenConext/OpenConext-oidcng/issues/239))
2438
- Do not expose mappings
2539
- Add eduPersonAssurance claim
2640
- Support preferred language user attribute
27-
- Switch to JDK 21 and upgrade Spring Boot and dependencies (oauth2-oidc-sdk 11.23.1, OpenSAML 5.1.4, xmlsec 4.0.4, BouncyCastle 1.80, logstash-logback-encoder 8.1)
41+
- Switch to JDK 21 and upgrade Spring Boot and dependencies (oauth2-oidc-sdk 11.23.1, OpenSAML 5.1.4, xmlsec 4.0.4,
42+
BouncyCastle 1.80, logstash-logback-encoder 8.1)
2843
- Enable @Scheduled annotations in standalone mode
2944
- Include SRAM services
3045
- Build improvements: ARM images, Docker deployment refactoring, multi-module Maven structure
3146
- CI and plugin upgrades
3247

3348
## 8.0.1
34-
- Backward compatibility for comma-separated scope values ([#238](https://github.com/OpenConext/OpenConext-oidcng/issues/238))
49+
50+
- Backward compatibility for comma-separated scope
51+
values ([#238](https://github.com/OpenConext/OpenConext-oidcng/issues/238))
3552
- Device code flow textual fixes (NL)
3653

3754
## 8.0.0
55+
3856
- Migrate to Spring Boot 3 (incl. Spring Security 6) and align codebase accordingly
3957
- Migrate SAML stack to OpenSAML 5; adapt request/response handling and validations
4058
- Update OAuth 2.0 / OIDC SDK to 11.22.1 (PR [#229](https://github.com/OpenConext/OpenConext-oidcng/pull/229))
41-
- Update GitHub Actions: setup-java to v4 (PR [#210](https://github.com/OpenConext/OpenConext-oidcng/pull/210)), codecov-action to 5.3.1 (PR [#227](https://github.com/OpenConext/OpenConext-oidcng/pull/227)), mongodb action to 1.12.0 (PR [#222](https://github.com/OpenConext/OpenConext-oidcng/pull/222))
59+
- Update GitHub Actions: setup-java to v4 (PR [#210](https://github.com/OpenConext/OpenConext-oidcng/pull/210)),
60+
codecov-action to 5.3.1 (PR [#227](https://github.com/OpenConext/OpenConext-oidcng/pull/227)), mongodb action to
61+
1.12.0 (PR [#222](https://github.com/OpenConext/OpenConext-oidcng/pull/222))
4262
- Ensure the original SAML AuthnRequest ID is preserved in the authentication flow
4363
- Upgrade mail SMTP dependency and related test adjustments
4464
- update PKCE-related test vectors to compliant values

oidc/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<parent>
1010
<groupId>org.openconext</groupId>
1111
<artifactId>oidcng-parent</artifactId>
12-
<version>8.4.0</version>
12+
<version>8.5.0</version>
1313
</parent>
1414

1515
<dependencyManagement>

oidc/src/main/java/oidc/eduid/AttributePseudonymisation.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ public Optional<Map<String, String>> pseudonymise(OpenIDClient resourceServer, O
8383
.queryParam("sp_entity_id", resourceServer.getClientId())
8484
.queryParam("sp_institution_guid", resourceServerInstitutionGuid)
8585
.toUriString();
86+
8687
ResponseEntity<Map<String, String>> responseEntity =
8788
restTemplate.exchange(uriString, HttpMethod.GET, requestEntity, new ParameterizedTypeReference<>() {
8889
});

oidc/src/main/java/oidc/endpoints/IntrospectEndpoint.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,13 +152,15 @@ public ResponseEntity<Map<String, Object>> introspect(HttpServletRequest request
152152
@SuppressWarnings("unchecked")
153153
private boolean validPseudonymisation(Map<String, Object> userAttributes, OpenIDClient resourceServer, OpenIDClient openIDClient) {
154154
String eduId = (String) userAttributes.get("eduid");
155-
Optional<Map<String, String>> pseudonymiseResult = attributePseudonymisation.pseudonymise(resourceServer, openIDClient, eduId);
156-
if (pseudonymiseResult.isPresent() && !pseudonymiseResult.get().containsKey("eduperson_principal_name")) {
157-
//The user is not linked to an IdP belonging to this RS
158-
userAttributes.put("eduid", pseudonymiseResult.get().get("eduid"));
155+
Map<String, String> pseudonymiseResult = attributePseudonymisation.pseudonymise(resourceServer, openIDClient, eduId)
156+
.orElseGet(Collections::emptyMap);
157+
if (pseudonymiseResult.containsKey("eduid") &&
158+
!pseudonymiseResult.containsKey("eduperson_principal_name")) {
159+
//The user is not linked to an IdP belonging to this RS - only replace the pseudo eduid and not other attributes
160+
userAttributes.put("eduid", pseudonymiseResult.get("eduid"));
159161
return false;
160162
}
161-
userAttributes.putAll(pseudonymiseResult.orElseGet(Collections::emptyMap));
163+
userAttributes.putAll(pseudonymiseResult);
162164
return true;
163165
}
164166

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
package oidc.endpoints;
2+
3+
import com.github.tomakehurst.wiremock.junit.WireMockRule;
4+
import com.nimbusds.oauth2.sdk.GrantType;
5+
import io.restassured.response.Response;
6+
import oidc.AbstractIntegrationTest;
7+
import org.apache.http.HttpStatus;
8+
import org.junit.ClassRule;
9+
import org.junit.Test;
10+
import org.springframework.boot.test.context.SpringBootTest;
11+
12+
import java.io.IOException;
13+
import java.util.HashMap;
14+
import java.util.Map;
15+
import java.util.UUID;
16+
17+
import static com.github.tomakehurst.wiremock.client.WireMock.*;
18+
import static io.restassured.RestAssured.given;
19+
import static org.junit.Assert.assertEquals;
20+
21+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
22+
properties = {
23+
"cron.node-cron-job-responsible=false",
24+
"eduid.uri=http://localhost:8099/attribute-manipulation",
25+
"features.enforce-eduid-resource-server-linked-account=false"
26+
27+
})
28+
public class IntrospectEndpointDisabledEduIDEnforcementTest extends AbstractIntegrationTest {
29+
30+
@ClassRule
31+
public static WireMockRule wireMockRule = new WireMockRule(8099);
32+
33+
@Test
34+
public void introspectionEduIdEmptyPseudonymisation() throws IOException {
35+
Map<String, String> queryParams = new HashMap<>();
36+
queryParams.put("scope", "openid");
37+
queryParams.put("client_id", "mock-sp");
38+
queryParams.put("response_type", "code");
39+
queryParams.put("redirect_uri", openIDClient("mock-sp").getRedirectUrls().get(0));
40+
41+
Response response = given().redirects().follow(false)
42+
.when()
43+
.header("Content-type", "application/json")
44+
.queryParams(queryParams)
45+
.get("oidc/authorize?user=eduid");
46+
47+
String code = getCode(response);
48+
Map<String, Object> body = doToken(code, "mock-sp", "secret", GrantType.AUTHORIZATION_CODE);
49+
String accessToken = (String) body.get("access_token");
50+
51+
stubFor(get(urlPathMatching("/attribute-manipulation")).willReturn(aResponse()
52+
.withHeader("Content-Type", "application/json")
53+
.withBody(objectMapper.writeValueAsString(Map.of()))));
54+
Map<String, Object> results = given()
55+
.when()
56+
.header("Content-type", "application/x-www-form-urlencoded")
57+
.auth()
58+
.preemptive()
59+
.basic("resource-server-playground-client", "secret")
60+
.formParam("token", accessToken)
61+
.post("oidc/introspect")
62+
.as(mapTypeRef);
63+
//See src/main/resources/data/eduid.json
64+
assertEquals("3415570f-be91-4ba8-b9ba-e479d18094d5", results.get("eduid"));
65+
}
66+
67+
@Test
68+
public void introspectionEduIdValidPseudonymisation() throws IOException {
69+
Map<String, String> queryParams = new HashMap<>();
70+
queryParams.put("scope", "openid");
71+
queryParams.put("client_id", "mock-sp");
72+
queryParams.put("response_type", "code");
73+
queryParams.put("redirect_uri", openIDClient("mock-sp").getRedirectUrls().get(0));
74+
75+
Response response = given().redirects().follow(false)
76+
.when()
77+
.header("Content-type", "application/json")
78+
.queryParams(queryParams)
79+
.get("oidc/authorize?user=eduid");
80+
81+
String code = getCode(response);
82+
Map<String, Object> body = doToken(code, "mock-sp", "secret", GrantType.AUTHORIZATION_CODE);
83+
String accessToken = (String) body.get("access_token");
84+
85+
Map<Object, Object> pseudonymiseResults = Map.of("eduid", UUID.randomUUID().toString());
86+
stubFor(get(urlPathMatching("/attribute-manipulation")).willReturn(aResponse()
87+
.withHeader("Content-Type", "application/json")
88+
.withBody(objectMapper.writeValueAsString(pseudonymiseResults))));
89+
90+
Map<String, Object> results = given()
91+
.when()
92+
.header("Content-type", "application/x-www-form-urlencoded")
93+
.auth()
94+
.preemptive()
95+
.basic("resource-server-playground-client", "secret")
96+
.formParam("token", accessToken)
97+
.post("oidc/introspect")
98+
.as(mapTypeRef);
99+
//Replaced pseudo eduID
100+
assertEquals(pseudonymiseResults.get("eduid"), results.get("eduid"));
101+
}
102+
103+
@Test
104+
public void introspectionEduIdErrorPseudonymisation() throws IOException {
105+
Map<String, String> queryParams = new HashMap<>();
106+
queryParams.put("scope", "openid");
107+
queryParams.put("client_id", "mock-sp");
108+
queryParams.put("response_type", "code");
109+
queryParams.put("redirect_uri", openIDClient("mock-sp").getRedirectUrls().get(0));
110+
111+
Response response = given().redirects().follow(false)
112+
.when()
113+
.header("Content-type", "application/json")
114+
.queryParams(queryParams)
115+
.get("oidc/authorize?user=eduid");
116+
117+
String code = getCode(response);
118+
Map<String, Object> body = doToken(code, "mock-sp", "secret", GrantType.AUTHORIZATION_CODE);
119+
String accessToken = (String) body.get("access_token");
120+
121+
stubFor(get(urlPathMatching("/attribute-manipulation"))
122+
.willReturn(aResponse()
123+
.withHeader("Content-Type", "application/json")
124+
.withStatus(HttpStatus.SC_BAD_REQUEST)));
125+
126+
Map<String, Object> results = given()
127+
.when()
128+
.header("Content-type", "application/x-www-form-urlencoded")
129+
.auth()
130+
.preemptive()
131+
.basic("resource-server-playground-client", "secret")
132+
.formParam("token", accessToken)
133+
.post("oidc/introspect")
134+
.as(mapTypeRef);
135+
//See src/main/resources/data/eduid.json
136+
assertEquals("3415570f-be91-4ba8-b9ba-e479d18094d5", results.get("eduid"));
137+
}
138+
139+
@Test
140+
public void introspectionEduIdRSNotLinkedPseudonymisation() throws IOException {
141+
Map<String, String> queryParams = new HashMap<>();
142+
queryParams.put("scope", "openid");
143+
queryParams.put("client_id", "mock-sp");
144+
queryParams.put("response_type", "code");
145+
queryParams.put("redirect_uri", openIDClient("mock-sp").getRedirectUrls().get(0));
146+
147+
Response response = given().redirects().follow(false)
148+
.when()
149+
.header("Content-type", "application/json")
150+
.queryParams(queryParams)
151+
.get("oidc/authorize?user=eduid");
152+
153+
String code = getCode(response);
154+
Map<String, Object> body = doToken(code, "mock-sp", "secret", GrantType.AUTHORIZATION_CODE);
155+
String accessToken = (String) body.get("access_token");
156+
157+
Map<Object, Object> pseudonymiseResults = Map.of(
158+
"eduid", UUID.randomUUID().toString(),
159+
"eduperson_principal_name", "some-eppn"
160+
);
161+
stubFor(get(urlPathMatching("/attribute-manipulation")).willReturn(aResponse()
162+
.withHeader("Content-Type", "application/json")
163+
.withBody(objectMapper.writeValueAsString(pseudonymiseResults))));
164+
165+
Map<String, Object> results = given()
166+
.when()
167+
.header("Content-type", "application/x-www-form-urlencoded")
168+
.auth()
169+
.preemptive()
170+
.basic("resource-server-playground-client", "secret")
171+
.formParam("token", accessToken)
172+
.post("oidc/introspect")
173+
.as(mapTypeRef);
174+
//See the pseudonymiseResults with an eduperson_principal_name
175+
assertEquals(pseudonymiseResults.get("eduid"), results.get("eduid"));
176+
assertEquals(pseudonymiseResults.get("eduperson_principal_name"), results.get("eduperson_principal_name"));
177+
}
178+
179+
180+
}

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
<groupId>org.openconext</groupId>
1313
<artifactId>oidcng-parent</artifactId>
14-
<version>8.4.0</version>
14+
<version>8.5.0</version>
1515
<name>oidcng</name>
1616
<packaging>pom</packaging>
1717

release/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<groupId>org.openconext</groupId>
77
<artifactId>oidcng-parent</artifactId>
8-
<version>8.4.0</version>
8+
<version>8.5.0</version>
99
</parent>
1010

1111
<artifactId>oidcng-release</artifactId>

0 commit comments

Comments
 (0)