Skip to content

Commit c688fcc

Browse files
committed
Mark all connections for closure on LDAP Auth Credential expiration
When an authenticated connection is made by the drivers, the server takes the raw credentials passed in the HELLO message and uses them to generate a token which is then cached and used to authorize all subsequent requests. In some implementations of auth (principally LDAP), this cached token can expire and the original raw credentials are required to generate a new token. Since these raw credentials are not stored on the server and are only provided during connection initialization, the only way currently to refresh these tokens is by terminating the existing connection and create new connections. When the driver receives an error with status `Status.Security.AuthorizationExpired` this should have the effect of marking all current connections as stale/invalid, forcing the driver to establish new connections and therefore refreshing the credentials cached on the server. Existing connections which are currently in use should not be interrupted.
1 parent b3a0394 commit c688fcc

File tree

4 files changed

+127
-6
lines changed

4 files changed

+127
-6
lines changed

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

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,19 @@
1818
*/
1919

2020
import PooledConnectionProvider from './connection-provider-pooled'
21-
import { createChannelConnection, DelegateConnection } from '../connection'
22-
import { internal } from 'neo4j-driver-core'
21+
import {
22+
createChannelConnection,
23+
DelegateConnection,
24+
ConnectionErrorHandler
25+
} from '../connection'
26+
import { internal, error } from 'neo4j-driver-core'
2327

2428
const {
2529
constants: { BOLT_PROTOCOL_V4_0, BOLT_PROTOCOL_V3 }
2630
} = internal
2731

