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

Feat/entity permisions graphql #187

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a54720f
Extend graphql schema to show relevant entity roles
assadriaz Sep 25, 2024
6df41a0
Add test coverage in TiamatAuthorizationServiceTest
assadriaz Sep 26, 2024
2db1415
Refactor PublicationDeliveryImporter makes AuthorizationService confi…
assadriaz Oct 8, 2024
bc9c1b2
Refactoring add test
assadriaz Oct 8, 2024
a6ccd0d
Update Test to cover more case of role assignment
assadriaz Oct 18, 2024
f2598f8
Update pom testcontainers dependency
assadriaz Oct 18, 2024
932a0a9
Update Test to cover more case of role assignment
assadriaz Oct 18, 2024
0ff27b4
Add Test to cover usecase user with multiple roles
assadriaz Oct 18, 2024
24ac5d3
update javadocs
assadriaz Oct 28, 2024
673643c
adds entity permission in group of stop places in graphql query
assadriaz Oct 28, 2024
66f3b2f
Refactoring updated DefaultAuthorizationService, to show correct perm…
assadriaz Oct 29, 2024
ddab310
Refactoring updated DefaultAuthorizationService, to show correct perm…
assadriaz Oct 29, 2024
7dcd562
Refactoring updated DefaultAuthorizationService, to show correct perm…
assadriaz Nov 5, 2024
505d6c5
Add entityFilter on allowStops,bannedStops and submodes.
assadriaz Nov 18, 2024
d9515b3
change port in docker compose.yaml
assadriaz Nov 18, 2024
759d27a
use camel case in GraphQLNames
assadriaz Nov 18, 2024
ea9d3c5
use camel case in GraphQLNames
assadriaz Nov 18, 2024
9f2f4be
update filter by type, allow all type attribute
assadriaz Nov 19, 2024
288408a
Revert entityFilter changes
assadriaz Nov 19, 2024
e493a30
Revert entityFilter changes
assadriaz Nov 19, 2024
76d034b
implement filterByRole in getStopTypesOrSubmode method
assadriaz Nov 22, 2024
d7b0443
fix bugs
assadriaz Nov 26, 2024
d4a4c7c
update groupOfStops in DefaultAuthorizationService
assadriaz Nov 29, 2024
4ec1d7f
LocationPermissionsFetcher wip
assadriaz Dec 3, 2024
4b4685b
LocationPermissionsFetcher implementation
assadriaz Dec 3, 2024
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 @@ -58,15 +58,15 @@
<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.26</entur.helpers.version>
<entur.helpers.version>2.31</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>
<argLine/>

<xercesImpl.version>2.12.2</xercesImpl.version>
<maven.compiler.args>--add-opens java.base/java.lang=ALL-UNNAMED</maven.compiler.args>
<testcontainers.version>1.19.2</testcontainers.version>
<testcontainers.version>1.20.2</testcontainers.version>
</properties>

