Skip to content

Commit 6002a8c

Browse files
authored
Introduce auth rotation and session auth support (#890)
# ADR 012: re-authentication This PR introduces two new auth mechanics for different use-cases 1) Auth Rotation 2) Session Auth (a.k.a. user switching) **Note that all APIs introduced in this PR are previews** See https://github.com/neo4j/neo4j-python-driver/wiki/preview-features ## 1) Auth Rotation This is used for auth tokens that is expected to expire (e.g., SSO). A `neo4j.auth_management.AuthManager` instance (or `neo4j.auth_management.AsyncAuthManager` for async driver) may be passed to the driver instead of a static auth token. ```python import neo4j from neo4j.auth_management import AuthManager class MyManager(AuthManager): ... # see API dos for details with neo4j.GraphDatabase.driver( "neo4j://example.com:7687", auth=MyManager(), ) as driver: ... ``` The easiest way to get started is using the provided `AuthManager` implementation. For example: ```python import neo4j from neo4j.auth_management import ( AuthManagers, ExpiringAuth, ) def auth_provider(): # some way to getting a token sso_token = get_sso_token() # assume we know our tokens expire every 60 seconds expires_in = 60 return ExpiringAuth( auth=neo4j.bearer_auth(sso_token), # Include a little buffer so that we fetch a new token # *before* the old one expires expires_in=expires_in - 10 ) with neo4j.GraphDatabase.driver( "neo4j://example.com:7687", auth=AuthManagers.temporal(auth_provider) ) as driver: ... ``` **Note** This API is explicitly *not* designed for switching users. In fact, the token returned by each manager must always belong to the same identity. Switching identities using the `AuthManager` is undefined behavior. ## 2) Session Auth For the purpose of switching users, `Session`s can be configured with a static auth token. This is very similar to impersonation in that all work in the session will be executed in the security context of the user associated with the auth token. The major difference is that impersonation does not require or verify authentication information of the target user, however it requires the impersonating user to have the permission to impersonate. **Note** This requires Bolt protocol version 5.3 or higher (Neo4j DBMS 5.8+). ```python import neo4j with neo4j.GraphDatabase.driver( "neo4j://example.com:7687", auth=("user1", "password1"), ) as driver: with driver.session(database="neo4j") as session: ... # do some work as user1 with driver.session(database="neo4j", auth=("user2", "password2")) as session: ... # do some work as user2 ``` ## References Depends on: * neo4j-drivers/testkit#539 Related PRs: * #891 Issues: * closes #834
1 parent 288dc12 commit 6002a8c

File tree

85 files changed

+6327
-2226
lines changed

Some content is hidden

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

85 files changed

+6327
-2226
lines changed

docs/source/api.rst

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,17 @@ Auth
107107

108108
To authenticate with Neo4j the authentication details are supplied at driver creation.
109109

110-
The auth token is an object of the class :class:`neo4j.Auth` containing the details.
110+
The auth token is an object of the class :class:`neo4j.Auth` containing static details or :class:`neo4j.auth_management.AuthManager` object.
111111

112112
.. autoclass:: neo4j.Auth
113113

114+
.. autoclass:: neo4j.auth_management.AuthManager
115+
:members:
116+
117+
.. autoclass:: neo4j.auth_management.AuthManagers
118+
:members:
119+
120+
.. autoclass:: neo4j.auth_management.ExpiringAuth
114121

115122

116123
Example:
@@ -154,7 +161,8 @@ Closing a driver will immediately shut down all connections in the pool.
154161

155162
.. autoclass:: neo4j.Driver()
156163
:members: session, query_bookmark_manager, encrypted, close,
157-
verify_connectivity, get_server_info
164+
verify_connectivity, get_server_info, verify_authentication,
165+
supports_session_auth, supports_multi_db
158166

159167
.. method:: execute_query(query, parameters_=None,routing_=neo4j.RoutingControl.WRITERS, database_=None, impersonated_user_=None, bookmark_manager_=self.query_bookmark_manager, result_transformer_=Result.to_eager_result, **kwargs)
160168

@@ -174,7 +182,7 @@ Closing a driver will immediately shut down all connections in the pool.
174182

175183
def execute_query(
176184
query_, parameters_, routing_, database_, impersonated_user_,
177-
bookmark_manager_, result_transformer_, **kwargs
185+
bookmark_manager_, auth_, result_transformer_, **kwargs
178186
):
179187
def work(tx):
180188
result = tx.run(query_, parameters_, **kwargs)
@@ -184,6 +192,7 @@ Closing a driver will immediately shut down all connections in the pool.
184192
database=database_,
185193
impersonated_user=impersonated_user_,
186194
bookmark_manager=bookmark_manager_,
195+
auth=auth_,
187196
) as session:
188197
if routing_ == RoutingControl.WRITERS:
189198
return session.execute_write(work)
@@ -263,6 +272,20 @@ Closing a driver will immediately shut down all connections in the pool.
263272

