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

Gh-3344: Change graph permissions operation in federated POC #3345

Merged
merged 2 commits into from
Dec 11, 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
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import uk.gov.gchq.gaffer.data.element.id.EntityId;
import uk.gov.gchq.gaffer.federated.simple.access.GraphAccess;
import uk.gov.gchq.gaffer.federated.simple.operation.AddGraph;
import uk.gov.gchq.gaffer.federated.simple.operation.ChangeGraphAccess;
import uk.gov.gchq.gaffer.federated.simple.operation.ChangeGraphId;
import uk.gov.gchq.gaffer.federated.simple.operation.FederatedOperationChainValidator;
import uk.gov.gchq.gaffer.federated.simple.operation.GetAllGraphIds;
Expand All @@ -44,6 +45,7 @@
import uk.gov.gchq.gaffer.federated.simple.operation.handler.get.GetAllGraphIdsHandler;
import uk.gov.gchq.gaffer.federated.simple.operation.handler.get.GetAllGraphInfoHandler;
import uk.gov.gchq.gaffer.federated.simple.operation.handler.get.GetSchemaHandler;
import uk.gov.gchq.gaffer.federated.simple.operation.handler.misc.ChangeGraphAccessHandler;
import uk.gov.gchq.gaffer.federated.simple.operation.handler.misc.ChangeGraphIdHandler;
import uk.gov.gchq.gaffer.federated.simple.operation.handler.misc.RemoveGraphHandler;
import uk.gov.gchq.gaffer.graph.GraphSerialisable;
Expand Down Expand Up @@ -132,7 +134,8 @@ public class FederatedStore extends Store {
new SimpleEntry<>(GetSchema.class, new GetSchemaHandler()),
new SimpleEntry<>(ChangeGraphId.class, new ChangeGraphIdHandler()),
new SimpleEntry<>(GetAllGraphInfo.class, new GetAllGraphInfoHandler()),
new SimpleEntry<>(RemoveGraph.class, new RemoveGraphHandler()))
new SimpleEntry<>(RemoveGraph.class, new RemoveGraphHandler()),
new SimpleEntry<>(ChangeGraphAccess.class, new ChangeGraphAccessHandler()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

/**
Expand Down Expand Up @@ -289,6 +292,22 @@ public void changeGraphId(final String graphToUpdateId, final String newGraphId)
}
}

/**
* Updates a graph access by overwriting the access for that graph
* stored in the cache.
*
* @param graphId The graph ID to update.
* @param newAccess The new graph access.
* @throws CacheOperationException If issue updating the cache.
*/
public void changeGraphAccess(final String graphId, final GraphAccess newAccess) throws CacheOperationException {
final GraphSerialisable graph = getGraph(graphId);
// Create the new pair
final Pair<GraphSerialisable, GraphAccess> graphAndAccessPair = new ImmutablePair<>(graph, newAccess);
// Add to the cache this will overwrite any existing value
graphCache.getCache().put(graphId, graphAndAccessPair);
}

/**
* Gets a merged schema based on the graphs specified.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
* Copyright 2024 Crown Copyright
*
* 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.
*/

package uk.gov.gchq.gaffer.federated.simple.operation;

import org.apache.commons.lang3.exception.CloneFailedException;

import uk.gov.gchq.gaffer.access.predicate.AccessPredicate;
import uk.gov.gchq.gaffer.operation.Operation;
import uk.gov.gchq.koryphe.Since;
import uk.gov.gchq.koryphe.Summary;

import java.util.Map;

@Since("2.4.0")
@Summary("Changes the access controls on a Graph")
public class ChangeGraphAccess implements Operation {

private String graphId;
private String owner;
private Boolean isPublic;
private AccessPredicate readPredicate;
private AccessPredicate writePredicate;
private Map<String, String> options;

// Getters
/**
* Get the graph ID of the graph that will be changed.
*
* @return the graph ID
*/
public String getGraphId() {
return graphId;
}

public String getOwner() {
return owner;
}

public Boolean isPublic() {
return isPublic;
}

public AccessPredicate getReadPredicate() {
return readPredicate;
}

public AccessPredicate getWritePredicate() {
return writePredicate;
}

// Setters
/**
* Set the graph ID of the current graph.
*
* @param graphId the graph ID
*/
public void setGraphId(final String graphId) {
this.graphId = graphId;
}

public void setOwner(final String owner) {
this.owner = owner;
}

public void setIsPublic(final Boolean isPublic) {
this.isPublic = isPublic;
}

public void setReadPredicate(final AccessPredicate readPredicate) {
this.readPredicate = readPredicate;
}

public void setWritePredicate(final AccessPredicate writePredicate) {
this.writePredicate = writePredicate;
}

@Override
public Map<String, String> getOptions() {
return options;
}

@Override
public void setOptions(final Map<String, String> options) {
this.options = options;
}

Check warning on line 99 in store-implementation/simple-federated-store/src/main/java/uk/gov/gchq/gaffer/federated/simple/operation/ChangeGraphAccess.java

View check run for this annotation

Codecov / codecov/patch

store-implementation/simple-federated-store/src/main/java/uk/gov/gchq/gaffer/federated/simple/operation/ChangeGraphAccess.java#L98-L99

Added lines #L98 - L99 were not covered by tests

@Override
public Operation shallowClone() throws CloneFailedException {
return new ChangeGraphAccess.Builder()
.graphId(graphId)
.owner(owner)
.isPublic(isPublic)
.readPredicate(readPredicate)
.writePredicate(writePredicate)
.options(options)
.build();

Check warning on line 110 in store-implementation/simple-federated-store/src/main/java/uk/gov/gchq/gaffer/federated/simple/operation/ChangeGraphAccess.java

View check run for this annotation

Codecov / codecov/patch

store-implementation/simple-federated-store/src/main/java/uk/gov/gchq/gaffer/federated/simple/operation/ChangeGraphAccess.java#L103-L110

Added lines #L103 - L110 were not covered by tests
}

public static class Builder extends Operation.BaseBuilder<ChangeGraphAccess, Builder> {
public Builder() {
super(new ChangeGraphAccess());
}

/**
* Set the current graph ID
*
* @param graphId the graph ID of the graph to alter
* @return The builder
*/
public Builder graphId(final String graphId) {
_getOp().setGraphId(graphId);
return _self();
}

/**
* Set the new owner of the Graph.
*
* @param owner The owner.
* @return The builder
*/
public Builder owner(final String owner) {
_getOp().setOwner(owner);
return _self();
}

/**
* Set if graph is public.
*
* @param isPublic Is the graph public.
* @return The builder
*/
public Builder isPublic(final Boolean isPublic) {
_getOp().setIsPublic(isPublic);
return _self();
}

/**
* Set the read predicate for the graph.
*
* @param readPredicate The read predicate.
* @return The builder.
*/
public Builder readPredicate(final AccessPredicate readPredicate) {
_getOp().setReadPredicate(readPredicate);
return _self();
}

/**
* Set the write predicate.
*
* @param writePredicate The write predicate.
* @return The builder.
*/
public Builder writePredicate(final AccessPredicate writePredicate) {
_getOp().setWritePredicate(writePredicate);
return _self();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2024 Crown Copyright
*
* 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.
*/

package uk.gov.gchq.gaffer.federated.simple.operation.handler.misc;

import uk.gov.gchq.gaffer.cache.exception.CacheOperationException;
import uk.gov.gchq.gaffer.federated.simple.FederatedStore;
import uk.gov.gchq.gaffer.federated.simple.access.GraphAccess;
import uk.gov.gchq.gaffer.federated.simple.operation.ChangeGraphAccess;
import uk.gov.gchq.gaffer.operation.OperationException;
import uk.gov.gchq.gaffer.store.Context;
import uk.gov.gchq.gaffer.store.Store;
import uk.gov.gchq.gaffer.store.operation.handler.OperationHandler;

public class ChangeGraphAccessHandler implements OperationHandler<ChangeGraphAccess> {

@Override
public Object doOperation(final ChangeGraphAccess operation, final Context context, final Store store) throws OperationException {
try {
// Check user for write access as we're modifying the graph
GraphAccess existingAccess = ((FederatedStore) store).getGraphAccess(operation.getGraphId());
if (!existingAccess.hasWriteAccess(context.getUser(), store.getProperties().getAdminAuth())) {
throw new OperationException(
"User: '" + context.getUser().getUserId() + "' does not have write permissions for Graph: " + operation.getGraphId());
}

// Create the new access object based on what was specified
GraphAccess.Builder accessBuilder = new GraphAccess.Builder()
.owner(operation.getOwner() != null ? operation.getOwner() : existingAccess.getOwner())
.isPublic(operation.isPublic() != null ? operation.isPublic() : existingAccess.isPublic())
.readAccessPredicate(operation.getReadPredicate() != null ? operation.getReadPredicate() : existingAccess.getReadAccessPredicate())
.writeAccessPredicate(operation.getWritePredicate() != null ? operation.getWritePredicate() : existingAccess.getWriteAccessPredicate());

// Update the access
((FederatedStore) store).changeGraphAccess(operation.getGraphId(), accessBuilder.build());
} catch (final CacheOperationException e) {
throw new OperationException("Error changing the Graph Access for: " + operation.getGraphId(), e);

Check warning on line 50 in store-implementation/simple-federated-store/src/main/java/uk/gov/gchq/gaffer/federated/simple/operation/handler/misc/ChangeGraphAccessHandler.java

View check run for this annotation

Codecov / codecov/patch

store-implementation/simple-federated-store/src/main/java/uk/gov/gchq/gaffer/federated/simple/operation/handler/misc/ChangeGraphAccessHandler.java#L49-L50

Added lines #L49 - L50 were not covered by tests
}

return null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Copyright 2024 Crown Copyright
*
* 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.
*/

package uk.gov.gchq.gaffer.federated.simple.operation;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import uk.gov.gchq.gaffer.access.predicate.NoAccessPredicate;
import uk.gov.gchq.gaffer.access.predicate.UnrestrictedAccessPredicate;
import uk.gov.gchq.gaffer.cache.CacheServiceLoader;
import uk.gov.gchq.gaffer.cache.exception.CacheOperationException;
import uk.gov.gchq.gaffer.federated.simple.FederatedStore;
import uk.gov.gchq.gaffer.federated.simple.access.GraphAccess;
import uk.gov.gchq.gaffer.graph.GraphConfig;
import uk.gov.gchq.gaffer.operation.OperationException;
import uk.gov.gchq.gaffer.store.Context;
import uk.gov.gchq.gaffer.store.StoreException;
import uk.gov.gchq.gaffer.store.StoreProperties;
import uk.gov.gchq.gaffer.store.schema.Schema;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;

class ChangeGraphAccessTest {

@AfterEach
void reset() {
CacheServiceLoader.shutdown();
}

@Test
void shouldChangeAccessOfGraph() throws StoreException, OperationException, CacheOperationException {
final String federatedGraphId = "federated";
final String graphId = "shouldChangeAccessOfGraph";

final FederatedStore federatedStore = new FederatedStore();
federatedStore.initialise(federatedGraphId, null, new StoreProperties());

// Add a graph with no restrictions
final AddGraph addGraph = new AddGraph.Builder()
.graphConfig(new GraphConfig(graphId))
.schema(new Schema())
.properties(new StoreProperties().getProperties())
.owner("oldOwner")
.isPublic(true)
.readPredicate(new UnrestrictedAccessPredicate())
.writePredicate(new UnrestrictedAccessPredicate())
.build();

// Change the graph access
final ChangeGraphAccess changeGraphAccess = new ChangeGraphAccess.Builder()
.graphId(graphId)
.owner("newOwner")
.isPublic(false)
.readPredicate(new NoAccessPredicate())
.writePredicate(new NoAccessPredicate())
.build();

// When
federatedStore.execute(addGraph, new Context());
federatedStore.execute(changeGraphAccess, new Context());

GraphAccess updatedAccess = federatedStore.getGraphAccess(graphId);

// Then
assertThat(updatedAccess.getOwner()).isEqualTo("newOwner");
assertThat(updatedAccess.isPublic()).isFalse();
assertThat(updatedAccess.getReadAccessPredicate()).isInstanceOf(NoAccessPredicate.class);
assertThat(updatedAccess.getWriteAccessPredicate()).isInstanceOf(NoAccessPredicate.class);
}

@Test
void shouldNotChangeAccessOfAccessControlledGraph() throws StoreException, OperationException {
final String federatedGraphId = "federated";
final String graphId = "shouldNotChangeAccessOfAccessControlledGraph";

final FederatedStore federatedStore = new FederatedStore();
federatedStore.initialise(federatedGraphId, null, new StoreProperties());

// Add a graph that no one can edit
final AddGraph addGraph = new AddGraph.Builder()
.graphConfig(new GraphConfig(graphId))
.schema(new Schema())
.properties(new StoreProperties().getProperties())
.isPublic(false)
.writePredicate(new NoAccessPredicate())
.build();

final ChangeGraphAccess changeGraphAccess = new ChangeGraphAccess.Builder()
.graphId(graphId)
.isPublic(true)
.build();

// When
federatedStore.execute(addGraph, new Context());

// Then
assertThatExceptionOfType(OperationException.class)
.isThrownBy(() -> federatedStore.execute(changeGraphAccess, new Context()))
.withMessageContaining("does not have write permissions");
}
}
Loading