Skip to content

Commit 7c01fa7

Browse files
committed
Introduce Impersonation support
This update brings support for the new impersonation feature, which allows creating sessions with impersonated user.
1 parent d622c3e commit 7c01fa7

File tree

76 files changed

+943
-380
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+943
-380
lines changed

driver/src/main/java/org/neo4j/driver/SessionConfig.java

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,15 @@ public class SessionConfig
4141
private final AccessMode defaultAccessMode;
4242
private final String database;
4343
private final Optional<Long> fetchSize;
44+
private final String impersonatedUser;
4445

4546
private SessionConfig( Builder builder )
4647
{
4748
this.bookmarks = builder.bookmarks;
4849
this.defaultAccessMode = builder.defaultAccessMode;
4950
this.database = builder.database;
5051
this.fetchSize = builder.fetchSize;
52+
this.impersonatedUser = builder.impersonatedUser;
5153
}
5254

5355
/**
@@ -116,13 +118,19 @@ public Optional<String> database()
116118

117119
/**
118120
* This value if set, overrides the default fetch size set on {@link Config#fetchSize()}.
121+
*
119122
* @return an optional value of fetch size.
120123
*/
121124
public Optional<Long> fetchSize()
122125
{
123126
return fetchSize;
124127
}
125128

129+
public Optional<String> impersonatedUser()
130+
{
131+
return Optional.ofNullable( impersonatedUser );
132+
}
133+
126134
@Override
127135
public boolean equals( Object o )
128136
{
@@ -136,20 +144,20 @@ public boolean equals( Object o )
136144
}
137145
SessionConfig that = (SessionConfig) o;
138146
return Objects.equals( bookmarks, that.bookmarks ) && defaultAccessMode == that.defaultAccessMode && Objects.equals( database, that.database )
139-
&& Objects.equals( fetchSize, that.fetchSize );
147+
&& Objects.equals( fetchSize, that.fetchSize ) && Objects.equals( impersonatedUser, that.impersonatedUser );
140148
}
141149

142150
@Override
143151
public int hashCode()
144152
{
145-
return Objects.hash( bookmarks, defaultAccessMode, database );
153+
return Objects.hash( bookmarks, defaultAccessMode, database, impersonatedUser );
146154
}
147155

148156
@Override
149157
public String toString()
150158
{
151159
return "SessionParameters{" + "bookmarks=" + bookmarks + ", defaultAccessMode=" + defaultAccessMode + ", database='" + database + '\'' +
152-
", fetchSize=" + fetchSize + '}';
160+
", fetchSize=" + fetchSize + "impersonatedUser=" + impersonatedUser + '}';
153161
}
154162

