Skip to content

Commit

Permalink
Update component injection validation rules
Browse files Browse the repository at this point in the history
@​Context field types can now be categorized in three ways:

 1. supported: it is legal for any procedure|function. It can be:
    - Log
    - TerminationGuard
    - GraphDatabaseService
    - SecurityContext
    - ProcedureTransaction
 2. restricted: it can be used if and only if the procedure
 |function is declared in `dbms.security.procedures.unrestricted`
 setting. The type can be:
    - GraphDatabaseAPI
    - KernelTransaction
    - DependencyResolver
    - UserManager
    - EnterpriseAuthManager
    - SecurityLog
 3. unsupported: it is illegal (any other type, basically).

The processor cannot know whether a procedure|function is
unrestricted at the time of compilation and will, by default, emit
a warning if such a type is encountered. The existing processor
option `-AIgnoreContextWarnings` can be used to mute this.

From now on, if a type is unsupported, a compilation error will be
raised.
  • Loading branch information
fbiville committed Jun 23, 2017
1 parent 8717822 commit becf0cb
Show file tree
Hide file tree
Showing 18 changed files with 541 additions and 94 deletions.
1 change: 0 additions & 1 deletion community/procedure-compiler/processor/pom.xml
Expand Up @@ -8,7 +8,6 @@
<relativePath>../../..</relativePath>
</parent>

<groupId>org.neo4j</groupId>
<artifactId>procedure-compiler</artifactId>
<version>3.2.2-SNAPSHOT</version>
<packaging>jar</packaging>
Expand Down
Expand Up @@ -20,14 +20,9 @@
package org.neo4j.tooling.procedure;

import com.google.auto.service.AutoService;
import org.neo4j.tooling.procedure.messages.CompilationMessage;
import org.neo4j.tooling.procedure.messages.MessagePrinter;
import org.neo4j.tooling.procedure.validators.DuplicatedProcedureValidator;
import org.neo4j.tooling.procedure.visitors.StoredProcedureVisitor;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;
Expand All @@ -45,13 +40,17 @@
import javax.lang.model.util.Types;

import org.neo4j.procedure.Procedure;
import org.neo4j.tooling.procedure.messages.CompilationMessage;
import org.neo4j.tooling.procedure.messages.MessagePrinter;
import org.neo4j.tooling.procedure.validators.DuplicatedProcedureValidator;
import org.neo4j.tooling.procedure.visitors.StoredProcedureVisitor;

@AutoService( Processor.class )
public class ProcedureProcessor extends AbstractProcessor
{

public static final String IGNORE_CONTEXT_WARNINGS_OPTION = "IgnoreContextWarnings";
private static final Class<Procedure> sprocType = Procedure.class;
private static final String IGNORE_CONTEXT_WARNINGS = "IgnoreContextWarnings";
private final Set<Element> visitedProcedures = new LinkedHashSet<>();

private Function<Collection<Element>,Stream<CompilationMessage>> duplicationPredicate;
Expand All @@ -76,15 +75,13 @@ public static Optional<String> getCustomName( Procedure proc )
@Override
public Set<String> getSupportedOptions()
{
return Collections.singleton( IGNORE_CONTEXT_WARNINGS );
return Collections.singleton( IGNORE_CONTEXT_WARNINGS_OPTION );
}

@Override
public Set<String> getSupportedAnnotationTypes()
{
Set<String> types = new HashSet<>();
types.add( sprocType.getName() );
return types;
return Collections.singleton( sprocType.getName() );
}

@Override
Expand All @@ -102,8 +99,8 @@ public synchronized void init( ProcessingEnvironment processingEnv )

visitedProcedures.clear();
messagePrinter = new MessagePrinter( processingEnv.getMessager() );
visitor = new StoredProcedureVisitor( typeUtils, elementUtils, processingEnv.getOptions().containsKey(
IGNORE_CONTEXT_WARNINGS) );
visitor = new StoredProcedureVisitor( typeUtils, elementUtils,
processingEnv.getOptions().containsKey( IGNORE_CONTEXT_WARNINGS_OPTION ) );
duplicationPredicate =
new DuplicatedProcedureValidator<>( elementUtils, sprocType, ProcedureProcessor::getCustomName );
}
Expand Down
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2002-2017 "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.tooling.procedure.messages;