264273
See also the Session config :ref:`impersonated-user-ref`.
265274
:type impersonated_user_: typing.Optional[str]
275+
:param auth_:
276+
Authentication information to use for this query.
277+
278+
By default, the driver configuration is used.
279+
280+
**This is a preview** (see :ref:`filter-warnings-ref`).
281+
It might be changed without following the deprecation policy.
282+
See also
283+
https://github.com/neo4j/neo4j-python-driver/wiki/preview-features
284+
285+
See also the Session config :ref:`session-auth-ref`.
286+
:type auth_: typing.Union[
287+
typing.Tuple[typing.Any, typing.Any], neo4j.Auth, None
288+
]
266289
:param result_transformer_:
267290
A function that gets passed the :class:`neo4j.Result` object
268291
resulting from the query and converts it to a different type. The
@@ -273,7 +296,6 @@ Closing a driver will immediately shut down all connections in the pool.
273296
The transformer function must **not** return the
274297
:class:`neo4j.Result` itself.
275298

276-
277299
.. warning::
278300

279301
N.B. the driver might retry the underlying transaction so the
@@ -334,7 +356,7 @@ Closing a driver will immediately shut down all connections in the pool.
334356

335357
Defaults to the driver's :attr:`.query_bookmark_manager`.
336358

337-
Pass :const:`None` to disable causal consistency.
359+
Pass :data:`None` to disable causal consistency.
338360
:type bookmark_manager_:
339361
typing.Union[neo4j.BookmarkManager, neo4j.BookmarkManager,
340362
None]
@@ -348,7 +370,7 @@ Closing a driver will immediately shut down all connections in the pool.
348370
:returns: the result of the ``result_transformer``
349371
:rtype: T
350372

351-
**This is experimental.** (See :ref:`filter-warnings-ref`)
373+
**This is experimental** (see :ref:`filter-warnings-ref`).
352374
It might be changed or removed any time even without prior notice.
353375

354376
We are looking for feedback on this feature. Please let us know what
@@ -357,6 +379,9 @@ Closing a driver will immediately shut down all connections in the pool.
357379

358380
.. versionadded:: 5.5
359381

382+
.. versionchanged:: 5.8
383+
Added the ``auth_`` parameter.
384+
360385

361386
.. _driver-configuration-ref:
362387

@@ -433,7 +458,7 @@ Specify whether TCP keep-alive should be enabled.
433458
:Type: ``bool``
434459
:Default: ``True``
435460

436-
**This is experimental.** (See :ref:`filter-warnings-ref`)
461+
**This is experimental** (see :ref:`filter-warnings-ref`).
437462
It might be changed or removed any time even without prior notice.
438463

439464

@@ -784,6 +809,7 @@ Session
784809
.. automethod:: execute_write
785810

786811

812+
787813
Query
788814
=====
789815

