Skip to content

Commit c43b56e

Browse files
authored
Introducing protocol 4.4 and User Impersonation (#784)
* Introducing protocol 4.4 and User Impersonation Users can, when they have been granted explicit permission, run transactions against the database as different users. When impersonating a user, the query is run as the complete security context of the impersonated user and not the authenticated user (e.g. home database, permissions etc). - Create protocol 4.4 version - Add protocol to the handshake - Change the messages RUN, BEGIN and ROUTE to support the impersonation parameter - Add support to the impersonation into the driver surface - Add special treatments for the default/home database
1 parent 0b9adcf commit c43b56e

39 files changed

+1912
-229
lines changed

packages/bolt-connection/src/bolt/bolt-protocol-util.js

+22-1
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,25 @@ function assertDatabaseIsEmpty (database, onProtocolError = () => {}, observer)
5757
}
5858
}
5959

60-
export { assertDatabaseIsEmpty, assertTxConfigIsEmpty }
60+
/**
61+
* Asserts that the passed-in impersonated user is empty
62+
* @param {string} impersonatedUser
63+
* @param {function (err:Error)} onProtocolError Called when it does have impersonated user set
64+
* @param {any} observer
65+
*/
66+
function assertImpersonatedUserIsEmpty (impersonatedUser, onProtocolError = () => {}, observer) {
67+
if (impersonatedUser) {
68+
const error = newError(
69+
'Driver is connected to the database that does not support user impersonation. ' +
70+
'Please upgrade to neo4j 4.4.0 or later in order to use this functionality. ' +
71+
`Trying to impersonate ${impersonatedUser}.`
72+
)
73+
74+
// unsupported API was used, consider this a fatal error for the current connection
75+
onProtocolError(error.message)
76+
observer.onError(error)
77+
throw error
78+
}
79+
}
80+
81+
export { assertDatabaseIsEmpty, assertTxConfigIsEmpty, assertImpersonatedUserIsEmpty }