import javax.lang.model.element.Element;
import javax.lang.model.element.VariableElement;
import javax.tools.Diagnostic;

public class ContextFieldError implements CompilationMessage
{
private final Element element;
private final String contents;

public ContextFieldError( VariableElement element, String message, Object... args )
{
this.element = element;
this.contents = String.format( message, args );
}

@Override
public Element getElement()
{
return element;
}

@Override
public String getContents()
{
return contents;
}
}
Expand Up @@ -21,42 +21,54 @@

import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.SimpleElementVisitor8;
import javax.lang.model.util.Types;

import org.neo4j.graphdb.DependencyResolver;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.kernel.api.KernelTransaction;
import org.neo4j.kernel.api.security.SecurityContext;
import org.neo4j.kernel.api.security.UserManager;
import org.neo4j.kernel.internal.GraphDatabaseAPI;
import org.neo4j.logging.Log;
import org.neo4j.procedure.Context;
import org.neo4j.procedure.ProcedureTransaction;
import org.neo4j.procedure.TerminationGuard;
import org.neo4j.tooling.procedure.ProcedureProcessor;
import org.neo4j.tooling.procedure.messages.CompilationMessage;
import org.neo4j.tooling.procedure.messages.ContextFieldError;
import org.neo4j.tooling.procedure.messages.ContextFieldWarning;
import org.neo4j.tooling.procedure.messages.FieldError;

