Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor authorization service #180

Merged
merged 3 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<parent>
<groupId>org.entur.ror</groupId>
<artifactId>superpom</artifactId>
<version>2.10</version>
<version>2.13</version>
</parent>
<groupId>org.rutebanken</groupId>
<artifactId>tiamat</artifactId>
Expand Down Expand Up @@ -58,7 +58,7 @@
<hazelcast.version>5.2.4</hazelcast.version>
<swagger-jersey2.version>2.2.20</swagger-jersey2.version>
<netex-java-model.version>2.0.14</netex-java-model.version>
<entur.helpers.version>2.11</entur.helpers.version>
<entur.helpers.version>2.26</entur.helpers.version>
<jts-core.version>1.19.0</jts-core.version>
<groovy-all.version>4.0.17</groovy-all.version>
<rest-assured.version>5.4.0</rest-assured.version>
Expand Down
51 changes: 51 additions & 0 deletions src/main/java/org/rutebanken/tiamat/auth/AuthorizationService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.rutebanken.tiamat.auth;

import org.rutebanken.helper.organisation.RoleAssignment;
import org.rutebanken.tiamat.model.EntityStructure;
import org.springframework.security.access.AccessDeniedException;

import java.util.Collection;
import java.util.Set;

/**
* Authorize operations for the current user.
*/
public interface AuthorizationService {

/**
* Verify that the current user have right to edit any entity?
*/
void verifyCanEditAllEntities();


/**
* Does the current user have edit right on all the given entities?
*/
boolean canEditEntities(Collection<? extends EntityStructure> entities);

/**
* Verify that the current user has edit right on all the given entities.
* @throws AccessDeniedException if not.
*/
void verifyCanEditEntities(Collection<? extends EntityStructure> entities);

/**
* Verify that the current user has delete right on all the given entities.
* @throws AccessDeniedException if not.
*/
void verifyCanDeleteEntities(Collection<? extends EntityStructure> entities);

/**
* Return the subset of the roles that the current user holds that apply to this entity.
* */
<T extends EntityStructure> Set<String> getRelevantRolesForEntity(T entity);

/**
* Does the role assignment give edit right on the given entity?
* (for unit tests only)
*/
<T extends EntityStructure> boolean canEditEntity(RoleAssignment roleAssignment, T entity);



}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package org.rutebanken.tiamat.auth;

import org.apache.commons.lang3.StringUtils;
import org.rutebanken.helper.organisation.AuthorizationConstants;
import org.rutebanken.helper.organisation.DataScopedAuthorizationService;
import org.rutebanken.helper.organisation.RoleAssignment;
import org.rutebanken.helper.organisation.RoleAssignmentExtractor;
import org.rutebanken.tiamat.model.EntityStructure;
import org.springframework.security.access.AccessDeniedException;

import java.util.Collection;
import java.util.List;
import java.util.Set;

import static org.rutebanken.helper.organisation.AuthorizationConstants.*;

