Skip to content

Commit 83945d9

Browse files
committed
Fixes #442
1 parent 71e9933 commit 83945d9

6 files changed

Lines changed: 97 additions & 10 deletions

File tree

client/src/locale/en.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ const en = {
131131
register: "Register new organisation: ‘<strong>{{name}}</strong>’",
132132
confirmation: "Are you sure you want to register a new organisation called {{name}}?",
133133
confirmationAfter: "Your organisation is created. You can continue to create applications. You will be contacted by mail within <strong>3 working days</strong>. Your reference number of our internal ticketing system is <strong>{{jiraKey}}</strong></strong>",
134-
neworganisation: "New organisation created",
134+
newOrganization: "New organisation created",
135135
flash: "Created organisation {{name}}."
136136
},
137137
userHome: {

server/src/main/java/access/api/PublicController.java

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,69 @@
11
package access.api;
22

3+
import access.config.Config;
34
import access.manage.Manage;
4-
import access.manage.ManageData;
55
import access.model.EntityType;
66
import access.model.Environment;
77
import lombok.SneakyThrows;
88
import org.apache.commons.logging.Log;
99
import org.apache.commons.logging.LogFactory;
1010
import org.springframework.http.MediaType;
1111
import org.springframework.http.ResponseEntity;
12+
import org.springframework.security.core.Authentication;
13+
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
1214
import org.springframework.web.bind.annotation.GetMapping;
1315
import org.springframework.web.bind.annotation.PathVariable;
1416
import org.springframework.web.bind.annotation.RequestMapping;
1517
import org.springframework.web.bind.annotation.RestController;
1618

1719
import java.util.List;
1820
import java.util.Map;
21+
import java.util.Set;
22+
import java.util.stream.Collectors;
23+
24+
import static access.manage.ManageData.getData;
25+
import static access.manage.ManageData.getMetaDataFields;
1926

2027
@RestController
2128
@RequestMapping(value = {"/api/v1/public"}, produces = MediaType.APPLICATION_JSON_VALUE)
29+
@SuppressWarnings("unchecked")
2230
public class PublicController {
2331

2432
private static final Log LOG = LogFactory.getLog(PublicController.class);
2533

2634
private final Manage manage;
35+
private final Config config;
2736

2837
@SneakyThrows
29-
public PublicController(Manage manage) {
38+
public PublicController(Manage manage, Config config) {
3039
this.manage = manage;
40+
this.config = config;
3141
}
3242

3343
@GetMapping("/service-providers")
34-
public ResponseEntity<List<Map<String, Object>>> serviceProviders() {
44+
public ResponseEntity<List<Map<String, Object>>> serviceProviders(Authentication authentication) {
3545
LOG.debug("/serviceProviders");
36-
return ResponseEntity.ok(manage.serviceProvidersLight(Environment.PROD));
46+
List<Map<String, Object>> providers = manage.serviceProvidersLight(Environment.PROD);
47+
if (authentication == null) {
48+
providers.removeIf(provider -> removeNonPublicProvider(provider));
49+
} else {
50+
DefaultOidcUser user = (DefaultOidcUser) authentication.getPrincipal();
51+
String schacHomeOrganization = (String) user.getClaims().get("schac_home_organization");
52+
boolean isExternalUserFromSchacHome = schacHomeOrganization.equals(config.getEduIdSchacHomeOrganization());
53+
if (isExternalUserFromSchacHome) {
54+
providers.removeIf(provider -> removeNonPublicProvider(provider));
55+
} else {
56+
//We need the identity provider to see which providers are connected and are therefore visiblle
57+
String authenticatingAuthority = (String) user.getClaims().get("authenticating_authority");
58+
Map<String, Object> identityProvider = manage.identityProviderByEntityID(authenticatingAuthority);
59+
Set<String> allowedEntities = ((List<Map<String, String>>) getData(identityProvider).getOrDefault("allowedEntities", List.of()))
60+
.stream()
61+
.map(allowedEntity -> allowedEntity.get("name"))
62+
.collect(Collectors.toSet());
63+
providers.removeIf(provider -> removeNonPublicProvider(provider, allowedEntities));
64+
}
65+
}
66+
return ResponseEntity.ok(providers);
3767
}
3868

3969
@GetMapping("/identity-providers")
@@ -49,10 +79,23 @@ public ResponseEntity<Map<String, Object>> serviceProviderDetail(
4979
LOG.debug("/identityProviders");
5080
Map<String, Object> provider = manage
5181
.providerById(entityType, identifier, Environment.PROD);
52-
ManageData.getMetaDataFields(ManageData.getData(provider)).keySet()
82+
getMetaDataFields(getData(provider)).keySet()
5383
.removeIf(key -> key.startsWith("contacts:"));
5484
return ResponseEntity.ok(provider);
5585
}
5686

87+
private boolean removeNonPublicProvider(Map<String, Object> provider) {
88+
Map<String, Object> metaDataFields = getMetaDataFields(getData(provider));
89+
boolean hidden = (boolean) metaDataFields.getOrDefault("coin:ss:hidden", false);
90+
boolean idpVisibleOnly = (boolean) metaDataFields.getOrDefault("coin:ss:idp_visible_only", false);
91+
return hidden || idpVisibleOnly;
92+
}
5793

94+
private boolean removeNonPublicProvider(Map<String, Object> provider, Set<String> allowedEntities) {
95+
Map<String, Object> data = getData(provider);
96+
Map<String, Object> metaDataFields = getMetaDataFields(data);
97+
boolean hidden = (boolean) metaDataFields.getOrDefault("coin:ss:hidden", false);
98+
boolean idpVisibleOnly = (boolean) metaDataFields.getOrDefault("coin:ss:idp_visible_only", false);
99+
return hidden || (idpVisibleOnly && !allowedEntities.contains((String) data.get("entityid")));
100+
}
58101
}

server/src/main/java/access/manage/RemoteManage.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,8 @@ public List<Map<String, Object>> serviceProvidersLight(Environment environment)
289289
Map<String, Object> baseQuery = getBaseQuery(false);
290290
List requestedAttributes = (List) baseQuery.get("REQUESTED_ATTRIBUTES");
291291
requestedAttributes.add("metaDataFields.coin:interfed_source");
292+
requestedAttributes.add("metaDataFields.coin:ss:hidden");
293+
requestedAttributes.add("metaDataFields.coin:ss:idp_visible_only");
292294
requestedAttributes.add("metaDataFields.application_tags");
293295

294296
String url = String.format("%s/manage/api/internal/search/%s",

server/src/main/resources/manage/oidc10_rp.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
"OrganizationName:en": "SURF bv",
3333
"logo:0:url": "https://static.surfconext.nl/media/idp/surfconext.png",
3434
"coin:application_url": "https://default-url-cloud.org",
35-
"coin:dashboard_connect_option": "connect_without_interaction_without_email"
35+
"coin:dashboard_connect_option": "connect_without_interaction_without_email",
36+
"coin:ss:idp_visible_only": true
3637
}
3738
}
3839
},
@@ -48,7 +49,8 @@
4849
"name:nl": "Unused NL",
4950
"OrganizationName:en": "SURF bv",
5051
"logo:0:url": "https://static.surfconext.nl/media/idp/surfconext.png",
51-
"coin:application_url": "https://default-url-unused.org"
52+
"coin:application_url": "https://default-url-unused.org",
53+
"coin:ss:hidden": true
5254
}
5355
}
5456
},