class ContextFieldVisitor extends SimpleElementVisitor8<Stream<CompilationMessage>,Void>
{
private static final Set<Class<?>> SUPPORTED_TYPES =
new LinkedHashSet<>( Arrays.asList( GraphDatabaseService.class, Log.class ) );
private static final Set<String> SUPPORTED_TYPES = new LinkedHashSet<>(
Arrays.asList( GraphDatabaseService.class.getName(), Log.class.getName(), TerminationGuard.class.getName(),
SecurityContext.class.getName(), ProcedureTransaction.class.getName() ) );
private static final Set<String> RESTRICTED_TYPES = new LinkedHashSet<>(
Arrays.asList( GraphDatabaseAPI.class.getName(), KernelTransaction.class.getName(),
DependencyResolver.class.getName(), UserManager.class.getName(),
// the following classes are not in the compiler classpath
"org.neo4j.kernel.enterprise.api.security.EnterpriseAuthManager",
"org.neo4j.server.security.enterprise.log.SecurityLog" ) );

private final Elements elements;
private final Types types;
private final boolean skipContextWarnings;
private final boolean ignoresWarnings;

ContextFieldVisitor( Types types, Elements elements, boolean skipContextWarnings )
ContextFieldVisitor( Types types, Elements elements, boolean ignoresWarnings )
{
this.elements = elements;
this.types = types;
this.skipContextWarnings = skipContextWarnings;
}

private static String types( Set<Class<?>> supportedTypes )
{
return supportedTypes.stream().map( Class::getName ).collect( Collectors.joining( ">, <", "<", ">" ) );
this.ignoresWarnings = ignoresWarnings;
}

@Override
Expand All @@ -69,36 +81,55 @@ private Stream<CompilationMessage> validateModifiers( VariableElement field )
{
if ( !hasValidModifiers( field ) )
{
return Stream.of( new FieldError( field,
"@%s usage error: field %s#%s should be public, non-static and non-final", Context.class.getName(),
field.getEnclosingElement().getSimpleName(), field.getSimpleName() ) );
return Stream.of( new ContextFieldError( field,
"@%s usage error: field %s should be public, non-static and non-final", Context.class.getName(),
fieldFullName( field ) ) );
}

return Stream.empty();
}

private Stream<CompilationMessage> validateInjectedTypes( VariableElement field )
{
if ( skipContextWarnings )
TypeMirror fieldType = field.asType();
if ( injectsAllowedType( fieldType ) )
{
return Stream.empty();
}

TypeMirror fieldType = field.asType();
if ( !injectsAllowedTypes( fieldType ) )
if ( injectsRestrictedType( fieldType ) )
{
return Stream
.of( new ContextFieldWarning( field, "@%s usage warning: found type: <%s>, expected one of: %s",
Context.class.getName(), fieldType.toString(), types( SUPPORTED_TYPES ) ) );
if ( ignoresWarnings )
{
return Stream.empty();
}

return Stream.of( new ContextFieldWarning( field, "@%s usage warning: found unsupported restricted type <%s> on %s.\n" +
"The procedure will not load unless declared via the configuration option 'dbms.security.procedures.unrestricted'.\n" +
"You can ignore this warning by passing the option -A%s to the Java compiler",
Context.class.getName(), fieldType.toString(), fieldFullName( field ),
ProcedureProcessor.IGNORE_CONTEXT_WARNINGS_OPTION ) );
}

return Stream.empty();
return Stream.of( new ContextFieldError( field,
"@%s usage error: found unknown type <%s> on field %s, expected one of: %s",
Context.class.getName(), fieldType.toString(), fieldFullName( field ),
joinTypes( SUPPORTED_TYPES ) ) );
}

private boolean injectsAllowedType( TypeMirror fieldType )
{
return matches( fieldType, SUPPORTED_TYPES );
}

private boolean injectsAllowedTypes( TypeMirror fieldType )
private boolean injectsRestrictedType( TypeMirror fieldType )
{
return supportedTypeMirrors( SUPPORTED_TYPES ).filter( t -> types.isSameType( t, fieldType ) ).findAny()
.isPresent();
return matches( fieldType, RESTRICTED_TYPES );
}

private boolean matches( TypeMirror fieldType, Set<String> typeNames )
{
return typeMirrors( typeNames ).anyMatch( t -> types.isSameType( t, fieldType ) );
}

private boolean hasValidModifiers( VariableElement field )
Expand All @@ -108,8 +139,18 @@ private boolean hasValidModifiers( VariableElement field )
!modifiers.contains( Modifier.FINAL );
}

private Stream<TypeMirror> supportedTypeMirrors( Set<Class<?>> supportedTypes )
private Stream<TypeMirror> typeMirrors( Set<String> typeNames )
{
return typeNames.stream().map( elements::getTypeElement ).filter( Objects::nonNull ).map( Element::asType );
}

private String fieldFullName( VariableElement field )
{
return String.format( "%s#%s", field.getEnclosingElement().getSimpleName(), field.getSimpleName() );
}

private static String joinTypes( Set<String> types )
{
return supportedTypes.stream().map( c -> elements.getTypeElement( c.getName() ).asType() );
return types.stream().collect( Collectors.joining( ">, <", "<", ">" ) );
}
}
Expand Up @@ -19,9 +19,6 @@
*/
package org.neo4j.tooling.procedure.visitors;

import org.neo4j.tooling.procedure.messages.CompilationMessage;
import org.neo4j.tooling.procedure.messages.FieldError;

import java.util.Set;
import java.util.stream.Stream;
import javax.lang.model.element.ElementVisitor;
Expand All @@ -32,15 +29,17 @@
import javax.lang.model.util.Types;

import org.neo4j.procedure.Context;
import org.neo4j.tooling.procedure.messages.CompilationMessage;
import org.neo4j.tooling.procedure.messages.FieldError;

