Skip to content

Commit

Permalink
Add admin annotation for procedures
Browse files Browse the repository at this point in the history
  • Loading branch information
OliviaYtterbrink committed May 17, 2018
1 parent 72dadff commit 353c4a4
Show file tree
Hide file tree
Showing 11 changed files with 100 additions and 80 deletions.
Expand Up @@ -41,6 +41,7 @@ public class ProcedureSignature
private final List<FieldSignature> inputSignature;
private final List<FieldSignature> outputSignature;
private final Mode mode;
private final boolean admin;
private final String deprecated;
private final String[] allowed;
private final String description;
Expand All @@ -52,6 +53,7 @@ public ProcedureSignature(
List<FieldSignature> inputSignature,
List<FieldSignature> outputSignature,
Mode mode,
boolean admin,
String deprecated,
String[] allowed,
String description,
Expand All @@ -62,6 +64,7 @@ public ProcedureSignature(
this.inputSignature = unmodifiableList( inputSignature );
this.outputSignature = outputSignature == VOID ? outputSignature : unmodifiableList( outputSignature );
this.mode = mode;
this.admin = admin;
this.deprecated = deprecated;
this.allowed = allowed;
this.description = description;
Expand All @@ -79,6 +82,11 @@ public Mode mode()
return mode;
}

public boolean admin()
{
return admin;
}

public Optional<String> deprecated()
{
return Optional.ofNullable( deprecated );
Expand Down Expand Up @@ -167,6 +175,7 @@ public static class Builder
private String[] allowed = new String[0];
private String description;
private String warning;
private boolean admin;

public Builder( String[] namespace, String name )
{
Expand Down Expand Up @@ -217,6 +226,12 @@ public Builder allowed( String[] allowed )
return this;
}

public Builder admin( boolean admin )
{
this.admin = admin;
return this;
}

public Builder warning( String warning )
{
this.warning = warning;
Expand All @@ -225,7 +240,7 @@ public Builder warning( String warning )

public ProcedureSignature build()
{
return new ProcedureSignature( name, inputSignature, outputSignature, mode, deprecated, allowed,
return new ProcedureSignature( name, inputSignature, outputSignature, mode, admin, deprecated, allowed,
description, warning, false );
}
}
Expand Down
Expand Up @@ -22,7 +22,6 @@
import java.util.Comparator;
import java.util.stream.Stream;

import org.neo4j.graphdb.security.AuthorizationViolationException;
import org.neo4j.internal.kernel.api.procs.ProcedureSignature;
import org.neo4j.internal.kernel.api.procs.UserFunctionSignature;
import org.neo4j.internal.kernel.api.security.SecurityContext;
Expand All @@ -31,12 +30,12 @@
import org.neo4j.kernel.impl.query.QueryExecutionEngine;
import org.neo4j.kernel.internal.GraphDatabaseAPI;
import org.neo4j.logging.Log;
import org.neo4j.procedure.Admin;
import org.neo4j.procedure.Context;
import org.neo4j.procedure.Description;
import org.neo4j.procedure.Name;
import org.neo4j.procedure.Procedure;

import static org.neo4j.graphdb.security.AuthorizationViolationException.PERMISSION_DENIED;
import static org.neo4j.procedure.Mode.DBMS;

@SuppressWarnings( "unused" )
Expand All @@ -51,15 +50,11 @@ public class BuiltInDbmsProcedures
@Context
public SecurityContext securityContext;

@Admin
@Description( "List the currently active config of Neo4j." )
@Procedure( name = "dbms.listConfig", mode = DBMS )
public Stream<ConfigResult> listConfig( @Name( value = "searchString", defaultValue = "" ) String searchString )
{
securityContext.assertCredentialsNotExpired();
if ( !securityContext.isAdmin() )
{
throw new AuthorizationViolationException( PERMISSION_DENIED );
}
Config config = graph.getDependencyResolver().resolveDependency( Config.class );
return config.getConfigValues().values().stream()
.filter( c -> !c.internal() )
Expand Down Expand Up @@ -88,16 +83,11 @@ public Stream<FunctionResult> listFunctions()
.map( FunctionResult::new );
}

@Admin
@Description( "Clears all query caches." )
@Procedure( name = "dbms.clearQueryCaches", mode = DBMS )
public Stream<StringResult> clearAllQueryCaches()
{
securityContext.assertCredentialsNotExpired();
if ( !securityContext.isAdmin() )
{
throw new AuthorizationViolationException( PERMISSION_DENIED );
}

QueryExecutionEngine queryExecutionEngine = graph.getDependencyResolver().resolveDependency( QueryExecutionEngine.class );
long numberOfClearedQueries = queryExecutionEngine.clearQueryCaches() - 1; // this query itself does not count

Expand Down
Expand Up @@ -36,13 +36,15 @@

import org.neo4j.collection.RawIterator;
import org.neo4j.graphdb.Resource;
import org.neo4j.graphdb.security.AuthorizationViolationException;
import org.neo4j.internal.kernel.api.exceptions.KernelException;
import org.neo4j.internal.kernel.api.exceptions.ProcedureException;
import org.neo4j.internal.kernel.api.procs.FieldSignature;
import org.neo4j.internal.kernel.api.procs.ProcedureSignature;
import org.neo4j.internal.kernel.api.procs.QualifiedName;
import org.neo4j.internal.kernel.api.procs.UserAggregator;
import org.neo4j.internal.kernel.api.procs.UserFunctionSignature;
import org.neo4j.internal.kernel.api.security.SecurityContext;
import org.neo4j.io.IOUtils;
import org.neo4j.kernel.api.ResourceTracker;
import org.neo4j.kernel.api.exceptions.ComponentInjectionException;
Expand All @@ -57,6 +59,7 @@
import org.neo4j.kernel.api.proc.FailedLoadProcedure;
import org.neo4j.kernel.impl.proc.OutputMappers.OutputMapper;
import org.neo4j.logging.Log;
import org.neo4j.procedure.Admin;
import org.neo4j.procedure.Description;
import org.neo4j.procedure.Mode;
import org.neo4j.procedure.PerformsWrites;
Expand All @@ -71,6 +74,7 @@
import static java.util.Collections.emptyIterator;
import static java.util.Collections.emptyList;
import static org.neo4j.graphdb.factory.GraphDatabaseSettings.procedure_unrestricted;
import static org.neo4j.graphdb.security.AuthorizationViolationException.PERMISSION_DENIED;
import static org.neo4j.helpers.collection.Iterators.asRawIterator;

/**
Expand Down Expand Up @@ -271,6 +275,7 @@ private CallableProcedure compileProcedure( Class<?> procDefinition, MethodHandl
String description = description( method );
Procedure procedure = method.getAnnotation( Procedure.class );
Mode mode = procedure.mode();
boolean admin = method.isAnnotationPresent( Admin.class );
if ( method.isAnnotationPresent( PerformsWrites.class ) )
{
if ( procedure.mode() != org.neo4j.procedure.Mode.DEFAULT )
Expand Down Expand Up @@ -299,13 +304,13 @@ private CallableProcedure compileProcedure( Class<?> procDefinition, MethodHandl
description = describeAndLogLoadFailure( procName );
ProcedureSignature signature =
new ProcedureSignature( procName, inputSignature, outputMapper.signature(), Mode.DEFAULT,
null, new String[0], description, warning, false );
admin, null, new String[0], description, warning, false );
return new FailedLoadProcedure( signature );
}
}

ProcedureSignature signature =
new ProcedureSignature( procName, inputSignature, outputMapper.signature(), mode, deprecated,
new ProcedureSignature( procName, inputSignature, outputMapper.signature(), mode, admin, deprecated,
config.rolesFor( procName.toString() ), description, warning, false );
return new ReflectiveProcedure( signature, constructor, method, outputMapper, setters );
}
Expand Down Expand Up @@ -643,6 +648,17 @@ public RawIterator<Object[],ProcedureException> apply( Context ctx, Object[] inp
//API injection
inject( ctx, cls );

// Admin check
if ( signature.admin() )
{
SecurityContext securityContext = ctx.get( Context.SECURITY_CONTEXT );
securityContext.assertCredentialsNotExpired();
if ( !securityContext.isAdmin() )
{
throw new AuthorizationViolationException( PERMISSION_DENIED );
}
}

// Call the method
Object rs = procedureMethod.invoke( cls, input );

Expand Down
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2002-2018 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.procedure;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* This annotation marks a {@link Procedure} as only being executable by a user with admin permissions.
*/
@Target( ElementType.METHOD )
@Retention( RetentionPolicy.RUNTIME )
public @interface Admin
{
}
Expand Up @@ -25,16 +25,15 @@
import java.util.stream.Stream;

import org.neo4j.causalclustering.core.state.machines.dummy.DummyRequest;
import org.neo4j.graphdb.security.AuthorizationViolationException;
import org.neo4j.internal.kernel.api.security.SecurityContext;
import org.neo4j.logging.Log;
import org.neo4j.procedure.Admin;
import org.neo4j.procedure.Context;
import org.neo4j.procedure.Description;
import org.neo4j.procedure.Name;
import org.neo4j.procedure.Procedure;

import static java.lang.Math.toIntExact;
import static org.neo4j.graphdb.security.AuthorizationViolationException.PERMISSION_DENIED;
import static org.neo4j.procedure.Mode.DBMS;

@SuppressWarnings( "unused" )
Expand All @@ -52,12 +51,11 @@ public class ReplicationBenchmarkProcedure
private static long startTime;
private static List<Worker> workers;

@Admin
@Description( "Start the benchmark." )
@Procedure( name = "dbms.cluster.benchmark.start", mode = DBMS )
public synchronized void start( @Name( "nThreads" ) Long nThreads, @Name( "blockSize" ) Long blockSize )
{
checkSecurity();

if ( workers != null )
{
throw new IllegalStateException( "Already running." );
Expand All @@ -76,12 +74,11 @@ public synchronized void start( @Name( "nThreads" ) Long nThreads, @Name( "block
}
}

@Admin
@Description( "Stop a running benchmark." )
@Procedure( name = "dbms.cluster.benchmark.stop", mode = DBMS )
public synchronized Stream<BenchmarkResult> stop() throws InterruptedException
{
checkSecurity();

if ( workers == null )
{
throw new IllegalStateException( "Not running." );
Expand Down Expand Up @@ -115,15 +112,6 @@ public synchronized Stream<BenchmarkResult> stop() throws InterruptedException
return Stream.of( new BenchmarkResult( totalRequests, totalBytes, runTime ) );
}

private void checkSecurity() throws AuthorizationViolationException
{
securityContext.assertCredentialsNotExpired();
if ( !securityContext.isAdmin() )
{
throw new AuthorizationViolationException( PERMISSION_DENIED );
}
}

private class Worker implements Runnable
{
private final int blockSize;
Expand Down
Expand Up @@ -491,7 +491,7 @@ class CloseTransactionTest extends CypherFunSuite with GraphIcing {
val procedureName = new QualifiedName(Array[String]("org", "neo4j", "bench"), "getAllNodes")
val emptySignature: util.List[FieldSignature] = List.empty[FieldSignature].asJava
val signature: ProcedureSignature = new ProcedureSignature(
procedureName, paramSignature, resultSignature, Mode.READ, null, Array.empty,
procedureName, paramSignature, resultSignature, Mode.READ, false, null, Array.empty,
null, null, false)

def paramSignature: util.List[FieldSignature] = List.empty[FieldSignature].asJava
Expand Down
Expand Up @@ -71,7 +71,7 @@ class ExecutionEngineIT extends CypherFunSuite with GraphIcing {
val procedureName = new QualifiedName(Array[String]("org", "neo4j", "bench"), "getAllNodes")
val emptySignature: util.List[FieldSignature] = List.empty[FieldSignature].asJava
val signature: ProcedureSignature = new ProcedureSignature(
procedureName, paramSignature, resultSignature, Mode.READ, null, Array.empty,
procedureName, paramSignature, resultSignature, Mode.READ, false, null, Array.empty,
null, null, false)

def paramSignature: util.List[FieldSignature] = List.empty[FieldSignature].asJava
Expand Down

0 comments on commit 353c4a4

Please sign in to comment.