Skip to content

Commit

Permalink
Swagger interface for external token API
Browse files Browse the repository at this point in the history
  • Loading branch information
oharsta committed Nov 17, 2023
1 parent a9a8487 commit ed5e156
Show file tree
Hide file tree
Showing 13 changed files with 118 additions and 30 deletions.
2 changes: 2 additions & 0 deletions server/src/main/java/access/api/InvitationController.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,15 @@
import java.util.*;
import java.util.stream.Collectors;

import static access.SwaggerOpenIdConfig.API_TOKENS_SCHEME_NAME;
import static access.SwaggerOpenIdConfig.OPEN_ID_SCHEME_NAME;
import static java.util.stream.Collectors.toSet;

@RestController
@RequestMapping(value = {"/api/v1/invitations", "/api/external/v1/invitations"}, produces = MediaType.APPLICATION_JSON_VALUE)
@Transactional
@SecurityRequirement(name = OPEN_ID_SCHEME_NAME, scopes = {"openid"})
@SecurityRequirement(name = API_TOKENS_SCHEME_NAME)
@EnableConfigurationProperties(SuperAdmin.class)
public class InvitationController implements HasManage {

Expand Down
2 changes: 2 additions & 0 deletions server/src/main/java/access/api/ManageController.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@
import java.util.*;
import java.util.stream.Collectors;

import static access.SwaggerOpenIdConfig.API_TOKENS_SCHEME_NAME;
import static access.SwaggerOpenIdConfig.OPEN_ID_SCHEME_NAME;

@RestController
@RequestMapping(value = {"/api/v1/manage", "/api/external/v1/manage"}, produces = MediaType.APPLICATION_JSON_VALUE)
@Transactional
@SecurityRequirement(name = OPEN_ID_SCHEME_NAME, scopes = {"openid"})
@SecurityRequirement(name = API_TOKENS_SCHEME_NAME)
@EnableConfigurationProperties(Config.class)
@SuppressWarnings("unchecked")
public class ManageController {
Expand Down
2 changes: 2 additions & 0 deletions server/src/main/java/access/api/RoleController.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@
import java.util.*;
import java.util.stream.Collectors;

import static access.SwaggerOpenIdConfig.API_TOKENS_SCHEME_NAME;
import static access.SwaggerOpenIdConfig.OPEN_ID_SCHEME_NAME;

@RestController
@RequestMapping(value = {"/api/v1/roles", "/api/external/v1/roles", }, produces = MediaType.APPLICATION_JSON_VALUE)
@Transactional
@SecurityRequirement(name = OPEN_ID_SCHEME_NAME, scopes = {"openid"})
@SecurityRequirement(name = API_TOKENS_SCHEME_NAME)
@EnableConfigurationProperties(Config.class)
public class RoleController {
private static final Log LOG = LogFactory.getLog(RoleController.class);
Expand Down
2 changes: 2 additions & 0 deletions server/src/main/java/access/api/UserController.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,15 @@
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

import static access.SwaggerOpenIdConfig.API_TOKENS_SCHEME_NAME;
import static access.SwaggerOpenIdConfig.OPEN_ID_SCHEME_NAME;

@RestController
@RequestMapping(value = {"/api/v1/users", "/api/external/v1/users"}, produces = MediaType.APPLICATION_JSON_VALUE)
@Transactional
@SecurityRequirement(name = OPEN_ID_SCHEME_NAME, scopes = {"openid"})
@EnableConfigurationProperties(Config.class)
@SecurityRequirement(name = API_TOKENS_SCHEME_NAME)
public class UserController {

private static final Log LOG = LogFactory.getLog(UserController.class);
Expand Down
2 changes: 2 additions & 0 deletions server/src/main/java/access/api/UserRoleController.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@
import java.util.List;
import java.util.Map;

import static access.SwaggerOpenIdConfig.API_TOKENS_SCHEME_NAME;
import static access.SwaggerOpenIdConfig.OPEN_ID_SCHEME_NAME;

@RestController
@RequestMapping(value = {"/api/v1/user_roles", "/api/external/v1/user_roles"}, produces = MediaType.APPLICATION_JSON_VALUE)
@Transactional
@SecurityRequirement(name = OPEN_ID_SCHEME_NAME, scopes = {"openid"})
@EnableConfigurationProperties(Config.class)
@SecurityRequirement(name = API_TOKENS_SCHEME_NAME)
public class UserRoleController {

private static final Log LOG = LogFactory.getLog(UserRoleController.class);
Expand Down
4 changes: 1 addition & 3 deletions server/src/main/java/access/cron/IdentityProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@

import java.io.Serializable;

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
public class IdentityProvider implements Serializable {

private String displayNameEn;
Expand All @@ -21,7 +19,7 @@ public class IdentityProvider implements Serializable {

public String getName() {
String language = LocaleContextHolder.getLocale().getLanguage();
if ("en".equals(language.toLowerCase())) {
if ("en".equalsIgnoreCase(language)) {
return StringUtils.hasText(displayNameEn) ? displayNameEn : displayNameNl;
}
return StringUtils.hasText(displayNameNl) ? displayNameNl : displayNameEn;
Expand Down
14 changes: 7 additions & 7 deletions server/src/main/java/access/security/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,10 @@ SecurityFilterChain sessionSecurityFilterChain(HttpSecurity http,
UserRepository userRepository,
@Value("${institution-admin.entitlement}") String entitlement,
@Value("${institution-admin.organization-guid-prefix}") String organizationGuidPrefix) throws Exception {
final RequestHeaderRequestMatcher apiTokenRequestMatcher = new RequestHeaderRequestMatcher(API_TOKEN_HEADER);
http
.csrf(c -> c
.ignoringRequestMatchers("/login/oauth2/code/oidcng")
.ignoringRequestMatchers("/api/v1/validations/**")
.ignoringRequestMatchers(apiTokenRequestMatcher))
.ignoringRequestMatchers("/api/v1/validations/**"))
.securityMatcher("/login/oauth2/**", "/oauth2/authorization/**", "/api/v1/**")
.authorizeHttpRequests(c -> c
.requestMatchers(
Expand All @@ -148,9 +146,6 @@ SecurityFilterChain sessionSecurityFilterChain(HttpSecurity http,
"/ui/**",
"internal/**")
.permitAll()
//The API token is secured in the UserHandlerMethodArgumentResolver
.requestMatchers(apiTokenRequestMatcher)
.permitAll()
.anyRequest()
.authenticated()
)
Expand Down Expand Up @@ -180,7 +175,8 @@ private OAuth2AuthorizationRequestResolver authorizationRequestResolver(
@Order(2)
SecurityFilterChain basicAuthenticationSecurityFilterChain(HttpSecurity http) throws Exception {
http.csrf(c -> c.disable())
.securityMatcher("/api/voot/**",
.securityMatcher(
"/api/voot/**",
"/api/external/v1/voot/**",
"/api/aa/**",
"/api/external/v1/aa/**",
Expand All @@ -200,11 +196,15 @@ SecurityFilterChain basicAuthenticationSecurityFilterChain(HttpSecurity http) th
@Bean
@Order(3)
SecurityFilterChain jwtSecurityFilterChain(HttpSecurity http) throws Exception {
final RequestHeaderRequestMatcher apiTokenRequestMatcher = new RequestHeaderRequestMatcher(API_TOKEN_HEADER);
http.csrf(c -> c.disable())
.securityMatcher("/api/external/v1/**")
.authorizeHttpRequests(c -> c
.requestMatchers("/api/external/v1/validations/**")
.permitAll()
//The API token is secured in the UserHandlerMethodArgumentResolver
.requestMatchers(apiTokenRequestMatcher)
.permitAll()
.anyRequest()
.authenticated()
)
Expand Down
3 changes: 2 additions & 1 deletion server/src/main/resources/manage/saml20_idp.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@
"name:en": "Idp UU EN",
"name:nl": "IdP UU NL",
"OrganizationName:en": "SURF bv",
"logo:0:url": "https://static.surfconext.nl/media/idp/surfconext.png"
"logo:0:url": "https://static.surfconext.nl/media/idp/surfconext.png",
"coin:institution_guid": "test_institution_guid"
}
}
}
Expand Down
21 changes: 12 additions & 9 deletions server/src/test/java/access/AbstractTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@
import java.util.function.Consumer;
import java.util.function.UnaryOperator;

import static access.Seed.ORGANISATION_GUID;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static io.restassured.RestAssured.given;
import static org.junit.jupiter.api.Assertions.assertEquals;
Expand Down Expand Up @@ -114,14 +113,6 @@ public abstract class AbstractTest {
@LocalServerPort
protected int port;

protected static final UnaryOperator<Map<String, Object>> institutionalAdminEntitlementOperator = m -> {
m.put("eduperson_entitlement",
List.of(
"urn:mace:surfnet.nl:surfnet.nl:sab:role:SURFconextverantwoordelijke",
"urn:mace:surfnet.nl:surfnet.nl:sab:organizationGUID:" + ORGANISATION_GUID
));
return m;
};
@BeforeAll
protected static void beforeAll() {
RestAssured.config = RestAssuredConfig.config()
Expand Down Expand Up @@ -475,4 +466,16 @@ protected void stubForUpdateScimRolePatch() throws JsonProcessingException {
));
}

protected UnaryOperator<Map<String, Object>> institutionalAdminEntitlementOperator(String organisationGuid) {
return m -> {
m.put("eduperson_entitlement",
List.of(
"urn:mace:surfnet.nl:surfnet.nl:sab:role:SURFconextverantwoordelijke",
"urn:mace:surfnet.nl:surfnet.nl:sab:organizationGUID:" + organisationGuid
));
return m;
};
}


}
47 changes: 44 additions & 3 deletions server/src/test/java/access/api/APITokenControllerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import java.util.List;
import java.util.Map;
import java.util.UUID;

import static access.Seed.*;
import static io.restassured.RestAssured.given;
Expand All @@ -21,7 +22,8 @@ class APITokenControllerTest extends AbstractTest {
@Test
void apiTokensByInstitution() throws Exception {
super.stubForManageProviderByOrganisationGUID(ORGANISATION_GUID);
AccessCookieFilter accessCookieFilter = openIDConnectFlow("/api/v1/users/me", INSTITUTION_ADMIN, institutionalAdminEntitlementOperator);
AccessCookieFilter accessCookieFilter = openIDConnectFlow("/api/v1/users/me", INSTITUTION_ADMIN,
institutionalAdminEntitlementOperator(ORGANISATION_GUID));

List<APIToken> tokens = given()
.when()
Expand All @@ -38,7 +40,8 @@ void apiTokensByInstitution() throws Exception {
@Test
void create() throws Exception {
super.stubForManageProviderByOrganisationGUID(ORGANISATION_GUID);
AccessCookieFilter accessCookieFilter = openIDConnectFlow("/api/v1/users/me", INSTITUTION_ADMIN, institutionalAdminEntitlementOperator);
AccessCookieFilter accessCookieFilter = openIDConnectFlow("/api/v1/users/me", INSTITUTION_ADMIN,
institutionalAdminEntitlementOperator(ORGANISATION_GUID));
//First get the value, otherwise the creation will fail
Map<String, String> res = given()
.when()
Expand Down Expand Up @@ -69,11 +72,29 @@ void create() throws Exception {
assertEquals(HashGenerator.hashToken(token), apiTokenFromDB.getHashedValue());
}

@Test
void createWithFaultyToken() throws Exception {
super.stubForManageProviderByOrganisationGUID(ORGANISATION_GUID);
AccessCookieFilter accessCookieFilter = openIDConnectFlow("/api/v1/users/me", INSTITUTION_ADMIN,
institutionalAdminEntitlementOperator(ORGANISATION_GUID));
given()
.when()
.filter(accessCookieFilter.cookieFilter())
.accept(ContentType.JSON)
.header(accessCookieFilter.csrfToken().getHeaderName(), accessCookieFilter.csrfToken().getToken())
.contentType(ContentType.JSON)
.body(new APIToken("wrong", "wrong", "wrong"))
.post("/api/v1/tokens")
.then()
.statusCode(403);
}

@Test
void deleteToken() throws Exception {
super.stubForManageProviderByOrganisationGUID(ORGANISATION_GUID);
APIToken apiToken = apiTokenRepository.findByHashedValue(HashGenerator.hashToken(API_TOKEN_HASH)).get();
AccessCookieFilter accessCookieFilter = openIDConnectFlow("/api/v1/users/me", INSTITUTION_ADMIN, institutionalAdminEntitlementOperator);
AccessCookieFilter accessCookieFilter = openIDConnectFlow("/api/v1/users/me", INSTITUTION_ADMIN,
institutionalAdminEntitlementOperator(ORGANISATION_GUID));

given()
.when()
Expand All @@ -87,4 +108,24 @@ void deleteToken() throws Exception {
.statusCode(204);
assertEquals(0, apiTokenRepository.count());
}

@Test
void deleteOtherToken() throws Exception {
String organisationGUID = "test_institution_guid";
super.stubForManageProviderByOrganisationGUID(organisationGUID);
APIToken apiToken = apiTokenRepository.findByHashedValue(HashGenerator.hashToken(API_TOKEN_HASH)).get();
AccessCookieFilter accessCookieFilter = openIDConnectFlow("/api/v1/users/me", INSTITUTION_ADMIN,
institutionalAdminEntitlementOperator(organisationGUID));

given()
.when()
.filter(accessCookieFilter.cookieFilter())
.accept(ContentType.JSON)
.header(accessCookieFilter.csrfToken().getHeaderName(), accessCookieFilter.csrfToken().getToken())
.contentType(ContentType.JSON)
.pathParams("id", apiToken.getId())
.delete("/api/v1/tokens/{id}")
.then()
.statusCode(403);
}
}
25 changes: 19 additions & 6 deletions server/src/test/java/access/api/RoleControllerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,17 @@

import access.AbstractTest;
import access.AccessCookieFilter;
import access.Seed;
import access.manage.EntityType;
import access.model.RemoteProvisionedGroup;
import access.model.Role;
import access.model.RoleExists;
import io.restassured.common.mapper.TypeRef;
import io.restassured.http.ContentType;
import io.restassured.http.Headers;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;

import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.UnaryOperator;

import static access.Seed.*;
import static access.security.SecurityConfig.API_TOKEN_HEADER;
Expand Down Expand Up @@ -197,7 +193,7 @@ void rolesByApplicationInstitutionAdmin() throws Exception {
super.stubForManageProviderByOrganisationGUID(ORGANISATION_GUID);

AccessCookieFilter accessCookieFilter = openIDConnectFlow("/api/v1/users/me", INSTITUTION_ADMIN,
institutionalAdminEntitlementOperator);
institutionalAdminEntitlementOperator(ORGANISATION_GUID));
List<Role> roles = given()
.when()
.filter(accessCookieFilter.cookieFilter())
Expand Down Expand Up @@ -324,6 +320,23 @@ void roleNotFound() throws Exception {

}

@Test
void rolesByApplicationInstitutionAdminByAPI() throws Exception {
super.stubForManageProviderByEntityID(EntityType.SAML20_SP, "https://wiki");
super.stubForManageProviderByEntityID(EntityType.OIDC10_RP, "https://wiki");
super.stubForManageProviderByOrganisationGUID(ORGANISATION_GUID);

List<Role> roles = given()
.when()
.header(API_TOKEN_HEADER, API_TOKEN_HASH)
.accept(ContentType.JSON)
.contentType(ContentType.JSON)
.get("/api/external/v1/roles")
.as(new TypeRef<>() {
});
assertEquals(4, roles.size());
}

@Test
void deleteRoleWithAPI() throws Exception {
Role role = roleRepository.search("wiki", 1).get(0);
Expand All @@ -341,7 +354,7 @@ void deleteRoleWithAPI() throws Exception {
.header(API_TOKEN_HEADER, API_TOKEN_HASH)
.contentType(ContentType.JSON)
.pathParams("id", role.getId())
.delete("/api/v1/roles/{id}")
.delete("/api/external/v1/roles/{id}")
.then()
.statusCode(204);
assertEquals(0, roleRepository.search("wiki", 1).size());
Expand Down
3 changes: 2 additions & 1 deletion server/src/test/java/access/api/UserControllerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ void meWithOauth2Login() throws Exception {
void institutionAdminProvision() throws Exception {
super.stubForManageProviderByOrganisationGUID(ORGANISATION_GUID);

AccessCookieFilter accessCookieFilter = openIDConnectFlow("/api/v1/users/me", "new_institution_admin", institutionalAdminEntitlementOperator);
AccessCookieFilter accessCookieFilter = openIDConnectFlow("/api/v1/users/me", "new_institution_admin",
institutionalAdminEntitlementOperator(ORGANISATION_GUID));

User user = given()
.when()
Expand Down
21 changes: 21 additions & 0 deletions server/src/test/java/access/cron/IdentityProviderTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package access.cron;

import org.junit.jupiter.api.Test;
import org.springframework.context.i18n.LocaleContextHolder;

import java.util.Locale;

import static org.junit.jupiter.api.Assertions.*;

class IdentityProviderTest {

@Test
void getName() {
IdentityProvider identityProvider = new IdentityProvider(null, "nl", "logo");
assertEquals("nl", identityProvider.getName());

LocaleContextHolder.setLocale(Locale.forLanguageTag("nl"));
identityProvider = new IdentityProvider("en", "nl", "logo");
assertEquals("nl", identityProvider.getName());
}
}

0 comments on commit ed5e156

Please sign in to comment.