drf-impersonation 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- drf_impersonation-0.1.0/LICENSE +21 -0
- drf_impersonation-0.1.0/PKG-INFO +390 -0
- drf_impersonation-0.1.0/README.md +343 -0
- drf_impersonation-0.1.0/pyproject.toml +77 -0
- drf_impersonation-0.1.0/setup.cfg +4 -0
- drf_impersonation-0.1.0/src/drf_impersonation/__init__.py +1 -0
- drf_impersonation-0.1.0/src/drf_impersonation/apps.py +15 -0
- drf_impersonation-0.1.0/src/drf_impersonation/authentication.py +103 -0
- drf_impersonation-0.1.0/src/drf_impersonation/conf.py +21 -0
- drf_impersonation-0.1.0/src/drf_impersonation/openapi.py +71 -0
- drf_impersonation-0.1.0/src/drf_impersonation/py.typed +0 -0
- drf_impersonation-0.1.0/src/drf_impersonation/signals.py +5 -0
- drf_impersonation-0.1.0/src/drf_impersonation.egg-info/PKG-INFO +390 -0
- drf_impersonation-0.1.0/src/drf_impersonation.egg-info/SOURCES.txt +17 -0
- drf_impersonation-0.1.0/src/drf_impersonation.egg-info/dependency_links.txt +1 -0
- drf_impersonation-0.1.0/src/drf_impersonation.egg-info/requires.txt +15 -0
- drf_impersonation-0.1.0/src/drf_impersonation.egg-info/top_level.txt +1 -0
- drf_impersonation-0.1.0/tests/test_authentication.py +163 -0
- drf_impersonation-0.1.0/tests/test_openapi.py +87 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ahmad Ali
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: drf-impersonation
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Header-based user impersonation for Django REST Framework, works with any DRF authentication class
|
|
5
|
+
Author: Ahmad Ali
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ahmadjanan/drf-impersonation
|
|
8
|
+
Project-URL: Repository, https://github.com/ahmadjanan/drf-impersonation
|
|
9
|
+
Project-URL: Issues, https://github.com/ahmadjanan/drf-impersonation/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/ahmadjanan/drf-impersonation/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: django,djangorestframework,drf,impersonation,authentication,admin,support
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Environment :: Web Environment
|
|
15
|
+
Classifier: Framework :: Django
|
|
16
|
+
Classifier: Framework :: Django :: 4.2
|
|
17
|
+
Classifier: Framework :: Django :: 5.2
|
|
18
|
+
Classifier: Framework :: Django :: 6.0
|
|
19
|
+
Classifier: Framework :: Django :: 6.1
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
27
|
+
Classifier: Operating System :: OS Independent
|
|
28
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
29
|
+
Classifier: Topic :: Security
|
|
30
|
+
Classifier: Typing :: Typed
|
|
31
|
+
Requires-Python: >=3.10
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
License-File: LICENSE
|
|
34
|
+
Requires-Dist: Django>=4.2
|
|
35
|
+
Requires-Dist: djangorestframework>=3.14
|
|
36
|
+
Provides-Extra: openapi
|
|
37
|
+
Requires-Dist: drf-spectacular>=0.27; extra == "openapi"
|
|
38
|
+
Provides-Extra: test
|
|
39
|
+
Requires-Dist: pytest; extra == "test"
|
|
40
|
+
Requires-Dist: pytest-django; extra == "test"
|
|
41
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
42
|
+
Requires-Dist: drf-spectacular>=0.27; extra == "test"
|
|
43
|
+
Provides-Extra: lint
|
|
44
|
+
Requires-Dist: ruff; extra == "lint"
|
|
45
|
+
Requires-Dist: mypy; extra == "lint"
|
|
46
|
+
Dynamic: license-file
|
|
47
|
+
|
|
48
|
+
# drf-impersonation
|
|
49
|
+
|
|
50
|
+
[](https://pypi.org/project/drf-impersonation/)
|
|
51
|
+
[](https://pypi.org/project/drf-impersonation/)
|
|
52
|
+
[](https://pypi.org/project/drf-impersonation/)
|
|
53
|
+
[](https://github.com/ahmadjanan/drf-impersonation/actions/workflows/ci.yml)
|
|
54
|
+
[](https://github.com/ahmadjanan/drf-impersonation/blob/main/pyproject.toml)
|
|
55
|
+
[](https://peps.python.org/pep-0561/)
|
|
56
|
+
[](https://github.com/ahmadjanan/drf-impersonation/blob/main/LICENSE)
|
|
57
|
+
|
|
58
|
+
**Header-based user impersonation for Django REST Framework.** A permitted user sends one extra
|
|
59
|
+
header and the request runs as someone else. This works with every DRF authentication class:
|
|
60
|
+
Token, Session, JWT and OAuth.
|
|
61
|
+
|
|
62
|
+
```http
|
|
63
|
+
GET /api/orders/ HTTP/1.1
|
|
64
|
+
Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
|
|
65
|
+
X-Impersonate-User: 42
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The view sees `request.user` as user `42`. The real caller is still available on
|
|
69
|
+
`request.impersonator`.
|
|
70
|
+
|
|
71
|
+
## Contents
|
|
72
|
+
|
|
73
|
+
- [Why](#why)
|
|
74
|
+
- [Features](#features)
|
|
75
|
+
- [Requirements](#requirements)
|
|
76
|
+
- [Installation](#installation)
|
|
77
|
+
- [Quick start](#quick-start)
|
|
78
|
+
- [How it works](#how-it-works)
|
|
79
|
+
- [Behaviour](#behaviour)
|
|
80
|
+
- [Settings](#settings)
|
|
81
|
+
- [Customising the authentication class](#customising-the-authentication-class)
|
|
82
|
+
- [Signals](#signals)
|
|
83
|
+
- [OpenAPI](#openapi)
|
|
84
|
+
- [Type checking](#type-checking)
|
|
85
|
+
- [Recipes](#recipes)
|
|
86
|
+
- [Security considerations](#security-considerations)
|
|
87
|
+
- [Contributing](#contributing)
|
|
88
|
+
- [License](#license)
|
|
89
|
+
|
|
90
|
+
## Why
|
|
91
|
+
|
|
92
|
+
Support staff often need to see what a customer sees. Packages like `django-impersonate` and
|
|
93
|
+
`django-hijack` do this by changing the session, so they don't fit token-authenticated APIs that
|
|
94
|
+
have no session to change.
|
|
95
|
+
|
|
96
|
+
`drf-impersonation` runs inside the DRF authentication class. The base class authenticates the
|
|
97
|
+
real caller as normal. Then the mixin decides whether to swap in the target user. Impersonation
|
|
98
|
+
lasts for one request only, and the server keeps no state.
|
|
99
|
+
|
|
100
|
+
## Features
|
|
101
|
+
|
|
102
|
+
- **Any auth backend.** Mix it into Token, Session, SimpleJWT, OAuth or a custom class.
|
|
103
|
+
- **Stateless.** One header on one request. Nothing to start, stop or clean up.
|
|
104
|
+
- **Read-only by default.** While impersonating, write requests are refused unless you turn this off.
|
|
105
|
+
- **Safe defaults.** Only superusers can impersonate. Inactive users and privilege escalation are
|
|
106
|
+
refused.
|
|
107
|
+
- **No user enumeration.** A target that doesn't exist and a target you may not impersonate give
|
|
108
|
+
the same `404`.
|
|
109
|
+
- **Flexible lookups.** Find the target by primary key, UUID, email or any other field.
|
|
110
|
+
- **Audit hooks.** Each impersonated request is logged and sends a Django signal.
|
|
111
|
+
- **OpenAPI support.** The header appears in drf-spectacular schemas and Swagger UI.
|
|
112
|
+
- **Typed.** Ships `py.typed`.
|
|
113
|
+
|
|
114
|
+
## Requirements
|
|
115
|
+
|
|
116
|
+
| Dependency | Supported versions |
|
|
117
|
+
| --- | --- |
|
|
118
|
+
| Python | 3.10 – 3.15 |
|
|
119
|
+
| Django | 4.2, 5.2, 6.0, 6.1 |
|
|
120
|
+
| Django REST Framework | 3.14+ |
|
|
121
|
+
| drf-spectacular (optional) | 0.27+ |
|
|
122
|
+
|
|
123
|
+
CI runs the tests across the supported Python, Django and DRF versions.
|
|
124
|
+
|
|
125
|
+
## Installation
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
pip install drf-impersonation
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
To include OpenAPI support:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
pip install "drf-impersonation[openapi]"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Quick start
|
|
138
|
+
|
|
139
|
+
1. Add the app. This is only needed for [OpenAPI](#openapi) support, but adding it is harmless:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
INSTALLED_APPS = [
|
|
143
|
+
# ...
|
|
144
|
+
"rest_framework",
|
|
145
|
+
"drf_impersonation",
|
|
146
|
+
]
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
2. Use the ready-made token class:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
REST_FRAMEWORK = {
|
|
153
|
+
"DEFAULT_AUTHENTICATION_CLASSES": [
|
|
154
|
+
"drf_impersonation.authentication.ImpersonationTokenAuthentication",
|
|
155
|
+
],
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
For any other auth class, put `ImpersonationMixin` **before** it:
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
# myproject/authentication.py
|
|
163
|
+
from drf_impersonation.authentication import ImpersonationMixin
|
|
164
|
+
from rest_framework_simplejwt.authentication import JWTAuthentication
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class ImpersonationJWTAuthentication(ImpersonationMixin, JWTAuthentication):
|
|
168
|
+
pass
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
3. Send the header as a superuser:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
curl https://api.example.com/api/me/ \
|
|
175
|
+
-H "Authorization: Token <superuser token>" \
|
|
176
|
+
-H "X-Impersonate-User: 42"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
In a view, `request.user` is the target and `request.impersonator` is the real caller. When
|
|
180
|
+
there is no impersonation, `request.impersonator` is `None`:
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
@api_view(["GET"])
|
|
184
|
+
def me(request):
|
|
185
|
+
impersonator = request.impersonator
|
|
186
|
+
return Response(
|
|
187
|
+
{
|
|
188
|
+
"user": request.user.pk,
|
|
189
|
+
"impersonated_by": impersonator.pk if impersonator else None,
|
|
190
|
+
}
|
|
191
|
+
)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## How it works
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
Base auth class (Token / JWT / Session / ...)
|
|
198
|
+
│ no credentials ─────────────────────────▶ None. Bad credentials raise as usual.
|
|
199
|
+
▼ actor authenticated
|
|
200
|
+
X-Impersonate-User header?
|
|
201
|
+
│ no ─────────────────────────────────────▶ actor, request.impersonator = None
|
|
202
|
+
▼ yes
|
|
203
|
+
can_impersonate(actor)?
|
|
204
|
+
│ no ─────────────────────────────────────▶ header ignored, actor
|
|
205
|
+
▼ yes
|
|
206
|
+
READ_ONLY and method is not GET/HEAD/OPTIONS?
|
|
207
|
+
│ yes ────────────────────────────────────▶ 403 Impersonated requests are read-only
|
|
208
|
+
▼ no
|
|
209
|
+
target found and can_impersonate_target(actor, target)?
|
|
210
|
+
│ no ─────────────────────────────────────▶ 404 Impersonation target not found
|
|
211
|
+
▼ yes
|
|
212
|
+
on_impersonate() logs, user_impersonated signal ─▶ target, request.impersonator = actor
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
`request.auth` (the token, JWT payload and so on) still belongs to the **actor**.
|
|
216
|
+
|
|
217
|
+
## Behaviour
|
|
218
|
+
|
|
219
|
+
| Situation | Result |
|
|
220
|
+
| --- | --- |
|
|
221
|
+
| No header | No change. `request.impersonator` is `None`. |
|
|
222
|
+
| Caller may not impersonate | Header ignored. The request runs as the caller. |
|
|
223
|
+
| Write method (POST, PUT, PATCH, DELETE) while `READ_ONLY` is on | `403 Impersonated requests are read-only` |
|
|
224
|
+
| No user matches the header value | `404 Impersonation target not found` |
|
|
225
|
+
| Target is the caller, is inactive, or is a superuser while the caller is not | `404` |
|
|
226
|
+
| Success | `request.user` is the target, `request.impersonator` is the caller, a warning is logged and the signal is sent. |
|
|
227
|
+
|
|
228
|
+
The read-only check happens before the target lookup. A write request gets `403` whether or not
|
|
229
|
+
the target exists.
|
|
230
|
+
|
|
231
|
+
## Settings
|
|
232
|
+
|
|
233
|
+
All settings are optional. Put them in a `DRF_IMPERSONATION` dict:
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
DRF_IMPERSONATION = {
|
|
237
|
+
"READ_ONLY": True,
|
|
238
|
+
"SIGNALS": True,
|
|
239
|
+
"OPENAPI": True,
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
| Key | Default | Effect |
|
|
244
|
+
| --- | --- | --- |
|
|
245
|
+
| `READ_ONLY` | `True` | Refuse POST, PUT, PATCH and DELETE with `403` while impersonating. |
|
|
246
|
+
| `SIGNALS` | `True` | Send [`user_impersonated`](#signals) for each impersonated request. |
|
|
247
|
+
| `OPENAPI` | `True` | Document the header in [drf-spectacular](#openapi) schemas. |
|
|
248
|
+
|
|
249
|
+
Settings are read on every request, so `override_settings` works in tests. An unknown key raises
|
|
250
|
+
`ImproperlyConfigured`, which catches typos.
|
|
251
|
+
|
|
252
|
+
## Customising the authentication class
|
|
253
|
+
|
|
254
|
+
For behaviour that differs per class, subclass and override any of these:
|
|
255
|
+
|
|
256
|
+
| Attribute / method | Default | Purpose |
|
|
257
|
+
| --- | --- | --- |
|
|
258
|
+
| `impersonation_header` | `"X-Impersonate-User"` | Header that names the target. |
|
|
259
|
+
| `impersonation_lookup_fields` | `("pk",)` | Fields to match the header value against. All valid fields are tried together with OR, for example `("uuid", "email__iexact")`. |
|
|
260
|
+
| `can_impersonate(request, actor)` | `actor.is_superuser` | Whether the caller may impersonate anyone. |
|
|
261
|
+
| `can_impersonate_target(request, actor, target)` | Target is active, and no escalation to superuser | Whether the caller may impersonate this specific target. |
|
|
262
|
+
| `on_impersonate(request, actor, target)` | Logs a warning on the `drf_impersonation` logger | Runs for each impersonated request, before the signal. |
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
from drf_impersonation.authentication import ImpersonationTokenAuthentication
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class SupportImpersonation(ImpersonationTokenAuthentication):
|
|
269
|
+
impersonation_lookup_fields = ("pk", "email__iexact")
|
|
270
|
+
|
|
271
|
+
def can_impersonate(self, request, actor):
|
|
272
|
+
return actor.has_perm("accounts.impersonate_user")
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Signals
|
|
276
|
+
|
|
277
|
+
`drf_impersonation.signals.user_impersonated` is sent after each impersonated request is
|
|
278
|
+
authenticated. The sender is the authentication class. The keyword arguments are `request`,
|
|
279
|
+
`actor` and `target`.
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
from django.dispatch import receiver
|
|
283
|
+
from drf_impersonation.signals import user_impersonated
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
@receiver(user_impersonated)
|
|
287
|
+
def record_impersonation(sender, request, actor, target, **kwargs):
|
|
288
|
+
AuditEvent.objects.create(actor=actor, target=target, path=request.path)
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Set `"SIGNALS": False` to stop sending it.
|
|
292
|
+
|
|
293
|
+
## OpenAPI
|
|
294
|
+
|
|
295
|
+
With [drf-spectacular](https://drf-spectacular.readthedocs.io/) installed and `drf_impersonation`
|
|
296
|
+
in `INSTALLED_APPS`, every endpoint that uses an impersonation class documents the header. The
|
|
297
|
+
header is a security scheme called `impersonation`, and it is optional. Each operation lists the
|
|
298
|
+
base scheme alone and the base scheme together with the header:
|
|
299
|
+
|
|
300
|
+
```yaml
|
|
301
|
+
security:
|
|
302
|
+
- tokenAuth: []
|
|
303
|
+
- tokenAuth: []
|
|
304
|
+
impersonation: []
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Swagger UI then shows an `X-Impersonate-User` field under **Authorize**. If `READ_ONLY` is on,
|
|
308
|
+
the scheme description says that only safe methods are allowed.
|
|
309
|
+
|
|
310
|
+
The extension wraps whatever scheme drf-spectacular already uses for the base class, such as
|
|
311
|
+
Token, SimpleJWT or Session. If the base class has no scheme, nothing is added. Set
|
|
312
|
+
`"OPENAPI": False` to turn the extension off.
|
|
313
|
+
|
|
314
|
+
## Type checking
|
|
315
|
+
|
|
316
|
+
The package ships `py.typed`, so mypy and pyright check calls to its hooks. `request.impersonator`
|
|
317
|
+
is set at runtime on DRF's `Request`, which type checkers don't know about. Use
|
|
318
|
+
`getattr(request, "impersonator", None)` if your checker complains.
|
|
319
|
+
|
|
320
|
+
## Recipes
|
|
321
|
+
|
|
322
|
+
### Allow writes for some callers only
|
|
323
|
+
|
|
324
|
+
Turn off `READ_ONLY` and make the decision in a permission class:
|
|
325
|
+
|
|
326
|
+
```python
|
|
327
|
+
from rest_framework.permissions import SAFE_METHODS, BasePermission
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
class ImpersonationWritesForSuperusers(BasePermission):
|
|
331
|
+
def has_permission(self, request, view):
|
|
332
|
+
impersonator = getattr(request, "impersonator", None)
|
|
333
|
+
if impersonator is None or request.method in SAFE_METHODS:
|
|
334
|
+
return True
|
|
335
|
+
return impersonator.is_superuser
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Protect staff accounts from support agents
|
|
339
|
+
|
|
340
|
+
```python
|
|
341
|
+
def can_impersonate_target(self, request, actor, target):
|
|
342
|
+
if not super().can_impersonate_target(request, actor, target):
|
|
343
|
+
return False
|
|
344
|
+
return actor.is_superuser or not target.is_staff
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Route the default log
|
|
348
|
+
|
|
349
|
+
```python
|
|
350
|
+
LOGGING = {
|
|
351
|
+
"version": 1,
|
|
352
|
+
"handlers": {"audit": {"class": "logging.FileHandler", "filename": "impersonation.log"}},
|
|
353
|
+
"loggers": {"drf_impersonation": {"handlers": ["audit"], "level": "WARNING"}},
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Allow the header through CORS
|
|
358
|
+
|
|
359
|
+
Browsers only send custom headers that the server allows. With `django-cors-headers`:
|
|
360
|
+
|
|
361
|
+
```python
|
|
362
|
+
from corsheaders.defaults import default_headers
|
|
363
|
+
|
|
364
|
+
CORS_ALLOW_HEADERS = (*default_headers, "x-impersonate-user")
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## Security considerations
|
|
368
|
+
|
|
369
|
+
- **Keep `can_impersonate` narrow.** Anyone it allows can act as almost any user.
|
|
370
|
+
- **Leave `READ_ONLY` on** unless support staff really need to make changes on a user's behalf.
|
|
371
|
+
- **Keep the escalation check.** If you override `can_impersonate_target`, call `super()` so that
|
|
372
|
+
inactive users and superusers stay protected.
|
|
373
|
+
- **Keep a durable audit record.** The default log line is a minimum. Use the signal to store
|
|
374
|
+
each event.
|
|
375
|
+
- **`request.auth` is the actor's.** Token scopes and claims belong to the real caller, not the
|
|
376
|
+
target.
|
|
377
|
+
|
|
378
|
+
To report a vulnerability, see [SECURITY.md](https://github.com/ahmadjanan/drf-impersonation/blob/main/SECURITY.md).
|
|
379
|
+
|
|
380
|
+
## Contributing
|
|
381
|
+
|
|
382
|
+
Bug reports and pull requests are welcome. See
|
|
383
|
+
[CONTRIBUTING.md](https://github.com/ahmadjanan/drf-impersonation/blob/main/CONTRIBUTING.md) for
|
|
384
|
+
the development setup and the release process, and
|
|
385
|
+
[CHANGELOG.md](https://github.com/ahmadjanan/drf-impersonation/blob/main/CHANGELOG.md) for the
|
|
386
|
+
history.
|
|
387
|
+
|
|
388
|
+
## License
|
|
389
|
+
|
|
390
|
+
[MIT](https://github.com/ahmadjanan/drf-impersonation/blob/main/LICENSE) © Ahmad Ali
|