@@ -804,6 +830,7 @@ To construct a :class:`neo4j.Session` use the :meth:`neo4j.Driver.session` metho
804830
+ :ref:`default-access-mode-ref`
805831
+ :ref:`fetch-size-ref`
806832
+ :ref:`bookmark-manager-ref`
833+
+ :ref:`session-auth-ref`
807834
+ :ref:`session-notifications-min-severity-ref`
808835
+ :ref:`session-notifications-disabled-categories-ref`
809836

@@ -816,7 +843,7 @@ Optional :class:`neo4j.Bookmarks`. Use this to causally chain sessions.
816843
See :meth:`Session.last_bookmarks` or :meth:`AsyncSession.last_bookmarks` for
817844
more information.
818845

819-
:Default: ``None``
846+
:Default: :data:`None`
820847

821848
.. deprecated:: 5.0
822849
Alternatively, an iterable of strings can be passed. This usage is
@@ -995,10 +1022,33 @@ See :class:`.BookmarkManager` for more information.
9951022

9961023
.. versionadded:: 5.0
9971024

998-
**This is experimental.** (See :ref:`filter-warnings-ref`)
1025+
**This is experimental** (see :ref:`filter-warnings-ref`).
9991026
It might be changed or removed any time even without prior notice.
10001027

10011028

1029+
.. _session-auth-ref:
1030+
1031+
``auth``
1032+
--------
1033+
Optional :class:`neo4j.Auth` or ``(user, password)``-tuple. Use this overwrite the
1034+
authentication information for the session.
1035+
This requires the server to support re-authentication on the protocol level. You can
1036+
check this by calling :meth:`.Driver.supports_session_auth` / :meth:`.AsyncDriver.supports_session_auth`.
1037+
1038+
It is not possible to overwrite the authentication information for the session with no authentication,
1039+
i.e., downgrade the authentication at session level.
1040+
Instead, you should create a driver with no authentication and upgrade the authentication at session level as needed.
1041+
1042+
**This is a preview** (see :ref:`filter-warnings-ref`).
1043+
It might be changed without following the deprecation policy.
1044+
See also https://github.com/neo4j/neo4j-python-driver/wiki/preview-features
1045+
1046+
:Type: :data:`None`, :class:`.Auth` or ``(user, password)``-tuple
1047+
:Default: :data:`None` - use the authentication information provided during driver creation.
1048+
1049+
.. versionadded:: 5.x
1050+
1051+
10021052
.. _session-notifications-min-severity-ref:
10031053

10041054
``notifications_min_severity``
@@ -1293,7 +1343,7 @@ Graph
12931343

12941344
.. automethod:: relationship_type
12951345

1296-
**This is experimental.** (See :ref:`filter-warnings-ref`)
1346+
**This is experimental** (see :ref:`filter-warnings-ref`).
12971347
It might be changed or removed any time even without prior notice.
12981348

12991349

@@ -1751,6 +1801,8 @@ Server-side errors
17511801

17521802
* :class:`neo4j.exceptions.TokenExpired`
17531803

1804+
* :class:`neo4j.exceptions.TokenExpiredRetryable`
1805+
17541806
* :class:`neo4j.exceptions.Forbidden`
17551807

17561808
* :class:`neo4j.exceptions.DatabaseError`
@@ -1786,6 +1838,9 @@ Server-side errors
17861838
.. autoexception:: neo4j.exceptions.TokenExpired()
17871839
:show-inheritance:
17881840

1841+
.. autoexception:: neo4j.exceptions.TokenExpiredRetryable()
1842+
:show-inheritance:
1843+
17891844
.. autoexception:: neo4j.exceptions.Forbidden()
17901845
:show-inheritance:
17911846