public class FieldVisitor extends SimpleElementVisitor8<Stream<CompilationMessage>,Void>
{

private final ElementVisitor<Stream<CompilationMessage>,Void> contextFieldVisitor;

public FieldVisitor( Types types, Elements elements, boolean skipContextWarnings )
public FieldVisitor( Types types, Elements elements, boolean ignoresWarnings )
{
contextFieldVisitor = new ContextFieldVisitor( types, elements, skipContextWarnings );
contextFieldVisitor = new ContextFieldVisitor( types, elements, ignoresWarnings );
}

private static Stream<CompilationMessage> validateNonContextField( VariableElement field )
Expand Down
Expand Up @@ -19,9 +19,6 @@
*/
package org.neo4j.tooling.procedure.visitors;

import org.neo4j.tooling.procedure.messages.CompilationMessage;
import org.neo4j.tooling.procedure.messages.ProcedureMissingPublicNoArgConstructor;

import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
Expand All @@ -34,6 +31,9 @@
import javax.lang.model.util.SimpleElementVisitor8;
import javax.lang.model.util.Types;

import org.neo4j.tooling.procedure.messages.CompilationMessage;
import org.neo4j.tooling.procedure.messages.ProcedureMissingPublicNoArgConstructor;

import static javax.lang.model.util.ElementFilter.constructorsIn;

public class StoredProcedureClassVisitor extends SimpleElementVisitor8<Stream<CompilationMessage>,Void>
Expand All @@ -42,9 +42,9 @@ public class StoredProcedureClassVisitor extends SimpleElementVisitor8<Stream<Co
private final Set<TypeElement> visitedElements = new HashSet<>();
private final FieldVisitor fieldVisitor;

public StoredProcedureClassVisitor( Types types, Elements elements, boolean skipContextWarnings )
public StoredProcedureClassVisitor( Types types, Elements elements, boolean ignoresWarnings )
{
fieldVisitor = new FieldVisitor( types, elements, skipContextWarnings );
fieldVisitor = new FieldVisitor( types, elements, ignoresWarnings );
}

@Override
Expand Down
Expand Up @@ -19,10 +19,6 @@
*/
package org.neo4j.tooling.procedure.visitors;

import org.neo4j.tooling.procedure.compilerutils.TypeMirrorUtils;
import org.neo4j.tooling.procedure.messages.CompilationMessage;
import org.neo4j.tooling.procedure.messages.ReturnTypeError;

import java.util.List;
import java.util.function.Function;
import java.util.stream.Stream;
Expand All @@ -38,6 +34,9 @@
import javax.lang.model.util.Types;

import org.neo4j.procedure.Name;
import org.neo4j.tooling.procedure.compilerutils.TypeMirrorUtils;
import org.neo4j.tooling.procedure.messages.CompilationMessage;
import org.neo4j.tooling.procedure.messages.ReturnTypeError;

public class StoredProcedureVisitor extends SimpleElementVisitor8<Stream<CompilationMessage>,Void>
{
Expand All @@ -48,13 +47,13 @@ public class StoredProcedureVisitor extends SimpleElementVisitor8<Stream<Compila
private final TypeVisitor<Stream<CompilationMessage>,Void> recordVisitor;
private final ElementVisitor<Stream<CompilationMessage>,Void> parameterVisitor;

public StoredProcedureVisitor( Types typeUtils, Elements elementUtils, boolean skipContextWarnings )
public StoredProcedureVisitor( Types typeUtils, Elements elementUtils, boolean ignoresWarnings )
{
TypeMirrorUtils typeMirrors = new TypeMirrorUtils( typeUtils, elementUtils );

this.typeUtils = typeUtils;
this.elementUtils = elementUtils;
this.classVisitor = new StoredProcedureClassVisitor( typeUtils, elementUtils, skipContextWarnings );
this.classVisitor = new StoredProcedureClassVisitor( typeUtils, elementUtils, ignoresWarnings );
this.recordVisitor = new RecordTypeVisitor( typeUtils, typeMirrors );
this.parameterVisitor = new ParameterVisitor( new ParameterTypeVisitor( typeUtils, typeMirrors ) );
}
Expand Down

0 comments on commit becf0cb

Please sign in to comment.