Skip to content

Commit

Permalink
Feat: Authorisation for Revocation Endpoints (#146)
Browse files Browse the repository at this point in the history
* Add required authorisation for Revocation Endpoints

* Remove ValueMappers Wrapper
  • Loading branch information
f11h authored Jan 12, 2022
1 parent f35dab5 commit ab14590
Show file tree
Hide file tree
Showing 12 changed files with 575 additions and 224 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@

package eu.europa.ec.dgc.gateway.config;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import javax.annotation.PostConstruct;
Expand All @@ -46,7 +44,7 @@ public class ValidationRuleSchemaProvider {
private Schema validationRuleSchema;

@PostConstruct
void setup() throws FileNotFoundException, IOException {
void setup() throws IOException {
InputStream schemaInputStream = ResourceUtils.getURL(configProperties.getValidationRuleSchema()).openStream();

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@
package eu.europa.ec.dgc.gateway.entity;

import java.time.ZonedDateTime;
import java.util.List;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
Expand Down Expand Up @@ -81,6 +85,12 @@ public class TrustedPartyEntity {
@Enumerated(EnumType.STRING)
CertificateType certificateType;

@Enumerated(EnumType.STRING)
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "trusted_party_roles")
@Column(name = "role")
List<CertificateRoles> certificateRoles;

public enum CertificateType {
/**
* Certificate which the member state is using to authenticate at DGC Gateway (NBTLS).
Expand All @@ -97,4 +107,21 @@ public enum CertificateType {
*/
CSCA
}

public enum CertificateRoles {
/**
* User with this certificate is allowed to download Revocation List.
*/
REVOCATION_LIST_READER,

/**
* User with this certificate is allowed to upload Revocation List.
*/
REVOCATION_UPLOADER,

/**
* User with this certificate is allowed to delete Revocation List.
*/
REVOCATION_DELETER
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import eu.europa.ec.dgc.gateway.restapi.dto.revocation.RevocationBatchListDto;
import eu.europa.ec.dgc.gateway.restapi.filter.CertificateAuthenticationFilter;
import eu.europa.ec.dgc.gateway.restapi.filter.CertificateAuthenticationRequired;
import eu.europa.ec.dgc.gateway.restapi.filter.CertificateAuthenticationRole;
import eu.europa.ec.dgc.gateway.restapi.mapper.RevocationBatchMapper;
import eu.europa.ec.dgc.gateway.service.RevocationListService;
import io.swagger.v3.oas.annotations.Operation;
Expand Down Expand Up @@ -79,7 +80,7 @@ public class CertificateRevocationListController {
/**
* Endpoint to download Revocation Batch List.
*/
@CertificateAuthenticationRequired
@CertificateAuthenticationRequired(requiredRoles = CertificateAuthenticationRole.RevocationListReader)
@GetMapping(path = "", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(
security = {
Expand Down Expand Up @@ -129,7 +130,7 @@ public ResponseEntity<RevocationBatchListDto> downloadBatchList(
/**
* Endpoint to download Revocation Batch.
*/
@CertificateAuthenticationRequired
@CertificateAuthenticationRequired(requiredRoles = CertificateAuthenticationRole.RevocationListReader)
@GetMapping(value = "/{batchId}", produces = {
CmsStringMessageConverter.CONTENT_TYPE_CMS_TEXT_VALUE, CmsStringMessageConverter.CONTENT_TYPE_CMS_VALUE})
@Operation(
Expand Down Expand Up @@ -192,7 +193,7 @@ public ResponseEntity<String> downloadBatch(
/**
* Endpoint to upload Revocation Batch.
*/
@CertificateAuthenticationRequired
@CertificateAuthenticationRequired(requiredRoles = CertificateAuthenticationRole.RevocationUploader)
@PostMapping(value = "", consumes = {
CmsStringMessageConverter.CONTENT_TYPE_CMS_TEXT_VALUE, CmsStringMessageConverter.CONTENT_TYPE_CMS_VALUE})
@Operation(
Expand Down Expand Up @@ -269,7 +270,7 @@ public ResponseEntity<Void> uploadBatch(
/**
* Endpoint to delete Revocation Batch.
*/
@CertificateAuthenticationRequired
@CertificateAuthenticationRequired(requiredRoles = CertificateAuthenticationRole.RevocationDeleter)
@DeleteMapping(value = "", consumes = {
CmsStringMessageConverter.CONTENT_TYPE_CMS_TEXT_VALUE, CmsStringMessageConverter.CONTENT_TYPE_CMS_VALUE})
@Operation(
Expand Down Expand Up @@ -344,7 +345,7 @@ public ResponseEntity<Void> deleteBatch(
/**
* Alternative endpoint to delete revocation batches.
*/
@CertificateAuthenticationRequired
@CertificateAuthenticationRequired(requiredRoles = CertificateAuthenticationRole.RevocationDeleter)
@PostMapping(value = "/delete", consumes = {
CmsStringMessageConverter.CONTENT_TYPE_CMS_TEXT_VALUE, CmsStringMessageConverter.CONTENT_TYPE_CMS_VALUE})
public ResponseEntity<Void> deleteBatchAlternativeEndpoint(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import eu.europa.ec.dgc.gateway.config.DgcConfigProperties;
import eu.europa.ec.dgc.gateway.entity.TrustedPartyEntity;
import eu.europa.ec.dgc.gateway.exception.DgcgResponseException;
import eu.europa.ec.dgc.gateway.restapi.mapper.CertificateRoleMapper;
import eu.europa.ec.dgc.gateway.service.TrustedPartyService;
import eu.europa.ec.dgc.gateway.utils.DgcMdc;
import java.io.IOException;
Expand Down Expand Up @@ -65,6 +66,8 @@ public class CertificateAuthenticationFilter extends OncePerRequestFilter {

private final HandlerExceptionResolver handlerExceptionResolver;

private final CertificateRoleMapper certificateRoleMapper;

@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
try {
Expand Down Expand Up @@ -180,13 +183,58 @@ protected void doFilterInternal(
return;
}

if (!checkRequiredRoles(httpServletRequest, certFromDb.get())) {
log.error("Missing permissions to access this endpoint.");
handlerExceptionResolver.resolveException(
httpServletRequest, httpServletResponse, null,
new DgcgResponseException(
HttpStatus.FORBIDDEN,
"0x403",
"Client is not authorized to access the endpoint",
"", ""));

return;
}

log.info("Successful Authentication");
httpServletRequest.setAttribute(REQUEST_PROP_COUNTRY, distinguishNameMap.get("C"));
httpServletRequest.setAttribute(REQUEST_PROP_THUMBPRINT, headerCertThumbprint);

filterChain.doFilter(httpServletRequest, httpServletResponse);
}

private boolean checkRequiredRoles(HttpServletRequest request, TrustedPartyEntity entity) {
HandlerExecutionChain handlerExecutionChain;
try {
handlerExecutionChain = requestMap.getHandler(request);
} catch (Exception e) {
log.error("Failed to extract required roles from request.");
return false;
}

if (handlerExecutionChain == null) {
log.error("Failed to extract required roles from request.");
return false;
}

CertificateAuthenticationRole[] requiredRoles = ((HandlerMethod) handlerExecutionChain.getHandler())
.getMethod().getAnnotation(CertificateAuthenticationRequired.class).requiredRoles();

if (requiredRoles.length == 0) {
log.debug("Endpoint requires no special roles.");
return true;
}

for (CertificateAuthenticationRole requiredRole : requiredRoles) {
if (!entity.getCertificateRoles().contains(certificateRoleMapper.dtoToEntity(requiredRole))) {
log.error("Role {} is required to access endpoint", requiredRole.name());
return false;
}
}

return true;
}

/**
* Parses a given Distinguish Name string (e.g. "C=DE,OU=Test Unit,O=Test Company").
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,11 @@
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CertificateAuthenticationRequired {

/**
* List of {@link CertificateAuthenticationRole} which are required to access this endpoint.
* All mentioned roles must be assigned to the certificate.
*/
CertificateAuthenticationRole[] requiredRoles() default {};

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*-
* ---license-start
* EU Digital Green Certificate Gateway Service / dgc-gateway
* ---
* Copyright (C) 2021 T-Systems International GmbH and all other contributors
* ---
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ---license-end
*/

package eu.europa.ec.dgc.gateway.restapi.filter;

public enum CertificateAuthenticationRole {

RevocationListReader,
RevocationUploader,
RevocationDeleter

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*-
* ---license-start
* EU Digital Green Certificate Gateway Service / dgc-gateway
* ---
* Copyright (C) 2021 T-Systems International GmbH and all other contributors
* ---
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ---license-end
*/

package eu.europa.ec.dgc.gateway.restapi.mapper;

import eu.europa.ec.dgc.gateway.entity.TrustedPartyEntity;
import eu.europa.ec.dgc.gateway.restapi.filter.CertificateAuthenticationRole;
import org.mapstruct.Mapper;
import org.mapstruct.ValueMapping;

@Mapper(componentModel = "spring")
public interface CertificateRoleMapper {

@ValueMapping(source = "RevocationListReader", target = "REVOCATION_LIST_READER")
@ValueMapping(source = "RevocationUploader", target = "REVOCATION_UPLOADER")
@ValueMapping(source = "RevocationDeleter", target = "REVOCATION_DELETER")
TrustedPartyEntity.CertificateRoles dtoToEntity(CertificateAuthenticationRole dto);

}
1 change: 1 addition & 0 deletions src/main/resources/db/changelog.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@
<include file="db/changelog/fix-certificate-thumbprints.xml"/>
<include file="db/changelog/add-shedlock-table.xml"/>
<include file="db/changelog/add-revocation-batch-table.xml"/>
<include file="db/changelog/add-certificate-roles-table.xml"/>
</databaseChangeLog>
21 changes: 21 additions & 0 deletions src/main/resources/db/changelog/add-certificate-roles-table.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.6.xsd">


<changeSet id="add-certificate-roles-table" author="f11h">
<createTable tableName="trusted_party_roles">
<column name="trusted_party_entity_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="role" type="VARCHAR"/>
</createTable>

<addForeignKeyConstraint baseColumnNames="trusted_party_entity_id" baseTableName="trusted_party_roles"
constraintName="fk_trusted_party_roles_on_trusted_party_entity"
referencedColumnNames="id" referencedTableName="trusted_party"/>
</changeSet>
</databaseChangeLog>
Loading

0 comments on commit ab14590

Please sign in to comment.