docs/source/async_api.rst

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ The :class:`neo4j.AsyncDriver` construction is done via a ``classmethod`` on the
4040
4141
await driver.close() # close the driver object
4242
43-
asyncio.run(main())
43+
asyncio.run(main())
4444
4545
4646
For basic authentication, ``auth`` can be a simple tuple, for example:
@@ -67,7 +67,7 @@ The :class:`neo4j.AsyncDriver` construction is done via a ``classmethod`` on the
6767
async with AsyncGraphDatabase.driver(uri, auth=auth) as driver:
6868
... # use the driver
6969
70-
asyncio.run(main())
70+
asyncio.run(main())
7171
7272
7373
@@ -118,6 +118,20 @@ Each supported scheme maps to a particular :class:`neo4j.AsyncDriver` subclass t
118118
See https://neo4j.com/docs/operations-manual/current/configuration/ports/ for Neo4j ports.
119119

120120

121+
.. _async-auth-ref:
122+
123+
Async Auth
124+
==========
125+
126+
Authentication works the same as in the synchronous driver.
127+
With the exception that when using AuthManagers, their asynchronous equivalents have to be used.
128+
129+
.. autoclass:: neo4j.auth_management.AsyncAuthManager
130+
:members:
131+
132+
.. autoclass:: neo4j.auth_management.AsyncAuthManagers
133+
:members:
134+
121135

122136
***********
123137
AsyncDriver
@@ -136,7 +150,8 @@ Closing a driver will immediately shut down all connections in the pool.
136150

137151
.. autoclass:: neo4j.AsyncDriver()
138152
:members: session, query_bookmark_manager, encrypted, close,
139-
verify_connectivity, get_server_info
153+
verify_connectivity, get_server_info, verify_authentication,
154+
supports_session_auth, supports_multi_db
140155

141156
.. method:: execute_query(query, parameters_=None, routing_=neo4j.RoutingControl.WRITERS, database_=None, impersonated_user_=None, bookmark_manager_=self.query_bookmark_manager, result_transformer_=AsyncResult.to_eager_result, **kwargs)
142157
:async:
@@ -157,7 +172,7 @@ Closing a driver will immediately shut down all connections in the pool.
157172

158173
async def execute_query(
159174
query_, parameters_, routing_, database_, impersonated_user_,
160-
bookmark_manager_, result_transformer_, **kwargs
175+
bookmark_manager_, auth_, result_transformer_, **kwargs
161176
):
162177
async def work(tx):
163178
result = await tx.run(query_, parameters_, **kwargs)
@@ -167,6 +182,7 @@ Closing a driver will immediately shut down all connections in the pool.
167182
database=database_,
168183
impersonated_user=impersonated_user_,
169184
bookmark_manager=bookmark_manager_,
185+
auth=auth_,
170186
) as session:
171187
if routing_ == RoutingControl.WRITERS:
172188
return await session.execute_write(work)
@@ -202,13 +218,14 @@ Closing a driver will immediately shut down all connections in the pool.
202218
async def example(driver: neo4j.AsyncDriver) -> int:
203219
"""Call all young people "My dear" and get their count."""
204220
record = await driver.execute_query(
205-
"MATCH (p:Person) WHERE p.age <= 15 "
221+
"MATCH (p:Person) WHERE p.age <= $age "
206222
"SET p.nickname = 'My dear' "
207223
"RETURN count(*)",
208224
# optional routing parameter, as write is default
209225
# routing_=neo4j.RoutingControl.WRITERS, # or just "w",
210226
database_="neo4j",
211227
result_transformer_=neo4j.AsyncResult.single,
228+
age=15,
212229
)
213230
assert record is not None # for typechecking and illustration
214231
count = record[0]
@@ -245,6 +262,20 @@ Closing a driver will immediately shut down all connections in the pool.
245262