32+
const { SERVICE_UNAVAILABLE, newError } = error
33+
2834
export default class DirectConnectionProvider extends PooledConnectionProvider {
2935
constructor ({ id, config, log, address, userAgent, authToken }) {
3036
super({ id, config, log, userAgent, authToken })
@@ -37,9 +43,26 @@ export default class DirectConnectionProvider extends PooledConnectionProvider {
3743
* its arguments.
3844
*/
3945
acquireConnection ({ accessMode, database, bookmarks } = {}) {
46+
const databaseSpecificErrorHandler = ConnectionErrorHandler.create({
47+
errorCode: SERVICE_UNAVAILABLE,
48+
handleAuthorizationExpired: (error, address) =>
49+
this._handleAuthorizationExpired(error, address, database)
50+
})
51+
4052
return this._connectionPool
4153
.acquire(this._address)
42-
.then(connection => new DelegateConnection(connection, null))
54+
.then(
55+
connection =>
56+
new DelegateConnection(connection, databaseSpecificErrorHandler)
57+
)
58+
}
59+
60+
_handleAuthorizationExpired (error, address, database) {
61+
this._log.warn(
62+
`Direct driver ${this._id} will close connection to ${address} for database '${database}' because of an error ${error.code} '${error.message}'`
63+
)
64+
this._connectionPool.purge(address).catch(() => {})
65+
return error
4366
}
4467

4568
async _hasProtocolVersion (versionPredicate) {

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,14 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
100100
return error
101101
}
102102

103+
_handleAuthorizationExpired (error, address, database) {
104+
this._log.warn(
105+
`Routing driver ${this._id} will close connections to ${address} for database '${database}' because of an error ${error.code} '${error.message}'`
106+
)
107+
this._connectionPool.purge(address).catch(() => {})
108+
return error
109+
}
110+
103111
_handleWriteFailure (error, address, database) {
104112
this._log.warn(
105113
`Routing driver ${this._id} will forget writer ${address} for database '${database}' because of an error ${error.code} '${error.message}'`
@@ -122,7 +130,9 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
122130
const databaseSpecificErrorHandler = new ConnectionErrorHandler(
123131
SESSION_EXPIRED,
124132
(error, address) => this._handleUnavailability(error, address, database),
125-
(error, address) => this._handleWriteFailure(error, address, database)
133+
(error, address) => this._handleWriteFailure(error, address, database),
134+
(error, address) =>
135+
this._handleAuthorizationExpired(error, address, database)
126136
)
127137

128138
const routingTable = await this._freshRoutingTable({

bolt-connection/src/connection/connection-error-handler.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,30 @@ import { error } from 'neo4j-driver-core'
2222
const { SERVICE_UNAVAILABLE, SESSION_EXPIRED } = error
2323

2424
export default class ConnectionErrorHandler {
25-
constructor (errorCode, handleUnavailability, handleWriteFailure) {
25+
constructor (
26+
errorCode,
27+
handleUnavailability,
28+
handleWriteFailure,
29+
handleAuthorizationExpired
30+
) {
2631
this._errorCode = errorCode
2732
this._handleUnavailability = handleUnavailability || noOpHandler
2833
this._handleWriteFailure = handleWriteFailure || noOpHandler
34+
this._handleAuthorizationExpired = handleAuthorizationExpired || noOpHandler
35+
}
36+
37+
static create ({
38+
errorCode,
39+
handleUnavailability,
40+
handleWriteFailure,
41+
handleAuthorizationExpired
42+
}) {
43+
return new ConnectionErrorHandler(
44+
errorCode,
45+
handleUnavailability,
46+
handleWriteFailure,
47+
handleAuthorizationExpired
48+
)
2949
}
3050

3151
/**
@@ -43,6 +63,9 @@ export default class ConnectionErrorHandler {
4363
* @return {Neo4jError} new error that should be propagated to the user.
4464
*/
4565
handleAndTransformError (error, address) {
66+
if (isAutorizationExpiredError(error)) {
67+
return this._handleAuthorizationExpired(error, address)
68+
}
4669
if (isAvailabilityError(error)) {
4770
return this._handleUnavailability(error, address)
4871
}
@@ -53,6 +76,10 @@ export default class ConnectionErrorHandler {
5376
}
5477
}
5578

79+
function isAutorizationExpiredError (error) {
80+
return error && error.code === 'Neo.ClientError.Security.AuthorizationExpired'
81+
}
82+
5683
function isAvailabilityError (error) {
5784
if (error) {
5885
return (

test/internal/connection-error-handler.test.js renamed to bolt-connection/test/connection/connection-error-handler.test.js

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
* limitations under the License.
1818
*/
1919

20-
import ConnectionErrorHandler from '../../bolt-connection/lib/connection/connection-error-handler'
20+
import ConnectionErrorHandler from '../../src/connection/connection-error-handler'
2121
import { newError, error, internal } from 'neo4j-driver-core'
2222

2323
const {
@@ -79,6 +79,67 @@ describe('#unit ConnectionErrorHandler', () => {
7979
])
8080
})
8181

82+
it('should handle and transform authorization expired error', () => {
83+
const errors = []
84+
const addresses = []
85+
const transformedError = newError('Message', 'Code')
86+
const handler = ConnectionErrorHandler.create({
87+
errorCode: SERVICE_UNAVAILABLE,
88+
handleAuthorizationExpired: (error, address) => {
89+
errors.push(error)
90+
addresses.push(address)
91+
return transformedError
92+
}
93+
})
94+
95+
const error1 = newError(
96+
'C',
97+
'Neo.ClientError.Security.AuthorizationExpired'
98+
)
99+
100+
const errorTransformed1 = handler.handleAndTransformError(
101+
error1,
102+
ServerAddress.fromUrl('localhost:0')
103+
)
104+
105+
expect(errorTransformed1).toEqual(transformedError)
106+
107+
expect(addresses).toEqual([ServerAddress.fromUrl('localhost:0')])
108+
})
109+
110+
it('should return original erro if authorization expired handler is not informed', () => {
111+
const errors = []
112+
const addresses = []
113+
const transformedError = newError('Message', 'Code')
114+
const handler = ConnectionErrorHandler.create({
115+
errorCode: SERVICE_UNAVAILABLE,
116+
handleUnavailability: (error, address) => {
117+
errors.push(error)
118+
addresses.push(address)
119+
return transformedError
120+
},
121+
handleWriteFailure: (error, address) => {
122+
errors.push(error)
123+
addresses.push(address)
124+
return transformedError
125+
}
126+
})
127+
128+
const error1 = newError(
129+
'C',
130+
'Neo.ClientError.Security.AuthorizationExpired'
131+
)
132+
133+
const errorTransformed1 = handler.handleAndTransformError(
134+
error1,
135+
ServerAddress.fromUrl('localhost:0')
136+
)
137+
138+
expect(errorTransformed1).toEqual(error1)
139+
140+
expect(addresses).toEqual([])
141+
})
142+
82143
it('should handle and transform failure to write errors', () => {
83144
const errors = []
84145
const addresses = []

0 commit comments

Comments
 (0)