public class DefaultAuthorizationService implements AuthorizationService {
private final DataScopedAuthorizationService dataScopedAuthorizationService;
private final RoleAssignmentExtractor roleAssignmentExtractor;

public DefaultAuthorizationService(DataScopedAuthorizationService dataScopedAuthorizationService, RoleAssignmentExtractor roleAssignmentExtractor) {
this.dataScopedAuthorizationService = dataScopedAuthorizationService;
this.roleAssignmentExtractor = roleAssignmentExtractor;
}

@Override
public void verifyCanEditAllEntities() {
verifyCanEditAllEntities(roleAssignmentExtractor.getRoleAssignmentsForUser());
}

void verifyCanEditAllEntities(List<RoleAssignment> roleAssignments) {
if (roleAssignments
.stream()
.noneMatch(roleAssignment -> ROLE_EDIT_STOPS.equals(roleAssignment.getRole())
&& roleAssignment.getEntityClassifications() != null
&& roleAssignment.getEntityClassifications().get(AuthorizationConstants.ENTITY_TYPE) != null
&& roleAssignment.getEntityClassifications().get(AuthorizationConstants.ENTITY_TYPE).contains(ENTITY_CLASSIFIER_ALL_ATTRIBUTES)
&& StringUtils.isEmpty(roleAssignment.getAdministrativeZone())
)) {
throw new AccessDeniedException("Insufficient privileges for operation");
}
}

@Override
public boolean canEditEntities(Collection<? extends EntityStructure> entities) {
return dataScopedAuthorizationService.isAuthorized(ROLE_EDIT_STOPS, entities);
}

@Override
public <T extends EntityStructure> boolean canEditEntity(RoleAssignment roleAssignment, T entity) {
return dataScopedAuthorizationService.authorized(roleAssignment, entity, ROLE_EDIT_STOPS);
}

@Override
public void verifyCanEditEntities(Collection<? extends EntityStructure> entities) {
dataScopedAuthorizationService.assertAuthorized(ROLE_EDIT_STOPS, entities);
}

@Override
public void verifyCanDeleteEntities(Collection<? extends EntityStructure> entities) {
dataScopedAuthorizationService.assertAuthorized(ROLE_DELETE_STOPS, entities);

}

@Override
public <T extends EntityStructure> Set<String> getRelevantRolesForEntity(T entity) {
return dataScopedAuthorizationService.getRelevantRolesForEntity(entity);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

package org.rutebanken.tiamat.auth;

import org.rutebanken.helper.organisation.ReflectionAuthorizationService;
import org.rutebanken.tiamat.diff.TiamatObjectDiffer;
import org.rutebanken.tiamat.diff.generic.Difference;
import org.rutebanken.tiamat.model.StopPlace;
Expand All @@ -32,23 +31,21 @@
import java.util.Set;
import java.util.stream.Collectors;

import static org.rutebanken.helper.organisation.AuthorizationConstants.ROLE_EDIT_STOPS;

/**
* This authorization service is implemented mainly for handling multi modal stops.
* Generic authorization logic should be implemented in and handled by {@link ReflectionAuthorizationService}.
* Generic authorization logic should be implemented in and handled by {@link AuthorizationService}.
*/
@Service
public class StopPlaceAuthorizationService {

private static final Logger logger = LoggerFactory.getLogger(StopPlaceAuthorizationService.class);

private final ReflectionAuthorizationService authorizationService;
private final AuthorizationService authorizationService;

private final TiamatObjectDiffer tiamatObjectDiffer;

@Autowired
public StopPlaceAuthorizationService(ReflectionAuthorizationService authorizationService, TiamatObjectDiffer tiamatObjectDiffer) {
public StopPlaceAuthorizationService(AuthorizationService authorizationService, TiamatObjectDiffer tiamatObjectDiffer) {
this.authorizationService = authorizationService;
this.tiamatObjectDiffer = tiamatObjectDiffer;
}
Expand All @@ -65,7 +62,7 @@ public void assertAuthorizedToEdit(StopPlace existingVersion, StopPlace newVersi
* In this situation, the newVersion of the stop must only be populated with the children that are relevant to edit.
* If the newVersion of the stop place contains all current children, and the user does not have authorization to edit those stop places, authorization is not granted.
* <p>
* If the stop place is a normal mono modal stop place, the {@link ReflectionAuthorizationService} will be called directly.
* If the stop place is a normal mono modal stop place, the {@link AuthorizationService} will be called directly.
* @param existingVersion the current version of the stop place, persisted
* @param newVersion the new version of the same stop place, containing the changed state. If type is parent stop place, only child stops that the user would be authorized to change and edit, should be present.
* @param childStopsUpdated
Expand All @@ -76,7 +73,7 @@ public void assertAuthorizedToEdit(StopPlace existingVersion, StopPlace newVersi
// Only child stops that the user has access to should be provided with the new version
// If the stop place already contains children the user does not have access to, the user does not have access to terminate the stop place.

boolean accessToAllChildren = authorizationService.isAuthorized(ROLE_EDIT_STOPS, existingVersion.getChildren());
boolean accessToAllChildren = authorizationService.canEditEntities(existingVersion.getChildren());
if (!accessToAllChildren) {
// This user does not have access to all children.
// Could the user still be allowed to edit a child?
Expand All @@ -91,7 +88,7 @@ public void assertAuthorizedToEdit(StopPlace existingVersion, StopPlace newVersi
}

logger.debug("Must be authorized to edit these children: {}", mustBeAuthorizedToEditTheseChildren.stream().map(child -> child.getNetexId()).collect(Collectors.toList()));
authorizationService.assertAuthorized(ROLE_EDIT_STOPS, mustBeAuthorizedToEditTheseChildren);
authorizationService.verifyCanEditEntities( mustBeAuthorizedToEditTheseChildren);

Set<String> existingChildrenIds = existingVersion.getChildren().stream().map(s -> s.getNetexId()).collect(Collectors.toSet());

Expand All @@ -103,7 +100,7 @@ public void assertAuthorizedToEdit(StopPlace existingVersion, StopPlace newVersi
existingChildrenIds, newVersion.getNetexId(), mustBeAuthorizedToEditTheseChildren);
}
} else {
authorizationService.assertAuthorized(ROLE_EDIT_STOPS, Arrays.asList(newVersion));
authorizationService.verifyCanEditEntities( Arrays.asList(newVersion));
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@

package org.rutebanken.tiamat.config;

import org.rutebanken.helper.organisation.DataScopedAuthorizationService;
import org.rutebanken.helper.organisation.ReflectionAuthorizationService;
import org.rutebanken.helper.organisation.RoleAssignmentExtractor;
import org.rutebanken.tiamat.auth.TiamatEntityResolver;
import org.rutebanken.tiamat.auth.check.TiamatOriganisationChecker;
import org.rutebanken.tiamat.auth.check.TopographicPlaceChecker;
import org.rutebanken.tiamat.auth.AuthorizationService;
import org.rutebanken.tiamat.auth.DefaultAuthorizationService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -36,11 +39,16 @@ public class AuthorizationServiceConfig {


@Bean
public ReflectionAuthorizationService getAuthorizationService(RoleAssignmentExtractor roleAssignmentExtractor,
@Value("${authorization.enabled:true}") boolean authorizationEnabled,
TiamatOriganisationChecker tiamatOriganisationChecker,
TopographicPlaceChecker topographicPlaceChecker,
TiamatEntityResolver tiamatEntityResolver) {
public AuthorizationService authorizationService(DataScopedAuthorizationService dataScopedAuthorizationService, RoleAssignmentExtractor roleAssignmentExtractor) {
return new DefaultAuthorizationService(dataScopedAuthorizationService, roleAssignmentExtractor);
}

@Bean
public DataScopedAuthorizationService dataScopedAuthorizationService(RoleAssignmentExtractor roleAssignmentExtractor,
@Value("${authorization.enabled:true}") boolean authorizationEnabled,
TiamatOriganisationChecker tiamatOriganisationChecker,
TopographicPlaceChecker topographicPlaceChecker,
TiamatEntityResolver tiamatEntityResolver) {

// Should be made configurable
Map<String, List<String>> fieldMappings = new HashMap<>();
Expand All @@ -49,5 +57,4 @@ public ReflectionAuthorizationService getAuthorizationService(RoleAssignmentExtr

return new ReflectionAuthorizationService(roleAssignmentExtractor, authorizationEnabled, tiamatOriganisationChecker, topographicPlaceChecker, tiamatEntityResolver, fieldMappings);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

package org.rutebanken.tiamat.importer;

import org.rutebanken.helper.organisation.ReflectionAuthorizationService;
import org.rutebanken.tiamat.auth.AuthorizationService;
import org.rutebanken.tiamat.model.AddressablePlace;
import org.rutebanken.tiamat.model.AddressablePlaceRefStructure;
import org.rutebanken.tiamat.model.PathLink;
Expand All @@ -38,7 +38,6 @@
import java.util.concurrent.atomic.AtomicInteger;

import static java.util.stream.Collectors.toList;
import static org.rutebanken.helper.organisation.AuthorizationConstants.ROLE_EDIT_STOPS;

@Service
@Transactional
Expand All @@ -56,10 +55,10 @@ public class PathLinksImporter {

private final VersionIncrementor versionIncrementor;

private final ReflectionAuthorizationService authorizationService;
private final AuthorizationService authorizationService;

@Autowired
public PathLinksImporter(PathLinkRepository pathLinkRepository, ReferenceResolver referenceResolver, NetexMapper netexMapper, KeyValueListAppender keyValueListAppender, VersionIncrementor versionIncrementor, ReflectionAuthorizationService authorizationService) {
public PathLinksImporter(PathLinkRepository pathLinkRepository, ReferenceResolver referenceResolver, NetexMapper netexMapper, KeyValueListAppender keyValueListAppender, VersionIncrementor versionIncrementor, AuthorizationService authorizationService) {
this.pathLinkRepository = pathLinkRepository;
this.referenceResolver = referenceResolver;
this.netexMapper = netexMapper;
Expand All @@ -78,7 +77,7 @@ public List<org.rutebanken.netex.model.PathLink> importPathLinks(List<PathLink>
if(optionalPathLink.isPresent()) {
PathLink existing = optionalPathLink.get();
// Role edit stops. There could be a role for path links. But pathlinks are used in relation to stop place.
authorizationService.assertAuthorized(ROLE_EDIT_STOPS, Arrays.asList(existing, pathLink));
authorizationService.verifyCanEditEntities( Arrays.asList(existing, pathLink));
boolean changed = keyValueListAppender.appendToOriginalId(NetexIdMapper.ORIGINAL_ID_KEY, pathLink, existing);
if(changed) {
existing.setChanged(Instant.now());
Expand All @@ -87,7 +86,7 @@ public List<org.rutebanken.netex.model.PathLink> importPathLinks(List<PathLink>
versionIncrementor.incrementVersion(existing);
return existing;
} else {
authorizationService.assertAuthorized(ROLE_EDIT_STOPS, Arrays.asList(pathLink));
authorizationService.verifyCanEditEntities( Arrays.asList(pathLink));
logger.debug("No existing path link. Using incoming {}", pathLink);
pathLink.setCreated(Instant.now());
pathLink.setVersion(VersionIncrementor.INITIAL_VERSION);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import org.rutebanken.netex.model.PublicationDeliveryStructure;
import org.rutebanken.netex.model.SiteFrame;
import org.rutebanken.tiamat.auth.AuthorizationService;
import org.rutebanken.tiamat.exporter.PublicationDeliveryCreator;
import org.rutebanken.tiamat.importer.handler.GroupOfTariffZonesImportHandler;
import org.rutebanken.tiamat.importer.handler.ParkingsImportHandler;
Expand Down Expand Up @@ -57,6 +58,7 @@ public class PublicationDeliveryImporter {
private final ParkingsImportHandler parkingsImportHandler;
private final TopographicPlaceImportHandler topographicPlaceImportHandler;
private final BackgroundJobs backgroundJobs;
private final AuthorizationService authorizationService;

@Autowired
public PublicationDeliveryImporter(PublicationDeliveryHelper publicationDeliveryHelper, NetexMapper netexMapper,
Expand All @@ -67,7 +69,7 @@ public PublicationDeliveryImporter(PublicationDeliveryHelper publicationDelivery
GroupOfTariffZonesImportHandler groupOfTariffZonesImportHandler,
StopPlaceImportHandler stopPlaceImportHandler,
ParkingsImportHandler parkingsImportHandler,
BackgroundJobs backgroundJobs) {
BackgroundJobs backgroundJobs, AuthorizationService authorizationService) {
this.publicationDeliveryHelper = publicationDeliveryHelper;
this.parkingsImportHandler = parkingsImportHandler;
this.publicationDeliveryCreator = publicationDeliveryCreator;
Expand All @@ -77,6 +79,7 @@ public PublicationDeliveryImporter(PublicationDeliveryHelper publicationDelivery
this.groupOfTariffZonesImportHandler = groupOfTariffZonesImportHandler;
this.stopPlaceImportHandler = stopPlaceImportHandler;
this.backgroundJobs = backgroundJobs;
this.authorizationService = authorizationService;
}


Expand All @@ -87,6 +90,8 @@ public PublicationDeliveryStructure importPublicationDelivery(PublicationDeliver
@SuppressWarnings("unchecked")
public PublicationDeliveryStructure importPublicationDelivery(PublicationDeliveryStructure incomingPublicationDelivery, ImportParams importParams) {

authorizationService.verifyCanEditAllEntities();

if (incomingPublicationDelivery.getDataObjects() == null) {
String responseMessage = "Received publication delivery but it does not contain any data objects.";
logger.warn(responseMessage);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import org.rutebanken.helper.organisation.ReflectionAuthorizationService;
import org.rutebanken.tiamat.auth.AuthorizationService;
import org.rutebanken.tiamat.model.EntityInVersionStructure;
import org.rutebanken.tiamat.model.authorization.AuthorizationResponse;
import org.rutebanken.tiamat.netex.id.TypeFromIdResolver;
Expand All @@ -43,7 +43,7 @@ public class AuthorizationCheckDataFetcher implements DataFetcher {
private TypeFromIdResolver typeFromIdResolver;

@Autowired
private ReflectionAuthorizationService authorizationService;
private AuthorizationService authorizationService;

@Override
public Object get(DataFetchingEnvironment dataFetchingEnvironment) {
Expand Down
Loading