246263
See also the Session config :ref:`impersonated-user-ref`.
247264
:type impersonated_user_: typing.Optional[str]
265+
:param auth_:
266+
Authentication information to use for this query.
267+
268+
By default, the driver configuration is used.
269+
270+
**This is a preview** (see :ref:`filter-warnings-ref`).
271+
It might be changed without following the deprecation policy.
272+
See also
273+
https://github.com/neo4j/neo4j-python-driver/wiki/preview-features
274+
275+
See also the Session config :ref:`session-auth-ref`.
276+
:type auth_: typing.Union[
277+
typing.Tuple[typing.Any, typing.Any], neo4j.Auth, None
278+
]
248279
:param result_transformer_:
249280
A function that gets passed the :class:`neo4j.AsyncResult` object
250281
resulting from the query and converts it to a different type. The
@@ -315,7 +346,7 @@ Closing a driver will immediately shut down all connections in the pool.
315346

316347
Defaults to the driver's :attr:`.query_bookmark_manager`.
317348

318-
Pass :const:`None` to disable causal consistency.
349+
Pass :data:`None` to disable causal consistency.
319350
:type bookmark_manager_:
320351
typing.Union[neo4j.AsyncBookmarkManager, neo4j.BookmarkManager,
321352
None]
@@ -329,7 +360,7 @@ Closing a driver will immediately shut down all connections in the pool.
329360
:returns: the result of the ``result_transformer``
330361
:rtype: T
331362

332-
**This is experimental.** (See :ref:`filter-warnings-ref`)
363+
**This is experimental** (see :ref:`filter-warnings-ref`).
333364
It might be changed or removed any time even without prior notice.
334365

335366
We are looking for feedback on this feature. Please let us know what
@@ -338,15 +369,21 @@ Closing a driver will immediately shut down all connections in the pool.
338369

339370
.. versionadded:: 5.5
340371

372+
.. versionchanged:: 5.8
373+
Added the ``auth_`` parameter.
374+
341375

342376
.. _async-driver-configuration-ref:
343377

344378
Async Driver Configuration
345379
==========================
346380

347381
:class:`neo4j.AsyncDriver` is configured exactly like :class:`neo4j.Driver`
348-
(see :ref:`driver-configuration-ref`). The only difference is that the async
349-
driver accepts an async custom resolver function:
382+
(see :ref:`driver-configuration-ref`). The only differences are that the async
383+
driver accepts
384+
385+
* a sync as well as an async custom resolver function (see :ref:`async-resolver-ref`)
386+
* as sync as well as an async auth token manager (see :class:`neo4j.AsyncAuthManager`).
350387

351388

352389
.. _async-resolver-ref:
@@ -622,7 +659,7 @@ See :class:`BookmarkManager` for more information.
622659
:Type: :data:`None`, :class:`BookmarkManager`, or :class:`AsyncBookmarkManager`
623660
:Default: :data:`None`
624661

625-
**This is experimental.** (See :ref:`filter-warnings-ref`)
662+
**This is experimental** (see :ref:`filter-warnings-ref`).
626663
It might be changed or removed any time even without prior notice.
627664

628665

requirements-dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ tomlkit~=0.11.6
1717

1818
# needed for running tests
1919
coverage[toml]>=5.5
20+
freezegun >= 1.2.2
2021
mock>=4.0.3
2122
pyarrow>=1.0.0
2223
pytest>=6.2.5

src/neo4j/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
deprecation_warn as _deprecation_warn,
5353
ExperimentalWarning,
5454
get_user_agent,
55+
PreviewWarning,
5556
version as __version__,
5657
)
5758
from ._sync.driver import (
@@ -140,11 +141,13 @@
140141
"NotificationFilter",
141142
"NotificationSeverity",
142143
"PoolConfig",
144+
"PreviewWarning",
143145
"Query",
144146
"READ_ACCESS",
145147
"Record",
146148
"Result",
147149
"ResultSummary",
150+
"RenewableAuth",
148151
"RoutingControl",
149152
"ServerInfo",
150153
"Session",

0 commit comments

Comments
 (0)