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(graphql): granular permissions #1022

Merged
merged 8 commits into from
Aug 4, 2022
44 changes: 44 additions & 0 deletions src/main/java/io/cryostat/net/security/PermissionedAction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright The Cryostat Authors
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or data
* (collectively the "Software"), free of charge and under any and all copyright
* rights in the Software, and any and all patent rights owned or freely
* licensable by each licensor hereunder covering either (i) the unmodified
* Software as contributed to or provided by such licensor, or (ii) the Larger
* Works (as defined below), to deal in both
*
* (a) the Software, and
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software (each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
* The above copyright notice and either this complete permission notice or at
* a minimum a reference to the UPL must be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package io.cryostat.net.security;

import java.util.Set;

public interface PermissionedAction {
Set<ResourceAction> resourceActions();
}
8 changes: 2 additions & 6 deletions src/main/java/io/cryostat/net/web/http/RequestHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,14 @@
*/
package io.cryostat.net.web.http;

import java.util.Set;

import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.security.PermissionedAction;
import io.cryostat.net.web.http.api.ApiVersion;

import io.vertx.core.Handler;
import io.vertx.core.http.HttpMethod;
import io.vertx.ext.web.RoutingContext;

public interface RequestHandler extends Handler<RoutingContext> {
public interface RequestHandler extends Handler<RoutingContext>, PermissionedAction {
/** Lower number == higher priority handler */
static final int DEFAULT_PRIORITY = 100;

Expand All @@ -71,8 +69,6 @@ default String basePath() {

HttpMethod httpMethod();

Set<ResourceAction> resourceActions();

default boolean isAvailable() {
return true;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright The Cryostat Authors
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or data
* (collectively the "Software"), free of charge and under any and all copyright
* rights in the Software, and any and all patent rights owned or freely
* licensable by each licensor hereunder covering either (i) the unmodified
* Software as contributed to or provided by such licensor, or (ii) the Larger
* Works (as defined below), to deal in both
*
* (a) the Software, and
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software (each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
* The above copyright notice and either this complete permission notice or at
* a minimum a reference to the UPL must be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package io.cryostat.net.web.http.api.v2.graph;

import io.cryostat.net.AuthManager;
import io.cryostat.net.AuthorizationErrorException;
import io.cryostat.net.security.PermissionedAction;

import graphql.GraphQLContext;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import io.vertx.core.http.HttpHeaders;
import io.vertx.ext.web.RoutingContext;

abstract class AbstractPermissionedDataFetcher<T> implements DataFetcher<T>, PermissionedAction {

protected final AuthManager auth;

AbstractPermissionedDataFetcher(AuthManager auth) {
this.auth = auth;
}

@Override
public final T get(DataFetchingEnvironment environment) throws Exception {
GraphQLContext graphCtx = environment.getGraphQlContext();
RoutingContext ctx = graphCtx.get(RoutingContext.class);
boolean authenticated =
auth.validateHttpHeader(
() -> ctx.request().getHeader(HttpHeaders.AUTHORIZATION),
resourceActions())
.get();
if (!authenticated) {
throw new AuthorizationErrorException("Unauthorized");
}
return getAuthenticated(environment);
}

abstract T getAuthenticated(DataFetchingEnvironment environment) throws Exception;
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,24 +38,36 @@
package io.cryostat.net.web.http.api.v2.graph;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import javax.inject.Inject;

import io.cryostat.net.AuthManager;
import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.api.v2.graph.RecordingsFetcher.Recordings;
import io.cryostat.net.web.http.api.v2.graph.labels.LabelSelectorMatcher;

import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;

class ActiveRecordingsFetcher implements DataFetcher<List<GraphRecordingDescriptor>> {
class ActiveRecordingsFetcher
extends AbstractPermissionedDataFetcher<List<GraphRecordingDescriptor>> {

@Inject
ActiveRecordingsFetcher() {}
ActiveRecordingsFetcher(AuthManager auth) {
super(auth);
}

@Override
public Set<ResourceAction> resourceActions() {
return EnumSet.of(ResourceAction.READ_RECORDING, ResourceAction.READ_TARGET);
}

public List<GraphRecordingDescriptor> get(DataFetchingEnvironment environment)
@Override
public List<GraphRecordingDescriptor> getAuthenticated(DataFetchingEnvironment environment)
throws Exception {
Recordings source = environment.getSource();
List<GraphRecordingDescriptor> result = new ArrayList<>(source.active);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,31 +37,48 @@
*/
package io.cryostat.net.web.http.api.v2.graph;

import java.util.EnumSet;
import java.util.Set;

import javax.inject.Inject;

import io.cryostat.configuration.CredentialsManager;
import io.cryostat.net.AuthManager;
import io.cryostat.net.ConnectionDescriptor;
import io.cryostat.net.security.ResourceAction;
import io.cryostat.platform.ServiceRef;
import io.cryostat.recordings.RecordingArchiveHelper;
import io.cryostat.rules.ArchivedRecordingInfo;

import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;

class ArchiveRecordingMutator implements DataFetcher<ArchivedRecordingInfo> {
class ArchiveRecordingMutator extends AbstractPermissionedDataFetcher<ArchivedRecordingInfo> {

private final RecordingArchiveHelper recordingArchiveHelper;
private final CredentialsManager credentialsManager;

@Inject
ArchiveRecordingMutator(
RecordingArchiveHelper recordingArchiveHelper, CredentialsManager credentialsManager) {
AuthManager auth,
RecordingArchiveHelper recordingArchiveHelper,
CredentialsManager credentialsManager) {
super(auth);
this.recordingArchiveHelper = recordingArchiveHelper;
this.credentialsManager = credentialsManager;
}

@Override
public ArchivedRecordingInfo get(DataFetchingEnvironment environment) throws Exception {
public Set<ResourceAction> resourceActions() {
return EnumSet.of(
ResourceAction.READ_TARGET,
ResourceAction.CREATE_RECORDING,
ResourceAction.READ_RECORDING,
ResourceAction.READ_CREDENTIALS);
}

@Override
public ArchivedRecordingInfo getAuthenticated(DataFetchingEnvironment environment)
throws Exception {
GraphRecordingDescriptor source = environment.getSource();
ServiceRef target = source.target;
String uri = target.getServiceUri().toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,32 +38,42 @@
package io.cryostat.net.web.http.api.v2.graph;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import javax.inject.Inject;

import io.cryostat.net.AuthManager;
import io.cryostat.net.security.ResourceAction;
import io.cryostat.net.web.http.api.v2.graph.ArchivedRecordingsFetcher.Archived;
import io.cryostat.net.web.http.api.v2.graph.RecordingsFetcher.Recordings;
import io.cryostat.net.web.http.api.v2.graph.labels.LabelSelectorMatcher;
import io.cryostat.rules.ArchivedRecordingInfo;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;

@SuppressFBWarnings(
value = "URF_UNREAD_FIELD",
justification =
"The Archived and AggregateInfo fields are serialized and returned to the client by"
+ " the GraphQL engine")
class ArchivedRecordingsFetcher implements DataFetcher<Archived> {
class ArchivedRecordingsFetcher extends AbstractPermissionedDataFetcher<Archived> {

@Inject
ArchivedRecordingsFetcher() {}
ArchivedRecordingsFetcher(AuthManager auth) {
super(auth);
}

@Override
public Set<ResourceAction> resourceActions() {
return EnumSet.of(ResourceAction.READ_RECORDING);
}

public Archived get(DataFetchingEnvironment environment) throws Exception {
public Archived getAuthenticated(DataFetchingEnvironment environment) throws Exception {
Recordings source = environment.getSource();
FilterInput filter = FilterInput.from(environment);
List<ArchivedRecordingInfo> recordings = new ArrayList<>(source.archived);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,30 +37,48 @@
*/
package io.cryostat.net.web.http.api.v2.graph;

import java.util.EnumSet;
import java.util.Set;

import javax.inject.Inject;

import io.cryostat.configuration.CredentialsManager;
import io.cryostat.net.AuthManager;
import io.cryostat.net.ConnectionDescriptor;
import io.cryostat.net.security.ResourceAction;
import io.cryostat.platform.ServiceRef;
import io.cryostat.recordings.RecordingTargetHelper;

import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;

class DeleteActiveRecordingMutator implements DataFetcher<GraphRecordingDescriptor> {
class DeleteActiveRecordingMutator
extends AbstractPermissionedDataFetcher<GraphRecordingDescriptor> {

private final RecordingTargetHelper recordingTargetHelper;
private final CredentialsManager credentialsManager;

@Inject
DeleteActiveRecordingMutator(
RecordingTargetHelper recordingTargetHelper, CredentialsManager credentialsManager) {
AuthManager auth,
RecordingTargetHelper recordingTargetHelper,
CredentialsManager credentialsManager) {
super(auth);
this.recordingTargetHelper = recordingTargetHelper;
this.credentialsManager = credentialsManager;
}

@Override
public GraphRecordingDescriptor get(DataFetchingEnvironment environment) throws Exception {
public Set<ResourceAction> resourceActions() {
return EnumSet.of(
ResourceAction.DELETE_RECORDING,
ResourceAction.READ_TARGET,
ResourceAction.UPDATE_TARGET,
ResourceAction.DELETE_CREDENTIALS);
}

@Override
public GraphRecordingDescriptor getAuthenticated(DataFetchingEnvironment environment)
throws Exception {
GraphRecordingDescriptor source = environment.getSource();
ServiceRef target = source.target;
String uri = target.getServiceUri().toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,38 @@
*/
package io.cryostat.net.web.http.api.v2.graph;

import java.util.EnumSet;
import java.util.Set;

import javax.inject.Inject;

import io.cryostat.net.AuthManager;
import io.cryostat.net.security.ResourceAction;
import io.cryostat.recordings.RecordingArchiveHelper;
import io.cryostat.rules.ArchivedRecordingInfo;

import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;

class DeleteArchivedRecordingMutator implements DataFetcher<ArchivedRecordingInfo> {
class DeleteArchivedRecordingMutator
extends AbstractPermissionedDataFetcher<ArchivedRecordingInfo> {

private final RecordingArchiveHelper recordingArchiveHelper;

@Inject
DeleteArchivedRecordingMutator(RecordingArchiveHelper recordingArchiveHelper) {
DeleteArchivedRecordingMutator(
AuthManager auth, RecordingArchiveHelper recordingArchiveHelper) {
super(auth);
this.recordingArchiveHelper = recordingArchiveHelper;
}

@Override
public ArchivedRecordingInfo get(DataFetchingEnvironment environment) throws Exception {
public Set<ResourceAction> resourceActions() {
return EnumSet.of(ResourceAction.DELETE_RECORDING);
}

@Override
public ArchivedRecordingInfo getAuthenticated(DataFetchingEnvironment environment)
throws Exception {
ArchivedRecordingInfo source = environment.getSource();
return recordingArchiveHelper.deleteRecording(source.getName()).get();
}
Expand Down
Loading