server/src/main/resources/manage/saml20_sp.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
"coin:institution_guid": "ad93daef-0911-e511-80d0-005056956c1a",
1414
"coin:application_url": "https://default-url-wiki.org",
1515
"application_tags": ["education"],
16-
"coin:interfed_source": "WO"
16+
"coin:interfed_source": "WO",
17+
"coin:ss:idp_visible_only": true
1718
},
1819
"arp": {
1920
"enabled": true,

server/src/test/java/access/api/PublicControllerTest.java

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package access.api;
22

33
import access.AbstractTest;
4+
import access.AccessCookieFilter;
45
import access.model.EntityType;
56
import access.model.Environment;
67
import io.restassured.common.mapper.TypeRef;
@@ -27,7 +28,45 @@ void serviceProviders() {
2728
.get("/api/v1/public/service-providers")
2829
.as(new TypeRef<>() {
2930
});
30-
assertEquals(8, serviceProviders.size());
31+
//Three are filtered out because of coin:ss:hidden and coin:ss:idp_visible_only
32+
assertEquals(5, serviceProviders.size());
33+
}
34+
35+
@Test
36+
void serviceProvidersWithAuthenticatedInternalUser() {
37+
AccessCookieFilter accessCookieFilter = mockLoginFlow(MANAGE_SUB);
38+
this.stubForServiceProviders();
39+
this.stubForIdentityProviderByEntityId("http://mock-idp");
40+
List<Map<String, Object>> serviceProviders = given()
41+
.when()
42+
.filter(accessCookieFilter.cookieFilter())
43+
.header(csrfHeader(accessCookieFilter))
44+
.accept(ContentType.JSON)
45+
.contentType(ContentType.JSON)
46+
.get("/api/v1/public/service-providers")
47+
.as(new TypeRef<>() {
48+
});
49+
//Only two are filtered out because of coin:ss:hidden and coin:ss:idp_visible_only and internal user
50+
assertEquals(6, serviceProviders.size());
51+
}
52+
53+
@Test
54+
void serviceProvidersWithAuthenticatedExternalUser() {
55+
Map<String, Object> body = Map.of("sub", MANAGE_SUB, "schac_home_organization", "eduid.nl");
56+
AccessCookieFilter accessCookieFilter = mockLoginFlow(body);
57+
this.stubForServiceProviders();
58+
this.stubForIdentityProviderByEntityId("http://mock-idp");
59+
List<Map<String, Object>> serviceProviders = given()
60+
.when()
61+
.filter(accessCookieFilter.cookieFilter())
62+
.header(csrfHeader(accessCookieFilter))
63+
.accept(ContentType.JSON)
64+
.contentType(ContentType.JSON)
65+
.get("/api/v1/public/service-providers")
66+
.as(new TypeRef<>() {
67+
});
68+
//Three are filtered out because of coin:ss:hidden and coin:ss:idp_visible_only and external user
69+
assertEquals(5, serviceProviders.size());
3170
}
3271

3372
@Test

0 commit comments

Comments
 (0)