155163
/**
@@ -161,6 +169,7 @@ public static class Builder
161169
private Iterable<Bookmark> bookmarks = null;
162170
private AccessMode defaultAccessMode = AccessMode.WRITE;
163171
private String database = null;
172+
private String impersonatedUser = null;
164173

165174
private Builder()
166175
{
@@ -268,6 +277,31 @@ public Builder withFetchSize( long size )
268277
return this;
269278
}
270279

280+
/**
281+
* Set the impersonated user that the newly created session is going to use for query execution.
282+
* <p>
283+
* The driver must have the necessary permissions to impersonate and run queries as the impersonated user.
284+
* <p>
285+
* When {@link #withDatabase(String)} is not used, the driver will discover the default database name of the impersonated user on first session usage.
286+
* From that moment, the discovered database name will be used as the default database name for the whole lifetime of the new session.
287+
* <p>
288+
* <b>Compatible with 4.4+ only.</b> You MUST have all servers running 4.4 version or above and communicating over Bolt 4.4 or above.
289+
*
290+
* @param impersonatedUser the user to impersonate. Provided value should not be {@code null}.
291+
* @return this builder
292+
*/
293+
public Builder withImpersonatedUser( String impersonatedUser )
294+
{
295+
requireNonNull( impersonatedUser, "Impersonated user should not be null." );
296+
if ( impersonatedUser.isEmpty() )
297+
{
298+
// Empty string is an illegal user. Fail fast on client.
299+
throw new IllegalArgumentException( String.format( "Illegal impersonated user '%s'.", impersonatedUser ) );
300+
}
301+
this.impersonatedUser = impersonatedUser;
302+
return this;
303+
}
304+
271305
public SessionConfig build()
272306
{
273307
return new SessionConfig( this );

driver/src/main/java/org/neo4j/driver/internal/DirectConnectionProvider.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,21 @@
1818
*/
1919
package org.neo4j.driver.internal;
2020

21+
import java.util.concurrent.CompletableFuture;
2122
import java.util.concurrent.CompletionStage;
2223

2324
import org.neo4j.driver.internal.async.ConnectionContext;
2425
import org.neo4j.driver.internal.async.connection.DirectConnection;
2526
import org.neo4j.driver.internal.spi.Connection;
2627
import org.neo4j.driver.internal.spi.ConnectionPool;
2728
import org.neo4j.driver.internal.spi.ConnectionProvider;
29+
import org.neo4j.driver.internal.util.Futures;
2830

31+
import static org.neo4j.driver.internal.async.ConnectionContext.PENDING_DATABASE_NAME_EXCEPTION_SUPPLIER;
2932
import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.supportsMultiDatabase;
3033

3134
/**
32-
* Simple {@link ConnectionProvider connection provider} that obtains connections form the given pool only for
33-
* the given address.
35+
* Simple {@link ConnectionProvider connection provider} that obtains connections form the given pool only for the given address.
3436
*/
3537
public class DirectConnectionProvider implements ConnectionProvider
3638
{
@@ -46,7 +48,12 @@ public class DirectConnectionProvider implements ConnectionProvider
4648
@Override
4749
public CompletionStage<Connection> acquireConnection( ConnectionContext context )
4850
{
49-
return acquireConnection().thenApply( connection -> new DirectConnection( connection, context.databaseName(), context.mode() ) );
51+
CompletableFuture<DatabaseName> databaseNameFuture = context.databaseNameFuture();
52+
databaseNameFuture.complete( DatabaseNameUtil.defaultDatabase() );
53+
return acquireConnection().thenApply(
54+
connection -> new DirectConnection( connection,
55+
Futures.joinNowOrElseThrow( databaseNameFuture, PENDING_DATABASE_NAME_EXCEPTION_SUPPLIER ),
56+
context.mode(), context.impersonatedUser() ) );
5057
}
5158

5259
@Override
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
package org.neo4j.driver.internal;
20+
21+
import org.neo4j.driver.exceptions.ClientException;
22+
import org.neo4j.driver.internal.messaging.v44.BoltProtocolV44;
23+
import org.neo4j.driver.internal.spi.Connection;
24+
import org.neo4j.driver.internal.util.ServerVersion;
25+
26+
public class ImpersonationUtil
27+
{
28+
public static final String IMPERSONATION_UNSUPPORTED_ERROR_MESSAGE =
29+
"Detected connection that does not support impersonation, please make sure to have all servers running 4.4 version or above and communicating" +
30+
" over Bolt version 4.4 or above when using impersonation feature";
31+
32+
public static Connection ensureImpersonationSupport( Connection connection, String impersonatedUser )
33+
{
34+
if ( impersonatedUser != null && !supportsImpersonation( connection ) )
35+
{
36+
throw new ClientException( IMPERSONATION_UNSUPPORTED_ERROR_MESSAGE );
37+
}
38+
return connection;
39+
}
40+
41+
private static boolean supportsImpersonation( Connection connection )
42+
{
43+
return connection.serverVersion().greaterThanOrEqual( ServerVersion.v4_4_0 ) &&
44+
connection.protocol().version().compareTo( BoltProtocolV44.VERSION ) >= 0;
45+
}
46+
}

driver/src/main/java/org/neo4j/driver/internal/SessionFactoryImpl.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ public NetworkSession newInstance( SessionConfig sessionConfig )
5252
{
5353
BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( InternalBookmark.from( sessionConfig.bookmarks() ) );
5454
return createSession( connectionProvider, retryLogic, parseDatabaseName( sessionConfig ),
55-
sessionConfig.defaultAccessMode(), bookmarkHolder, parseFetchSize( sessionConfig ), logging );
55+
sessionConfig.defaultAccessMode(), bookmarkHolder, parseFetchSize( sessionConfig ),
56+
sessionConfig.impersonatedUser().orElse( null ), logging );
5657
}
5758

5859
private long parseFetchSize( SessionConfig sessionConfig )
@@ -98,10 +99,10 @@ public ConnectionProvider getConnectionProvider()
9899
}
99100

100101
private NetworkSession createSession( ConnectionProvider connectionProvider, RetryLogic retryLogic, DatabaseName databaseName, AccessMode mode,
101-
BookmarkHolder bookmarkHolder, long fetchSize, Logging logging )
102+
BookmarkHolder bookmarkHolder, long fetchSize, String impersonatedUser, Logging logging )
102103
{
103104
return leakedSessionsLoggingEnabled
104-
? new LeakLoggingNetworkSession( connectionProvider, retryLogic, databaseName, mode, bookmarkHolder, fetchSize, logging )
105-
: new NetworkSession( connectionProvider, retryLogic, databaseName, mode, bookmarkHolder, fetchSize, logging );
105+
? new LeakLoggingNetworkSession( connectionProvider, retryLogic, databaseName, mode, bookmarkHolder, impersonatedUser, fetchSize, logging )
106+
: new NetworkSession( connectionProvider, retryLogic, databaseName, mode, bookmarkHolder, impersonatedUser, fetchSize, logging );
106107
}
107108
}

