ccp4i2-api 0.3.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.
- ccp4i2_api-0.3.0/.gitignore +12 -0
- ccp4i2_api-0.3.0/PKG-INFO +98 -0
- ccp4i2_api-0.3.0/README.md +79 -0
- ccp4i2_api-0.3.0/ccp4i2_api/__init__.py +13 -0
- ccp4i2_api-0.3.0/ccp4i2_api/drf.py +58 -0
- ccp4i2_api-0.3.0/ccp4i2_api/exceptions.py +18 -0
- ccp4i2_api-0.3.0/ccp4i2_api/middleware/__init__.py +23 -0
- ccp4i2_api-0.3.0/ccp4i2_api/middleware/azure_ad.py +413 -0
- ccp4i2_api-0.3.0/ccp4i2_api/middleware/base.py +69 -0
- ccp4i2_api-0.3.0/ccp4i2_api/middleware/dev.py +48 -0
- ccp4i2_api-0.3.0/ccp4i2_api/middleware/dev_admin.py +65 -0
- ccp4i2_api-0.3.0/ccp4i2_api/middleware/local_session.py +65 -0
- ccp4i2_api-0.3.0/pyproject.toml +45 -0
- ccp4i2_api-0.3.0/tests/python/test_azure_ad.py +402 -0
- ccp4i2_api-0.3.0/tests/python/test_dev_admin.py +68 -0
- ccp4i2_api-0.3.0/tests/python/test_local_session.py +264 -0
- ccp4i2_api-0.3.0/tests/python/test_settings.py +25 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ccp4i2-api
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Shared API contract (auth, api-fetch, request/response types) for CCP4i2 and consumers
|
|
5
|
+
Project-URL: Homepage, https://github.com/ccp4/ccp4i2/tree/django-sliced/packages/ccp4i2-api
|
|
6
|
+
Project-URL: Repository, https://github.com/ccp4/ccp4i2
|
|
7
|
+
Project-URL: Issues, https://github.com/ccp4/ccp4i2/issues
|
|
8
|
+
Author: CCP4i2 contributors
|
|
9
|
+
License: LGPL-3.0-or-later
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Requires-Dist: certifi>=2024.0.0
|
|
12
|
+
Requires-Dist: django<5.0,>=4.2
|
|
13
|
+
Requires-Dist: djangorestframework>=3.14
|
|
14
|
+
Requires-Dist: pyjwt[crypto]>=2.10
|
|
15
|
+
Provides-Extra: test
|
|
16
|
+
Requires-Dist: pytest; extra == 'test'
|
|
17
|
+
Requires-Dist: pytest-django; extra == 'test'
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# `@ccp4/ccp4i2-api` / `ccp4i2-api`
|
|
21
|
+
|
|
22
|
+
Shared API contract for CCP4i2 and consumers (CCP4i2 Compounds, third-party
|
|
23
|
+
integrators) — auth handshake, api-fetch helpers, and request/response
|
|
24
|
+
types. One package, two language artifacts: TypeScript on the client side
|
|
25
|
+
(browser, Electron) and Python on the server side (Django middleware, DRF
|
|
26
|
+
authentication). Both halves agree on the canonical bearer-token format,
|
|
27
|
+
401 response shape, and the typed payloads carried over the authenticated
|
|
28
|
+
channel.
|
|
29
|
+
|
|
30
|
+
## Status
|
|
31
|
+
|
|
32
|
+
**Draft v0 — published to npm + PyPI from the in-tree workspace.** Source
|
|
33
|
+
of truth lives at
|
|
34
|
+
[`packages/ccp4i2-api/`](https://github.com/ccp4/ccp4i2/tree/django-sliced/packages/ccp4i2-api)
|
|
35
|
+
inside the `ccp4/ccp4i2` monorepo on the `django-sliced` branch and stays
|
|
36
|
+
there. The companion `ccp4/ccp4i2-api` GitHub repo (created for the npm
|
|
37
|
+
namespace and external visibility) does not host source.
|
|
38
|
+
|
|
39
|
+
Versions `0.1.0`–`0.3.0` were published under the previous name
|
|
40
|
+
`@ccp4/ccp4i2-auth` / `ccp4i2-auth`; the package was renamed at `0.3.0`
|
|
41
|
+
because its scope had grown beyond auth to cover the broader API contract.
|
|
42
|
+
The old name is unpublished/yanked; consumers should depend on
|
|
43
|
+
`@ccp4/ccp4i2-api` and `ccp4i2-api` from `0.3.0` onward.
|
|
44
|
+
|
|
45
|
+
Versioning follows semver from `0.x.y` onwards. The v0 contract is
|
|
46
|
+
documented in
|
|
47
|
+
[`docs/CCP4I2_SERVICE_CONTRACT.md`](https://github.com/ccp4/ccp4i2/blob/django-sliced/docs/CCP4I2_SERVICE_CONTRACT.md);
|
|
48
|
+
field stability promises take effect from this version.
|
|
49
|
+
|
|
50
|
+
## Layout
|
|
51
|
+
|
|
52
|
+
| Path | Purpose |
|
|
53
|
+
|---|---|
|
|
54
|
+
| `src/` | TypeScript source. Built to `lib/` via `npm run build`. |
|
|
55
|
+
| `lib/` | Built TypeScript output. Generated; gitignored. |
|
|
56
|
+
| `dist/` | Python distribution output (`python -m build`). Generated; gitignored. Kept distinct from `lib/` so `twine upload dist/*` doesn't accidentally pick up TypeScript artefacts. |
|
|
57
|
+
| `ccp4i2_api/` | Python source. Installed editable via `pip install -e .`. |
|
|
58
|
+
| `tests/js/` | TypeScript tests (vitest, when added). |
|
|
59
|
+
| `tests/python/` | Python tests (pytest, when added). |
|
|
60
|
+
|
|
61
|
+
## Consumer wiring
|
|
62
|
+
|
|
63
|
+
In-monorepo consumers can reference this package by local path for fast
|
|
64
|
+
iteration; out-of-monorepo consumers pull the published versions.
|
|
65
|
+
|
|
66
|
+
**TypeScript** — in-monorepo (`client/package.json`):
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
"dependencies": {
|
|
70
|
+
"@ccp4/ccp4i2-api": "file:../packages/ccp4i2-api"
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
(Path depth varies by consumer location.) Out-of-monorepo consumers use the
|
|
75
|
+
published range, e.g. `"@ccp4/ccp4i2-api": "^0.3.0"`.
|
|
76
|
+
|
|
77
|
+
**Python** — in-monorepo (`Docker/server/Dockerfile`, local dev setup):
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pip install -e packages/ccp4i2-api/
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Out-of-monorepo consumers `pip install ccp4i2-api>=0.3`.
|
|
84
|
+
|
|
85
|
+
## Development
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# TypeScript
|
|
89
|
+
cd packages/ccp4i2-api
|
|
90
|
+
npm install
|
|
91
|
+
npm run build # produces lib/
|
|
92
|
+
npm run watch # rebuilds on change
|
|
93
|
+
|
|
94
|
+
# Python
|
|
95
|
+
cd packages/ccp4i2-api
|
|
96
|
+
ccp4-python -m pip install -e .
|
|
97
|
+
ccp4-python -c "import ccp4i2_api; print(ccp4i2_api.__version__)"
|
|
98
|
+
```
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# `@ccp4/ccp4i2-api` / `ccp4i2-api`
|
|
2
|
+
|
|
3
|
+
Shared API contract for CCP4i2 and consumers (CCP4i2 Compounds, third-party
|
|
4
|
+
integrators) — auth handshake, api-fetch helpers, and request/response
|
|
5
|
+
types. One package, two language artifacts: TypeScript on the client side
|
|
6
|
+
(browser, Electron) and Python on the server side (Django middleware, DRF
|
|
7
|
+
authentication). Both halves agree on the canonical bearer-token format,
|
|
8
|
+
401 response shape, and the typed payloads carried over the authenticated
|
|
9
|
+
channel.
|
|
10
|
+
|
|
11
|
+
## Status
|
|
12
|
+
|
|
13
|
+
**Draft v0 — published to npm + PyPI from the in-tree workspace.** Source
|
|
14
|
+
of truth lives at
|
|
15
|
+
[`packages/ccp4i2-api/`](https://github.com/ccp4/ccp4i2/tree/django-sliced/packages/ccp4i2-api)
|
|
16
|
+
inside the `ccp4/ccp4i2` monorepo on the `django-sliced` branch and stays
|
|
17
|
+
there. The companion `ccp4/ccp4i2-api` GitHub repo (created for the npm
|
|
18
|
+
namespace and external visibility) does not host source.
|
|
19
|
+
|
|
20
|
+
Versions `0.1.0`–`0.3.0` were published under the previous name
|
|
21
|
+
`@ccp4/ccp4i2-auth` / `ccp4i2-auth`; the package was renamed at `0.3.0`
|
|
22
|
+
because its scope had grown beyond auth to cover the broader API contract.
|
|
23
|
+
The old name is unpublished/yanked; consumers should depend on
|
|
24
|
+
`@ccp4/ccp4i2-api` and `ccp4i2-api` from `0.3.0` onward.
|
|
25
|
+
|
|
26
|
+
Versioning follows semver from `0.x.y` onwards. The v0 contract is
|
|
27
|
+
documented in
|
|
28
|
+
[`docs/CCP4I2_SERVICE_CONTRACT.md`](https://github.com/ccp4/ccp4i2/blob/django-sliced/docs/CCP4I2_SERVICE_CONTRACT.md);
|
|
29
|
+
field stability promises take effect from this version.
|
|
30
|
+
|
|
31
|
+
## Layout
|
|
32
|
+
|
|
33
|
+
| Path | Purpose |
|
|
34
|
+
|---|---|
|
|
35
|
+
| `src/` | TypeScript source. Built to `lib/` via `npm run build`. |
|
|
36
|
+
| `lib/` | Built TypeScript output. Generated; gitignored. |
|
|
37
|
+
| `dist/` | Python distribution output (`python -m build`). Generated; gitignored. Kept distinct from `lib/` so `twine upload dist/*` doesn't accidentally pick up TypeScript artefacts. |
|
|
38
|
+
| `ccp4i2_api/` | Python source. Installed editable via `pip install -e .`. |
|
|
39
|
+
| `tests/js/` | TypeScript tests (vitest, when added). |
|
|
40
|
+
| `tests/python/` | Python tests (pytest, when added). |
|
|
41
|
+
|
|
42
|
+
## Consumer wiring
|
|
43
|
+
|
|
44
|
+
In-monorepo consumers can reference this package by local path for fast
|
|
45
|
+
iteration; out-of-monorepo consumers pull the published versions.
|
|
46
|
+
|
|
47
|
+
**TypeScript** — in-monorepo (`client/package.json`):
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@ccp4/ccp4i2-api": "file:../packages/ccp4i2-api"
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
(Path depth varies by consumer location.) Out-of-monorepo consumers use the
|
|
56
|
+
published range, e.g. `"@ccp4/ccp4i2-api": "^0.3.0"`.
|
|
57
|
+
|
|
58
|
+
**Python** — in-monorepo (`Docker/server/Dockerfile`, local dev setup):
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install -e packages/ccp4i2-api/
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Out-of-monorepo consumers `pip install ccp4i2-api>=0.3`.
|
|
65
|
+
|
|
66
|
+
## Development
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# TypeScript
|
|
70
|
+
cd packages/ccp4i2-api
|
|
71
|
+
npm install
|
|
72
|
+
npm run build # produces lib/
|
|
73
|
+
npm run watch # rebuilds on change
|
|
74
|
+
|
|
75
|
+
# Python
|
|
76
|
+
cd packages/ccp4i2-api
|
|
77
|
+
ccp4-python -m pip install -e .
|
|
78
|
+
ccp4-python -c "import ccp4i2_api; print(ccp4i2_api.__version__)"
|
|
79
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Shared API contract for CCP4i2 and consumers.
|
|
2
|
+
|
|
3
|
+
This package is the Python half of a bilingual API library; the TypeScript
|
|
4
|
+
half is published as ``@ccp4/ccp4i2-api``. Both halves implement the same
|
|
5
|
+
canonical bearer-token format, 401 response shape, and typed payloads
|
|
6
|
+
carried over the authenticated channel.
|
|
7
|
+
|
|
8
|
+
See the package README for current status and
|
|
9
|
+
``docs/CCP4I2_SERVICE_CONTRACT.md`` in the ccp4i2 monorepo for the
|
|
10
|
+
contract specification.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
__version__ = "0.3.0"
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""DRF authentication classes for CCP4i2.
|
|
2
|
+
|
|
3
|
+
These classes work in tandem with the middleware in
|
|
4
|
+
``ccp4i2_api.middleware`` — the middleware validates the bearer token
|
|
5
|
+
and sets ``request.user`` plus the ``REQUEST_FLAG_ATTR`` trust flag; the
|
|
6
|
+
DRF auth class checks the flag and surfaces ``request.user`` to DRF's
|
|
7
|
+
``IsAuthenticated`` permission.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from rest_framework.authentication import BaseAuthentication
|
|
11
|
+
|
|
12
|
+
from .middleware.base import REQUEST_FLAG_ATTR
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AzureADAuthentication(BaseAuthentication):
|
|
16
|
+
"""
|
|
17
|
+
DRF authentication class that uses the user set by AzureADAuthMiddleware.
|
|
18
|
+
|
|
19
|
+
This allows DRF's IsAuthenticated permission to work with our middleware.
|
|
20
|
+
The middleware does the actual JWT validation; this class just passes
|
|
21
|
+
the authenticated user to DRF.
|
|
22
|
+
|
|
23
|
+
In dev/Electron mode (CCP4I2_REQUIRE_AUTH not set), the middleware
|
|
24
|
+
auto-assigns a dev_admin user, which this class also recognizes.
|
|
25
|
+
|
|
26
|
+
Security: Only trusts users when our middleware has explicitly processed
|
|
27
|
+
the request (marked by ``REQUEST_FLAG_ATTR`` attribute). This prevents
|
|
28
|
+
spoofing attacks where a request might have ``request.user`` set by
|
|
29
|
+
some other means.
|
|
30
|
+
|
|
31
|
+
Note: this class is bound to the trust flag, not to the AzureAD chain
|
|
32
|
+
specifically; it works equally for any middleware that inherits from
|
|
33
|
+
``BaseAuthMiddleware`` (e.g., LocalSessionAuthMiddleware in desktop
|
|
34
|
+
mode), because they all set the same flag.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def authenticate(self, request):
|
|
38
|
+
"""
|
|
39
|
+
Return the user if already authenticated by middleware, None otherwise.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Tuple of (user, None) if authenticated, None if not.
|
|
43
|
+
"""
|
|
44
|
+
# Check if middleware already validated and set user
|
|
45
|
+
# The middleware sets these attributes on the underlying Django request
|
|
46
|
+
django_request = getattr(request, '_request', request)
|
|
47
|
+
|
|
48
|
+
# Security check: only trust users set by our middleware
|
|
49
|
+
# This prevents spoofing where request.user might be set elsewhere
|
|
50
|
+
if not getattr(django_request, REQUEST_FLAG_ATTR, False):
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
# Middleware ran - trust the user it set
|
|
54
|
+
user = getattr(django_request, 'user', None)
|
|
55
|
+
if user and user.is_authenticated and not user.is_anonymous:
|
|
56
|
+
return (user, None)
|
|
57
|
+
|
|
58
|
+
return None
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Exceptions used by the shared auth middleware contract."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AuthenticationFailed(Exception):
|
|
5
|
+
"""Raised by ``BaseAuthMiddleware.authenticate()`` to signal a 401.
|
|
6
|
+
|
|
7
|
+
The exception's first argument is the message returned in the canonical
|
|
8
|
+
401 response body (``{"success": false, "error": "..."}``).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AuthorizationFailed(Exception):
|
|
13
|
+
"""Raised by ``BaseAuthMiddleware.authenticate()`` to signal a 403.
|
|
14
|
+
|
|
15
|
+
Use when the request is *authenticated* (we know who is calling) but
|
|
16
|
+
*not authorized* (e.g., not a member of an allowed group). The first
|
|
17
|
+
argument is the message returned in the canonical 403 response body.
|
|
18
|
+
"""
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Django middleware shipped by the shared auth package.
|
|
2
|
+
|
|
3
|
+
Each module in this package implements one auth scheme; consumers select
|
|
4
|
+
which to enable via Django ``MIDDLEWARE`` settings. All non-dev schemes
|
|
5
|
+
inherit from ``BaseAuthMiddleware`` (or will, after the AzureAD refactor),
|
|
6
|
+
which defines the canonical 401 response shape and the ``REQUEST_FLAG_ATTR``
|
|
7
|
+
trust signal.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .azure_ad import AzureADAuthMiddleware
|
|
11
|
+
from .base import REQUEST_FLAG_ATTR, BaseAuthMiddleware
|
|
12
|
+
from .dev import DevAuthMiddleware
|
|
13
|
+
from .dev_admin import DevAdminMiddleware
|
|
14
|
+
from .local_session import LocalSessionAuthMiddleware
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"AzureADAuthMiddleware",
|
|
18
|
+
"BaseAuthMiddleware",
|
|
19
|
+
"DevAdminMiddleware",
|
|
20
|
+
"DevAuthMiddleware",
|
|
21
|
+
"LocalSessionAuthMiddleware",
|
|
22
|
+
"REQUEST_FLAG_ATTR",
|
|
23
|
+
]
|