Skip to content

Commit c5d737f

Browse files
committed
Implement user-switch doesn't allow sticky connection
1 parent d2355a5 commit c5d737f

File tree

13 files changed

+325
-79
lines changed

13 files changed

+325
-79
lines changed

packages/bolt-connection/src/connection-provider/connection-provider-direct.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import {
2424
ConnectionErrorHandler
2525
} from '../connection'
2626
import { internal, error } from 'neo4j-driver-core'
27-
import { object } from '../lang'
2827

2928
const {
3029
constants: { BOLT_PROTOCOL_V3, BOLT_PROTOCOL_V4_0, BOLT_PROTOCOL_V4_4 }
@@ -43,7 +42,7 @@ export default class DirectConnectionProvider extends PooledConnectionProvider {
4342
* See {@link ConnectionProvider} for more information about this method and
4443
* its arguments.
4544
*/
46-
async acquireConnection ({ accessMode, database, bookmarks, auth } = {}) {
45+
async acquireConnection ({ accessMode, database, bookmarks, auth, allowStickyConnection } = {}) {
4746
const databaseSpecificErrorHandler = ConnectionErrorHandler.create({
4847
errorCode: SERVICE_UNAVAILABLE,
4948
handleAuthorizationExpired: (error, address, conn) =>
@@ -52,9 +51,11 @@ export default class DirectConnectionProvider extends PooledConnectionProvider {
5251

5352
const connection = await this._connectionPool.acquire({ auth }, this._address)
5453

55-
if (auth && !object.equals(auth, connection.authToken)) {
56-
await connection._release()
57-
return await this._createStickyConnection({ address: this._address, auth })
54+
if (auth) {
55+
const stickyConnection = await this._getStickyConnection({ auth, connection, allowStickyConnection })
56+
if (stickyConnection) {
57+
return stickyConnection
58+
}
5859
}
5960

6061
return new DelegateConnection(connection, databaseSpecificErrorHandler)

packages/bolt-connection/src/connection-provider/connection-provider-pooled.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@
1919

2020
import { createChannelConnection, ConnectionErrorHandler } from '../connection'
2121
import Pool, { PoolConfig } from '../pool'
22-
import { error, ConnectionProvider, ServerInfo } from 'neo4j-driver-core'
22+
import { error, ConnectionProvider, ServerInfo, newError } from 'neo4j-driver-core'
2323
import AuthenticationProvider from './authentication-provider'
24+
import { object } from '../lang'
2425

2526
const { SERVICE_UNAVAILABLE } = error
2627
export default class PooledConnectionProvider extends ConnectionProvider {
@@ -167,6 +168,19 @@ export default class PooledConnectionProvider extends ConnectionProvider {
167168
return serverInfo
168169
}
169170

171+
async _getStickyConnection ({ auth, connection, allowStickyConnection }) {
172+
const connectionWithSameCredentials = object.equals(auth, connection.authToken)
173+
const shouldCreateStickyConnection = !connectionWithSameCredentials
174+
const stickyConnectionWasCreated = connectionWithSameCredentials && !connection.supportsReAuth
175+
if (allowStickyConnection !== true && (shouldCreateStickyConnection || stickyConnectionWasCreated)) {
176+
await connection._release()
177+
throw newError('Driver is connected to a database that does not support user switch.')
178+
} else if (allowStickyConnection === true && shouldCreateStickyConnection) {
179+
await connection._release()
180+
return await this._createStickyConnection({ address: this._address, auth })
181+
}
182+
}
183+
170184
async close () {
171185
// purge all idle connections in the connection pool
172186
await this._connectionPool.close()

packages/bolt-connection/src/connection-provider/connection-provider-routing.js

Lines changed: 47 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import {
2828
ConnectionErrorHandler,
2929
DelegateConnection
3030
} from '../connection'
31-
import { object } from '../lang'
3231

3332
const { SERVICE_UNAVAILABLE, SESSION_EXPIRED } = error
3433
const {
@@ -52,6 +51,7 @@ const AUTHORIZATION_EXPIRED_CODE =
5251
const INVALID_ARGUMENT_ERROR = 'Neo.ClientError.Statement.ArgumentError'
5352
const INVALID_REQUEST_ERROR = 'Neo.ClientError.Request.Invalid'
5453
const STATEMENT_TYPE_ERROR = 'Neo.ClientError.Statement.TypeError'
54+
const NOT_AVAILABLE = 'N/A'
5555

5656
const SYSTEM_DB_NAME = 'system'
5757
const DEFAULT_DB_NAME = null
@@ -143,7 +143,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
143143
* See {@link ConnectionProvider} for more information about this method and
144144
* its arguments.
145145
*/
146-
async acquireConnection ({ accessMode, database, bookmarks, impersonatedUser, onDatabaseNameResolved, auth } = {}) {
146+
async acquireConnection ({ accessMode, database, bookmarks, impersonatedUser, onDatabaseNameResolved, auth, allowStickyConnection } = {}) {
147147
let name
148148
let address
149149
const context = { database: database || DEFAULT_DB_NAME }
@@ -162,6 +162,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
162162
bookmarks,
163163
impersonatedUser,
164164
auth,
165+
allowStickyConnection,
165166
onDatabaseNameResolved: (databaseName) => {
166167
context.database = context.database || databaseName
167168
if (onDatabaseNameResolved) {
@@ -192,9 +193,11 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
192193
try {
193194
const connection = await this._connectionPool.acquire({ auth }, address)
194195

195-
if (auth && !object.equals(auth, connection.authToken)) {
196-
await connection._release()
197-
return await this._createStickyConnection({ address, auth })
196+
if (auth) {
197+
const stickyConnection = await this._getStickyConnection({ auth, connection, allowStickyConnection })
198+
if (stickyConnection) {
199+
return stickyConnection
200+
}
198201
}
199202

200203
return new DelegateConnection(connection, databaseSpecificErrorHandler)
@@ -312,7 +315,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
312315
})
313316
}
314317

315-
_freshRoutingTable ({ accessMode, database, bookmarks, impersonatedUser, onDatabaseNameResolved, auth } = {}) {
318+
_freshRoutingTable ({ accessMode, database, bookmarks, impersonatedUser, onDatabaseNameResolved, auth, allowStickyConnection } = {}) {
316319
const currentRoutingTable = this._routingTableRegistry.get(
317320
database,
318321
() => new RoutingTable({ database })
@@ -324,10 +327,10 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
324327
this._log.info(
325328
`Routing table is stale for database: "${database}" and access mode: "${accessMode}": ${currentRoutingTable}`
326329
)
327-
return this._refreshRoutingTable(currentRoutingTable, bookmarks, impersonatedUser, onDatabaseNameResolved, auth)
330+
return this._refreshRoutingTable(currentRoutingTable, bookmarks, impersonatedUser, onDatabaseNameResolved, auth, allowStickyConnection)
328331
}
329332

330-
_refreshRoutingTable (currentRoutingTable, bookmarks, impersonatedUser, onDatabaseNameResolved, auth) {
333+
_refreshRoutingTable (currentRoutingTable, bookmarks, impersonatedUser, onDatabaseNameResolved, auth, allowStickyConnection) {
331334
const knownRouters = currentRoutingTable.routers
332335

333336
if (this._useSeedRouter) {
@@ -337,7 +340,8 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
337340
bookmarks,
338341
impersonatedUser,
339342
onDatabaseNameResolved,
340-
auth
343+
auth,
344+
allowStickyConnection
341345
)
342346
}
343347
return this._fetchRoutingTableFromKnownRoutersFallbackToSeedRouter(
@@ -346,7 +350,8 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
346350
bookmarks,
347351
impersonatedUser,
348352
onDatabaseNameResolved,
349-
auth
353+
auth,
354+
allowStickyConnection
350355
)
351356
}
352357

@@ -356,7 +361,8 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
356361
bookmarks,
357362
impersonatedUser,
358363
onDatabaseNameResolved,
359-
auth
364+
auth,
365+
allowStickyConnection
360366
) {
361367
// we start with seed router, no routers were probed before
362368
const seenRouters = []
@@ -366,7 +372,8 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
366372
currentRoutingTable,
367373
bookmarks,
368374
impersonatedUser,
369-
auth
375+
auth,
376+
allowStickyConnection
370377
)
371378

372379
if (newRoutingTable) {
@@ -378,7 +385,8 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
378385
currentRoutingTable,
379386
bookmarks,
380387
impersonatedUser,
381-
auth
388+
auth,
389+
allowStickyConnection
382390
)
383391
newRoutingTable = newRoutingTable2
384392
error = error2 || error
@@ -398,14 +406,16 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
398406
bookmarks,
399407
impersonatedUser,
400408
onDatabaseNameResolved,
401-
auth
409+
auth,
410+
allowStickyConnection
402411
) {
403412
let [newRoutingTable, error] = await this._fetchRoutingTableUsingKnownRouters(
404413
knownRouters,
405414
currentRoutingTable,
406415
bookmarks,
407416
impersonatedUser,
408-
auth
417+
auth,
418+
allowStickyConnection
409419
)
410420

411421
if (!newRoutingTable) {
@@ -416,7 +426,8 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
416426
currentRoutingTable,
417427
bookmarks,
418428
impersonatedUser,
419-
auth
429+
auth,
430+
allowStickyConnection
420431
)
421432
}
422433

@@ -433,14 +444,16 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
433444
currentRoutingTable,
434445
bookmarks,
435446
impersonatedUser,
436-
auth
447+
auth,
448+
allowStickyConnection
437449
) {
438450
const [newRoutingTable, error] = await this._fetchRoutingTable(
439451
knownRouters,
440452
currentRoutingTable,
441453
bookmarks,
442454
impersonatedUser,
443-
auth
455+
auth,
456+
allowStickyConnection
444457
)
445458

446459
if (newRoutingTable) {
@@ -466,7 +479,8 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
466479
routingTable,
467480
bookmarks,
468481
impersonatedUser,
469-
auth
482+
auth,
483+
allowStickyConnection
470484
) {
471485
const resolvedAddresses = await this._resolveSeedRouter(seedRouter)
472486

@@ -475,7 +489,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
475489
address => seenRouters.indexOf(address) < 0
476490
)
477491

478-
return await this._fetchRoutingTable(newAddresses, routingTable, bookmarks, impersonatedUser, auth)
492+
return await this._fetchRoutingTable(newAddresses, routingTable, bookmarks, impersonatedUser, auth, allowStickyConnection)
479493
}
480494

481495
async _resolveSeedRouter (seedRouter) {
@@ -487,7 +501,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
487501
return [].concat.apply([], dnsResolvedAddresses)
488502
}
489503

490-
async _fetchRoutingTable (routerAddresses, routingTable, bookmarks, impersonatedUser, auth) {
504+
async _fetchRoutingTable (routerAddresses, routingTable, bookmarks, impersonatedUser, auth, allowStickyConnection) {
491505
return routerAddresses.reduce(
492506
async (refreshedTablePromise, currentRouter, currentIndex) => {
493507
const [newRoutingTable] = await refreshedTablePromise
@@ -511,7 +525,8 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
511525
currentRouter,
512526
bookmarks,
513527
impersonatedUser,
514-
auth
528+
auth,
529+
allowStickyConnection
515530
)
516531
if (session) {
517532
try {
@@ -536,16 +551,15 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
536551
)
537552
}
538553

539-
async _createSessionForRediscovery (routerAddress, bookmarks, impersonatedUser, auth) {
554+
async _createSessionForRediscovery (routerAddress, bookmarks, impersonatedUser, auth, allowStickyConnection) {
540555
try {
541-
let connection = await this._connectionPool.acquire({ auth }, routerAddress)
542-
543-
if (auth && !object.equals(auth, connection.authToken)) {
544-
await connection._release()
545-
connection = await this._createStickyConnection({
546-
address: routerAddress,
547-
auth
548-
})
556+
const connection = await this._connectionPool.acquire({ auth }, routerAddress)
557+
558+
if (auth) {
559+
const stickyConnection = await this._getStickyConnection({ auth, connection, allowStickyConnection })
560+
if (stickyConnection) {
561+
return stickyConnection
562+
}
549563
}
550564

551565
const databaseSpecificErrorHandler = ConnectionErrorHandler.create({
@@ -737,7 +751,8 @@ function _isFailFastError (error) {
737751
INVALID_BOOKMARK_MIXTURE_CODE,
738752
INVALID_ARGUMENT_ERROR,
739753
INVALID_REQUEST_ERROR,
740-
STATEMENT_TYPE_ERROR
754+
STATEMENT_TYPE_ERROR,
755+
NOT_AVAILABLE
741756
].includes(error.code)
742757
}
743758

packages/bolt-connection/src/pool/pool.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,6 @@ class Pool {
245245
try {
246246
// Invoke callback that creates actual connection
247247
resource = await this._create(acquisitionContext, address, (address, resource) => this._release(address, resource, pool))
248-
249248
pool.pushInUse(resource)
250249
resourceAcquired(key, this._activeResourceCounts)
251250
if (this._log.isDebugEnabled()) {

packages/bolt-connection/test/connection-provider/connection-provider-direct.test.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { Pool } from '../../src/pool'
2222
import { Connection, DelegateConnection } from '../../src/connection'
2323
import { internal, newError, ServerInfo } from 'neo4j-driver-core'
2424
import AuthenticationProvider from '../../src/connection-provider/authentication-provider'
25+
import { functional } from '../../src/lang'
2526

2627
const {
2728
serverAddress: { ServerAddress },
@@ -607,6 +608,36 @@ describe('.verifyConnectivityAndGetServerInfo()', () => {
607608
}
608609
})
609610
})
611+
612+
describe('user-switching', () => {
613+
describe.each([
614+
undefined,
615+
false,
616+
null
617+
])('when allowStickyConnection is %s', (allowStickyConnection) => {
618+
it('should raise and error when try switch user on acquire', async () => {
619+
const address = ServerAddress.fromUrl('localhost:123')
620+
const pool = newPool()
621+
const connection = new FakeConnection(address, () => {}, undefined, { some: 'auth' })
622+
const poolAcquire = jest.spyOn(pool, 'acquire').mockResolvedValue(connection)
623+
const connectionProvider = newDirectConnectionProvider(address, pool)
624+
const auth = { other: 'token' }
625+
626+
const error = await connectionProvider
627+
.acquireConnection({
628+
accessMode: 'READ',
629+
database: '',
630+
allowStickyConnection,
631+
auth
632+
})
633+
.catch(functional.identity)
634+
635+
expect(error).toEqual(newError('Driver is connected to a database that does not support user switch.'))
636+
expect(poolAcquire).toHaveBeenCalledWith({ auth }, address)
637+
expect(connection._release).toHaveBeenCalled()
638+
})
639+
})
640+
})
610641
})
611642

612643
function newDirectConnectionProvider (address, pool) {

0 commit comments

Comments
 (0)