Skip to content

Commit c7f69d4

Browse files
committed
Introduce impersonation support
This update brings support for the new impersonation feature. This feature allows creating sessions with impersonated user, including using default database of impersonated user.
1 parent d622c3e commit c7f69d4

File tree

77 files changed

+974
-391
lines changed

Some content is hidden

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

77 files changed

+974
-391
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)