-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
ContextFieldVisitor.java
158 lines (138 loc) · 6.27 KB
/
ContextFieldVisitor.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
/*
* 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.visitors;
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 static org.neo4j.tooling.procedure.CompilerOptions.IGNORE_CONTEXT_WARNINGS_OPTION;
class ContextFieldVisitor extends SimpleElementVisitor8<Stream<CompilationMessage>,Void>
{
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 ignoresWarnings;
ContextFieldVisitor( Types types, Elements elements, boolean ignoresWarnings )
{
this.elements = elements;
this.types = types;
this.ignoresWarnings = ignoresWarnings;
}
@Override
public Stream<CompilationMessage> visitVariable( VariableElement field, Void ignored )
{
return Stream.concat( validateModifiers( field ), validateInjectedTypes( field ) );
}
private Stream<CompilationMessage> validateModifiers( VariableElement field )
{
if ( !hasValidModifiers( field ) )
{
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 )
{
TypeMirror fieldType = field.asType();
if ( injectsAllowedType( fieldType ) )
{
return Stream.empty();
}
if ( injectsRestrictedType( fieldType ) )
{
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 ),
IGNORE_CONTEXT_WARNINGS_OPTION ) );
}
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 injectsRestrictedType( TypeMirror fieldType )
{
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 )
{
Set<Modifier> modifiers = field.getModifiers();
return modifiers.contains( Modifier.PUBLIC ) && !modifiers.contains( Modifier.STATIC ) &&
!modifiers.contains( Modifier.FINAL );
}
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 types.stream().collect( Collectors.joining( ">, <", "<", ">" ) );
}
}