driver/src/main/java/org/neo4j/driver/internal/async/ConnectionContext.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
*/
1919
package org.neo4j.driver.internal.async;
2020

21+
import java.util.concurrent.CompletableFuture;
22+
import java.util.function.Supplier;
23+
2124
import org.neo4j.driver.AccessMode;
2225
import org.neo4j.driver.Bookmark;
2326
import org.neo4j.driver.internal.DatabaseName;
@@ -28,9 +31,13 @@
2831
*/
2932
public interface ConnectionContext
3033
{
31-
DatabaseName databaseName();
34+
Supplier<IllegalStateException> PENDING_DATABASE_NAME_EXCEPTION_SUPPLIER = () -> new IllegalStateException( "Pending database name encountered." );
35+
36+
CompletableFuture<DatabaseName> databaseNameFuture();
3237

3338
AccessMode mode();
3439

3540
Bookmark rediscoveryBookmark();
41+
42+
String impersonatedUser();
3643
}

driver/src/main/java/org/neo4j/driver/internal/async/ImmutableConnectionContext.java

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
*/
1919
package org.neo4j.driver.internal.async;
2020

21+
import java.util.concurrent.CompletableFuture;
22+
2123
import org.neo4j.driver.AccessMode;
2224
import org.neo4j.driver.Bookmark;
2325
import org.neo4j.driver.internal.DatabaseName;
@@ -32,24 +34,26 @@
3234
*/
3335
public class ImmutableConnectionContext implements ConnectionContext
3436
{
35-
private static final ConnectionContext SINGLE_DB_CONTEXT = new ImmutableConnectionContext( defaultDatabase(), empty(), AccessMode.READ );
36-
private static final ConnectionContext MULTI_DB_CONTEXT = new ImmutableConnectionContext( systemDatabase(), empty(), AccessMode.READ );
37+
private static final ConnectionContext SINGLE_DB_CONTEXT = new ImmutableConnectionContext( defaultDatabase(), empty(), AccessMode.READ, null );
38+
private static final ConnectionContext MULTI_DB_CONTEXT = new ImmutableConnectionContext( systemDatabase(), empty(), AccessMode.READ, null );
3739

38-
private final DatabaseName databaseName;
40+
private final CompletableFuture<DatabaseName> databaseNameFuture;
3941
private final AccessMode mode;
4042
private final Bookmark rediscoveryBookmark;
43+
private final String impersonatedUser;
4144

42-
public ImmutableConnectionContext( DatabaseName databaseName, Bookmark bookmark, AccessMode mode )
45+
public ImmutableConnectionContext( DatabaseName databaseName, Bookmark bookmark, AccessMode mode, String impersonatedUser )
4346
{
44-
this.databaseName = databaseName;
47+
this.databaseNameFuture = CompletableFuture.completedFuture( databaseName );
4548
this.rediscoveryBookmark = bookmark;
4649
this.mode = mode;
50+
this.impersonatedUser = impersonatedUser;
4751
}
4852

4953
@Override
50-
public DatabaseName databaseName()
54+
public CompletableFuture<DatabaseName> databaseNameFuture()
5155
{
52-
return databaseName;
56+
return databaseNameFuture;
5357
}
5458

5559
@Override
@@ -64,10 +68,15 @@ public Bookmark rediscoveryBookmark()
6468
return rediscoveryBookmark;
6569
}
6670

71+
@Override
72+
public String impersonatedUser()
73+
{
74+
return impersonatedUser;
75+
}
76+
6777
/**
68-
* A simple context is used to test connectivity with a remote server/cluster.
69-
* As long as there is a read only service, the connection shall be established successfully.
70-
* Depending on whether multidb is supported or not, this method returns different context for routing table discovery.
78+
* A simple context is used to test connectivity with a remote server/cluster. As long as there is a read only service, the connection shall be established
79+
* successfully. Depending on whether multidb is supported or not, this method returns different context for routing table discovery.
7180
*/
7281
public static ConnectionContext simple( boolean supportsMultiDb )
7382
{

driver/src/main/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSession.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ public class LeakLoggingNetworkSession extends NetworkSession
3333
private final String stackTrace;
3434

3535
public LeakLoggingNetworkSession( ConnectionProvider connectionProvider, RetryLogic retryLogic, DatabaseName databaseName, AccessMode mode,
36-
BookmarkHolder bookmarkHolder, long fetchSize, Logging logging )
36+
BookmarkHolder bookmarkHolder, String impersonatedUser, long fetchSize, Logging logging )
3737
{
38-
super( connectionProvider, retryLogic, databaseName, mode, bookmarkHolder, fetchSize, logging );
38+
super( connectionProvider, retryLogic, databaseName, mode, bookmarkHolder, impersonatedUser, fetchSize, logging );
3939
this.stackTrace = captureStackTrace();
4040
}
4141

0 commit comments

Comments
 (0)