<dependencies>
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/org/rutebanken/tiamat/auth/AuthorizationService.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ public interface AuthorizationService {
*/
void verifyCanDeleteEntities(Collection<? extends EntityStructure> entities);

/**
* Verify that the current user has right to delete the given entity.
* @throws AccessDeniedException if not.
testower marked this conversation as resolved.
Show resolved Hide resolved
*/
boolean canDeleteEntity(EntityStructure entity);

/**
* Verify that the current user has right to edit the given entity.
* @throws AccessDeniedException if not.
*/
boolean canEditEntity(EntityStructure entity);

/**
* Return the subset of the roles that the current user holds that apply to this entity.
* */
Expand All @@ -46,6 +58,14 @@ public interface AuthorizationService {
*/
<T extends EntityStructure> boolean canEditEntity(RoleAssignment roleAssignment, T entity);

Set<String> getAllowedStopPlaceTypes(Object entity);

Set<String> getBannedStopPlaceTypes(Object entity);

Set<String> getAllowedSubmodes(Object entity);

Set<String> getBannedSubmodes(Object entity);



}
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,33 @@
import org.rutebanken.helper.organisation.DataScopedAuthorizationService;
import org.rutebanken.helper.organisation.RoleAssignment;
import org.rutebanken.helper.organisation.RoleAssignmentExtractor;
import org.rutebanken.tiamat.auth.check.TopographicPlaceChecker;
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 java.util.stream.Collectors;

import static org.rutebanken.helper.organisation.AuthorizationConstants.*;
import static org.rutebanken.helper.organisation.AuthorizationConstants.ENTITY_CLASSIFIER_ALL_ATTRIBUTES;
import static org.rutebanken.helper.organisation.AuthorizationConstants.ROLE_DELETE_STOPS;
import static org.rutebanken.helper.organisation.AuthorizationConstants.ROLE_EDIT_STOPS;

public class DefaultAuthorizationService implements AuthorizationService {
private final DataScopedAuthorizationService dataScopedAuthorizationService;
private final RoleAssignmentExtractor roleAssignmentExtractor;
private static final String STOP_PLACE_TYPE = "StopPlaceType";
private static final String SUBMODE = "Submode";
private final TopographicPlaceChecker topographicPlaceChecker;

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

@Override
public void verifyCanEditAllEntities() {
Expand Down Expand Up @@ -67,5 +77,46 @@ public <T extends EntityStructure> Set<String> getRelevantRolesForEntity(T entit
return dataScopedAuthorizationService.getRelevantRolesForEntity(entity);
}

@Override
public boolean canDeleteEntity(EntityStructure entity) {
testower marked this conversation as resolved.
Show resolved Hide resolved
return dataScopedAuthorizationService.isAuthorized(ROLE_DELETE_STOPS, List.of(entity));
}

@Override
public boolean canEditEntity(EntityStructure entity) {
testower marked this conversation as resolved.
Show resolved Hide resolved
return dataScopedAuthorizationService.isAuthorized(ROLE_EDIT_STOPS, List.of(entity));
}

@Override
public Set<String> getAllowedStopPlaceTypes(Object entity){
return getStopTypesOrSubmode(STOP_PLACE_TYPE, true, entity);
}

@Override
public Set<String> getBannedStopPlaceTypes(Object entity) {
return getStopTypesOrSubmode(STOP_PLACE_TYPE, false, entity);
}

@Override
public Set<String> getAllowedSubmodes(Object entity) {
return getStopTypesOrSubmode(SUBMODE, true, entity);
}

@Override
public Set<String> getBannedSubmodes(Object entity) {
return getStopTypesOrSubmode(SUBMODE, false, entity);
}


private Set<String> getStopTypesOrSubmode(String type, boolean isAllowed, Object entity) {
return roleAssignmentExtractor.getRoleAssignmentsForUser().stream()
.filter(roleAssignment -> roleAssignment.getEntityClassifications() != null)
.filter(roleAssignment -> topographicPlaceChecker.entityMatchesAdministrativeZone(roleAssignment, entity))
.filter(roleAssignment -> roleAssignment.getEntityClassifications().get(type) != null)
.map(roleAssignment -> roleAssignment.getEntityClassifications().get(type))
.flatMap(List::stream)
.filter(types -> isAllowed ? !types.startsWith("!") : types.startsWith("!"))
.map(types -> isAllowed ? types : types.substring(1))
.collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
import org.rutebanken.helper.organisation.DataScopedAuthorizationService;
import org.rutebanken.helper.organisation.ReflectionAuthorizationService;
import org.rutebanken.helper.organisation.RoleAssignmentExtractor;
import org.rutebanken.tiamat.auth.AuthorizationService;
import org.rutebanken.tiamat.auth.DefaultAuthorizationService;
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 @@ -39,8 +39,8 @@ public class AuthorizationServiceConfig {


@Bean
public AuthorizationService authorizationService(DataScopedAuthorizationService dataScopedAuthorizationService, RoleAssignmentExtractor roleAssignmentExtractor) {
return new DefaultAuthorizationService(dataScopedAuthorizationService, roleAssignmentExtractor);
public AuthorizationService authorizationService(DataScopedAuthorizationService dataScopedAuthorizationService, RoleAssignmentExtractor roleAssignmentExtractor, TopographicPlaceChecker topographicPlaceChecker) {
return new DefaultAuthorizationService(dataScopedAuthorizationService, roleAssignmentExtractor, topographicPlaceChecker);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.Timer;
Expand All @@ -59,6 +60,7 @@ public class PublicationDeliveryImporter {
private final TopographicPlaceImportHandler topographicPlaceImportHandler;
private final BackgroundJobs backgroundJobs;
private final AuthorizationService authorizationService;
private final boolean authorizationEnabled;

@Autowired
public PublicationDeliveryImporter(PublicationDeliveryHelper publicationDeliveryHelper, NetexMapper netexMapper,
Expand All @@ -69,7 +71,9 @@ public PublicationDeliveryImporter(PublicationDeliveryHelper publicationDelivery
GroupOfTariffZonesImportHandler groupOfTariffZonesImportHandler,
StopPlaceImportHandler stopPlaceImportHandler,
ParkingsImportHandler parkingsImportHandler,
BackgroundJobs backgroundJobs, AuthorizationService authorizationService) {
BackgroundJobs backgroundJobs,
AuthorizationService authorizationService,
@Value("${authorization.enabled:true}") boolean authorizationEnabled) {
this.publicationDeliveryHelper = publicationDeliveryHelper;
this.parkingsImportHandler = parkingsImportHandler;
this.publicationDeliveryCreator = publicationDeliveryCreator;
Expand All @@ -80,6 +84,7 @@ public PublicationDeliveryImporter(PublicationDeliveryHelper publicationDelivery
this.stopPlaceImportHandler = stopPlaceImportHandler;
this.backgroundJobs = backgroundJobs;
this.authorizationService = authorizationService;
this.authorizationEnabled = authorizationEnabled;
}


Expand All @@ -89,8 +94,9 @@ public PublicationDeliveryStructure importPublicationDelivery(PublicationDeliver

@SuppressWarnings("unchecked")
public PublicationDeliveryStructure importPublicationDelivery(PublicationDeliveryStructure incomingPublicationDelivery, ImportParams importParams) {

authorizationService.verifyCanEditAllEntities();
if(authorizationEnabled){
authorizationService.verifyCanEditAllEntities();
}

if (incomingPublicationDelivery.getDataObjects() == null) {
String responseMessage = "Received publication delivery but it does not contain any data objects.";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.rutebanken.tiamat.model.authorization;

import java.util.Collections;
import java.util.Set;

public class EntityPermissions {
private final Set<String> allowedStopPlaceTypes;
private final Set<String> bannedStopPlaceTypes;
private final Set<String> allowedSubmodes;
private final Set<String> bannedSubmodes;
private boolean canEdit;
private boolean canDelete;

public EntityPermissions(boolean canEdit, boolean canDelete, Set<String> allowedStopPlaceTypes, Set<String> bannedStopPlaceTypes, Set<String> allowedSubmodes, Set<String> bannedSubmodes) {
this.canEdit = canEdit;
this.canDelete = canDelete;
this.allowedStopPlaceTypes = allowedStopPlaceTypes == null ? Collections.emptySet() : allowedStopPlaceTypes;
this.bannedStopPlaceTypes = bannedStopPlaceTypes == null ? Collections.emptySet() : bannedStopPlaceTypes;
this.allowedSubmodes = allowedSubmodes == null ? Collections.emptySet() : allowedSubmodes;
this.bannedSubmodes = bannedSubmodes == null ? Collections.emptySet() : bannedSubmodes;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ public class GraphQLNames {
public static final String GROUP_OF_STOP_PLACES = "groupOfStopPlaces";
public static final String STOP_PLACE_GROUPS = "groups";

public static final String PERMISSIONS = "permissions";
public static final String ENTITY_PERMISSIONS= "EntityPermissions";

public static final String GROUP_OF_TARIFF_ZONES = "groupOfTariffZones";
public static final String GROUP_OF_TARIFF_ZONES_MEMBERS = "members";
public static final String OUTPUT_TYPE_GROUP_OF_TARIFF_ZONES ="GroupOfTariffZones";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
import org.rutebanken.tiamat.model.identification.IdentifiedEntity;
import org.rutebanken.tiamat.repository.TopographicPlaceRepository;
import org.rutebanken.tiamat.rest.graphql.fetchers.AuthorizationCheckDataFetcher;
import org.rutebanken.tiamat.rest.graphql.fetchers.EntityPermissionsFetcher;
import org.rutebanken.tiamat.rest.graphql.fetchers.FareZoneAuthoritiesFetcher;
import org.rutebanken.tiamat.rest.graphql.fetchers.GroupOfStopPlacesMembersFetcher;
import org.rutebanken.tiamat.rest.graphql.fetchers.GroupOfStopPlacesPurposeOfGroupingFetcher;
Expand Down Expand Up @@ -255,6 +256,9 @@ public class StopPlaceRegisterGraphQLSchema {
@Autowired
private StopPlaceTariffZoneFetcher stopPlaceTariffZoneFetcher;

@Autowired
private EntityPermissionsFetcher entityPermissionsFetcher;

@Autowired
private StopPlaceFareZoneFetcher stopPlaceFareZoneFetcher;

Expand Down Expand Up @@ -383,6 +387,35 @@ public void init() {

GraphQLObjectType validBetweenObjectType = createValidBetweenObjectType();

GraphQLObjectType entityPermissionObjectType = newObject()
.name(ENTITY_PERMISSIONS)
.field(newFieldDefinition()
.name("canEdit")
.type(GraphQLBoolean)
.build())
.field(newFieldDefinition()
.name("canDelete")
.type(GraphQLBoolean)
.build())

.field(newFieldDefinition()
.name("allowedStopPlaceTypes")
.type(new GraphQLList(GraphQLString))
.build())
.field(newFieldDefinition()
.name("bannedStopPlaceTypes")
.type(new GraphQLList(GraphQLString))
.build())
.field(newFieldDefinition()
.name("allowedSubmodes")
.type(new GraphQLList(GraphQLString))
.build())
.field(newFieldDefinition()
.name("bannedSubmodes")
.type(new GraphQLList(GraphQLString))
.build())
.build();

List<GraphQLFieldDefinition> zoneCommandFieldList = zoneCommonFieldListCreator.create(validBetweenObjectType);

commonFieldsList.addAll(zoneCommandFieldList);
Expand All @@ -397,7 +430,7 @@ public void init() {

MutableTypeResolver stopPlaceTypeResolver = new MutableTypeResolver();

List<GraphQLFieldDefinition> stopPlaceInterfaceFields = stopPlaceInterfaceCreator.createCommonInterfaceFields(tariffZoneObjectType,fareZoneObjectType, topographicPlaceObjectType, validBetweenObjectType);
List<GraphQLFieldDefinition> stopPlaceInterfaceFields = stopPlaceInterfaceCreator.createCommonInterfaceFields(tariffZoneObjectType,fareZoneObjectType, topographicPlaceObjectType, validBetweenObjectType, entityPermissionObjectType);
GraphQLInterfaceType stopPlaceInterface = stopPlaceInterfaceCreator.createInterface(stopPlaceInterfaceFields, commonFieldsList);

GraphQLObjectType stopPlaceObjectType = stopPlaceObjectTypeCreator.create(stopPlaceInterface, stopPlaceInterfaceFields, commonFieldsList, quayObjectType);
Expand Down Expand Up @@ -629,6 +662,9 @@ public GraphQLCodeRegistry buildCodeRegistry(TypeResolver stopPlaceTypeResolver)
registerDataFetcher(codeRegistryBuilder, OUTPUT_TYPE_QUAY, IMPORTED_ID, getOriginalIdsFetcher());
registerDataFetcher(codeRegistryBuilder, OUTPUT_TYPE_STOPPLACE, ID, getNetexIdFetcher());

registerDataFetcher(codeRegistryBuilder, OUTPUT_TYPE_STOPPLACE, PERMISSIONS, entityPermissionsFetcher);
registerDataFetcher(codeRegistryBuilder, OUTPUT_TYPE_PARENT_STOPPLACE, PERMISSIONS, entityPermissionsFetcher);

registerDataFetcher(codeRegistryBuilder, OUTPUT_TYPE_STOPPLACE, TARIFF_ZONES, stopPlaceTariffZoneFetcher);
registerDataFetcher(codeRegistryBuilder, OUTPUT_TYPE_PARENT_STOPPLACE, TARIFF_ZONES, stopPlaceTariffZoneFetcher);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package org.rutebanken.tiamat.rest.graphql.fetchers;

import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import org.rutebanken.tiamat.auth.AuthorizationService;
import org.rutebanken.tiamat.model.EntityInVersionStructure;
import org.rutebanken.tiamat.model.StopPlace;
import org.rutebanken.tiamat.model.authorization.EntityPermissions;
import org.rutebanken.tiamat.netex.id.TypeFromIdResolver;
import org.rutebanken.tiamat.repository.generic.GenericEntityInVersionRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Set;

@Component
public class EntityPermissionsFetcher implements DataFetcher {

private static final String STOP_PLACE_TYPE = "StopPlaceType";
private static final String SUBMODE = "Submode";
@Autowired
private GenericEntityInVersionRepository genericEntityInVersionRepository;

@Autowired
private TypeFromIdResolver typeFromIdResolver;


@Autowired
private AuthorizationService authorizationService;

@Override
public Object get(DataFetchingEnvironment environment) throws Exception {
final String netexId = ((StopPlace) environment.getSource()).getNetexId();

Class clazz = typeFromIdResolver.resolveClassFromId(netexId);
EntityInVersionStructure entityInVersionStructure = genericEntityInVersionRepository.findFirstByNetexIdOrderByVersionDesc(netexId, clazz);

if (entityInVersionStructure == null) {
throw new IllegalArgumentException("Cannot find entity with ID: " + netexId);
}

final boolean canEditEntities = authorizationService.canEditEntity(entityInVersionStructure);
final boolean canDeleteEntity = authorizationService.canDeleteEntity(entityInVersionStructure);
final Set<String> allowedStopPlaceTypes = authorizationService.getAllowedStopPlaceTypes(entityInVersionStructure);
final Set<String> bannedStopPlaceTypes = authorizationService.getBannedStopPlaceTypes(entityInVersionStructure);
final Set<String> allowedSubmode = authorizationService.getAllowedSubmodes(entityInVersionStructure);
final Set<String> bannedSubmode = authorizationService.getBannedSubmodes(entityInVersionStructure);


return new EntityPermissions(canEditEntities, canDeleteEntity, allowedStopPlaceTypes, bannedStopPlaceTypes, allowedSubmode, bannedSubmode);
}
}
Loading
Loading