packages/bolt-connection/src/bolt/bolt-protocol-v1.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
*/
1919
import {
2020
assertDatabaseIsEmpty,
21-
assertTxConfigIsEmpty
21+
assertTxConfigIsEmpty,
22+
assertImpersonatedUserIsEmpty
2223
} from './bolt-protocol-util'
2324
import { Chunker } from '../chunking'
2425
import { v1 } from '../packstream'
@@ -143,6 +144,7 @@ export default class BoltProtocol {
143144
* @param {TxConfig} param.txConfig the configuration.
144145
* @param {string} param.database the target database name.
145146
* @param {string} param.mode the access mode.
147+
* @param {string} param.impersonatedUser the impersonated user
146148
* @param {function(err: Error)} param.beforeError the callback to invoke before handling the error.
147149
* @param {function(err: Error)} param.afterError the callback to invoke after handling the error.
148150
* @param {function()} param.beforeComplete the callback to invoke before handling the completion.
@@ -154,6 +156,7 @@ export default class BoltProtocol {
154156
txConfig,
155157
database,
156158
mode,
159+
impersonatedUser,
157160
beforeError,
158161
afterError,
159162
beforeComplete,
@@ -167,6 +170,7 @@ export default class BoltProtocol {
167170
txConfig: txConfig,
168171
database,
169172
mode,
173+
impersonatedUser,
170174
beforeError,
171175
afterError,
172176
beforeComplete,
@@ -248,6 +252,7 @@ export default class BoltProtocol {
248252
* @param {Bookmark} param.bookmark the bookmark.
249253
* @param {TxConfig} param.txConfig the transaction configuration.
250254
* @param {string} param.database the target database name.
255+
* @param {string} param.impersonatedUser the impersonated user
251256
* @param {string} param.mode the access mode.
252257
* @param {function(keys: string[])} param.beforeKeys the callback to invoke before handling the keys.
253258
* @param {function(keys: string[])} param.afterKeys the callback to invoke after handling the keys.
@@ -266,6 +271,7 @@ export default class BoltProtocol {
266271
txConfig,
267272
database,
268273
mode,
274+
impersonatedUser,
269275
beforeKeys,
270276
afterKeys,
271277
beforeError,
@@ -289,6 +295,8 @@ export default class BoltProtocol {
289295
assertTxConfigIsEmpty(txConfig, this._onProtocolError, observer)
290296
// passing in a database name on this protocol version throws an error
291297
assertDatabaseIsEmpty(database, this._onProtocolError, observer)
298+
// passing impersonated user on this protocol version throws an error
299+
assertImpersonatedUserIsEmpty(impersonatedUser, this._onProtocolError, observer)
292300

293301
this.write(RequestMessage.run(query, parameters), observer, false)
294302
this.write(RequestMessage.pullAll(), observer, flush)

packages/bolt-connection/src/bolt/bolt-protocol-v3.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*/
1919
import BoltProtocolV2 from './bolt-protocol-v2'
2020
import RequestMessage from './request-message'
21-
import { assertDatabaseIsEmpty } from './bolt-protocol-util'
21+
import { assertDatabaseIsEmpty, assertImpersonatedUserIsEmpty } from './bolt-protocol-util'
2222
import {
2323
StreamObserver,
2424
LoginObserver,
@@ -78,6 +78,7 @@ export default class BoltProtocol extends BoltProtocolV2 {
7878
bookmark,
7979
txConfig,
8080
database,
81+
impersonatedUser,
8182
mode,
8283
beforeError,
8384
afterError,
@@ -95,6 +96,8 @@ export default class BoltProtocol extends BoltProtocolV2 {
9596

9697
// passing in a database name on this protocol version throws an error
9798
assertDatabaseIsEmpty(database, this._onProtocolError, observer)
99+
// passing impersonated user on this protocol version throws an error
100+
assertImpersonatedUserIsEmpty(impersonatedUser, this._onProtocolError, observer)
98101

99102
this.write(
100103
RequestMessage.begin({ bookmark, txConfig, mode }),
@@ -152,6 +155,7 @@ export default class BoltProtocol extends BoltProtocolV2 {
152155
bookmark,
153156
txConfig,
154157
database,
158+
impersonatedUser,
155159
mode,
156160
beforeKeys,
157161
afterKeys,
@@ -174,6 +178,8 @@ export default class BoltProtocol extends BoltProtocolV2 {
174178

175179
// passing in a database name on this protocol version throws an error
176180
assertDatabaseIsEmpty(database, this._onProtocolError, observer)
181+
// passing impersonated user on this protocol version throws an error
182+
assertImpersonatedUserIsEmpty(impersonatedUser, this._onProtocolError, observer)
177183

178184
this.write(
179185
RequestMessage.runWithMetadata(query, parameters, {

packages/bolt-connection/src/bolt/bolt-protocol-v4x0.js

+9
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
import BoltProtocolV3 from './bolt-protocol-v3'
2020
import RequestMessage, { ALL } from './request-message'
21+
import { assertImpersonatedUserIsEmpty } from './bolt-protocol-util'
2122
import {
2223
ResultStreamObserver,
2324
ProcedureRouteObserver
@@ -44,6 +45,7 @@ export default class BoltProtocol extends BoltProtocolV3 {
4445
bookmark,
4546
txConfig,
4647
database,
48+
impersonatedUser,
4749
mode,
4850
beforeError,
4951
afterError,
@@ -59,6 +61,9 @@ export default class BoltProtocol extends BoltProtocolV3 {
5961
})
6062
observer.prepareToHandleSingleResponse()
6163

64+
// passing impersonated user on this protocol version throws an error
65+
assertImpersonatedUserIsEmpty(impersonatedUser, this._onProtocolError, observer)
66+
6267
this.write(
6368
RequestMessage.begin({ bookmark, txConfig, database, mode }),
6469
observer,
@@ -75,6 +80,7 @@ export default class BoltProtocol extends BoltProtocolV3 {
7580
bookmark,
7681
txConfig,
7782
database,
83+
impersonatedUser,
7884
mode,
7985
beforeKeys,
8086
afterKeys,
@@ -101,6 +107,9 @@ export default class BoltProtocol extends BoltProtocolV3 {
101107
afterComplete
102108
})
103109

110+
// passing impersonated user on this protocol version throws an error
111+
assertImpersonatedUserIsEmpty(impersonatedUser, this._onProtocolError, observer)
112+
104113
const flushRun = reactive
105114
this.write(
106115
RequestMessage.runWithMetadata(query, parameters, {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
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+
import BoltProtocolV43 from './bolt-protocol-v4x3'
20+
21+
import { internal } from 'neo4j-driver-core'
22+
import RequestMessage, { ALL } from './request-message'
23+
import { RouteObserver, ResultStreamObserver } from './stream-observers'
24+
25+
const {
26+
constants: { BOLT_PROTOCOL_V4_4 },
27+
bookmark: { Bookmark },
28+
} = internal
29+
30+
export default class BoltProtocol extends BoltProtocolV43 {
31+
get version() {
32+
return BOLT_PROTOCOL_V4_4
33+
}
34+
35+
/**
36+
* Request routing information
37+
*
38+
* @param {Object} param -
39+
* @param {object} param.routingContext The routing context used to define the routing table.
40+
* Multi-datacenter deployments is one of its use cases
41+
* @param {string} param.databaseName The database name
42+
* @param {Bookmark} params.sessionContext.bookmark The bookmark used for request the routing table
43+
* @param {function(err: Error)} param.onError
44+
* @param {function(RawRoutingTable)} param.onCompleted
45+
* @returns {RouteObserver} the route observer
46+
*/
47+
requestRoutingInformation ({
48+
routingContext = {},
49+
databaseName = null,
50+
impersonatedUser = null,
51+
sessionContext = {},
52+
onError,
53+
onCompleted
54+
}) {
55+
const observer = new RouteObserver({
56+
onProtocolError: this._onProtocolError,
57+
onError,
58+
onCompleted
59+
})
60+
const bookmark = sessionContext.bookmark || Bookmark.empty()
61+
this.write(
62+
RequestMessage.routeV4x4(routingContext, bookmark.values(), { databaseName, impersonatedUser }),
63+
observer,
64+
true
65+
)
66+
67+
return observer
68+
}
69+
70+
run (
71+
query,
72+
parameters,
73+
{
74+
bookmark,
75+
txConfig,
76+
database,
77+
mode,
78+
impersonatedUser,
79+
beforeKeys,
80+
afterKeys,
81+
beforeError,
82+
afterError,
83+
beforeComplete,
84+
afterComplete,
85+
flush = true,
86+
reactive = false,
87+
fetchSize = ALL
88+
} = {}
89+
) {
90+
const observer = new ResultStreamObserver({
91+
server: this._server,
92+
reactive: reactive,
93+
fetchSize: fetchSize,
94+
moreFunction: this._requestMore.bind(this),
95+
discardFunction: this._requestDiscard.bind(this),
96+
beforeKeys,
97+
afterKeys,
98+
beforeError,
99+
afterError,
100+
beforeComplete,
101+
afterComplete
102+
})
103+
104+
const flushRun = reactive
105+
this.write(
106+
RequestMessage.runWithMetadata(query, parameters, {
107+
bookmark,
108+
txConfig,
109+
database,
110+
mode,
111+
impersonatedUser
112+
}),
113+
observer,
114+
flushRun && flush
115+
)
116+
117+
if (!reactive) {
118+
this.write(RequestMessage.pull({ n: fetchSize }), observer, flush)
119+
}
120+
121+
return observer
122+
}
123+
124+
beginTransaction ({
125+
bookmark,
126+
txConfig,
127+
database,
128+
mode,
129+
impersonatedUser,
130+
beforeError,
131+
afterError,
132+
beforeComplete,
133+
afterComplete
134+
} = {}) {
135+
const observer = new ResultStreamObserver({
136+
server: this._server,
137+
beforeError,
138+
afterError,
139+
beforeComplete,
140+
afterComplete
141+
})
142+
observer.prepareToHandleSingleResponse()
143+
144+
this.write(
145+
RequestMessage.begin({ bookmark, txConfig, database, mode, impersonatedUser }),
146+
observer,
147+
true
148+
)
149+
150+
return observer
151+
}
152+
153+
}

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

+11
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import BoltProtocolV4x0 from './bolt-protocol-v4x0'
2525
import BoltProtocolV4x1 from './bolt-protocol-v4x1'
2626
import BoltProtocolV4x2 from './bolt-protocol-v4x2'
2727
import BoltProtocolV4x3 from './bolt-protocol-v4x3'
28+
import BoltProtocolV4x4 from './bolt-protocol-v4x4'
2829
import { Chunker, Dechunker } from '../channel'
2930
import ResponseHandler from './response-handler'
3031

@@ -164,6 +165,16 @@ function createProtocol (
164165
onProtocolError,
165166
serversideRouting
166167
)
168+
case 4.4:
169+
return new BoltProtocolV4x4(
170+
server,
171+
chunker,
172+
packingConfig,
173+
createResponseHandler,
174+
log,
175+
onProtocolError,
176+
serversideRouting
177+
)
167178
default:
168179
throw newError('Unknown Bolt protocol version: ' + version)
169180
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ function parseNegotiatedResponse (buffer) {
7676
*/
7777
function newHandshakeBuffer () {
7878
return createHandshakeMessage([
79-
[version(4, 3), version(4, 2)],
79+
[version(4, 4), version(4, 2)],
8080
version(4, 1),
8181
version(4, 0),
8282
version(3, 0)

0 commit comments

Comments
 (0)