easyapi-django 1.2.2__tar.gz → 1.3.1__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.
- {easyapi_django-1.2.2/easyapi_django.egg-info → easyapi_django-1.3.1}/PKG-INFO +3 -2
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/README.md +2 -1
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/base.py +24 -8
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/mcp/protocol.py +9 -8
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/sensitive.py +12 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1/easyapi_django.egg-info}/PKG-INFO +3 -2
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/pyproject.toml +1 -1
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/LICENSE +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/MANIFEST.in +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/__init__.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/auth.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/bearer.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/blocking.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/calc.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/calc_resource.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/client_ip.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/constants.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/core/__init__.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/core/endpoint.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/core/resource.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/dates.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/exception.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/filters.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/gen/__init__.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/gen/cli.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/gen/config.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/gen/generator.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/gen/inspector/__init__.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/gen/inspector/base.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/gen/inspector/mysql.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/gen/inspector/postgres.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/gen/templates/README.md.j2 +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/gen/templates/asgi.py.j2 +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/gen/templates/env.example.j2 +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/gen/templates/gitignore.j2 +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/gen/templates/manage.py.j2 +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/gen/templates/module/models.py.j2 +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/gen/templates/module/resources.py.j2 +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/gen/templates/requirements.txt.j2 +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/gen/templates/router/endpoints.py.j2 +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/gen/templates/router/urls.py.j2 +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/gen/templates/run.sh.j2 +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/gen/templates/settings/env.py.j2 +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/gen/templates/settings/settings.py.j2 +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/helpers.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/management/__init__.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/management/commands/__init__.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/management/commands/mcp_serve.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/mcp/__init__.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/mcp/schema.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/mcp/server.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/mcp/testing.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/mcp/tool.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/mcp/tools.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/middleware.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/__init__.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/_common.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/apps.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/clients.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/codes.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/constants.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/devices.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/discovery.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/migrations/0001_initial.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/migrations/__init__.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/models.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/resolver.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/resources/__init__.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/resources/authorize.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/resources/device.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/resources/device_authorization.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/resources/register.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/resources/revoke.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/resources/token.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/routes.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/session.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/settings_helper.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/templates/zeromcp_oauth/consent.html +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/templates/zeromcp_oauth/device.html +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/tokens.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/openapi.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/orm/__init__.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/rate_limit.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/redis_config.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/routes.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/schemas.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/security.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/serializer.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/settings_helper.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/tenant/__init__.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/tenant/db_router.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/tenant/registry.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/tenant/tenant.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/util.py +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi_django.egg-info/SOURCES.txt +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi_django.egg-info/dependency_links.txt +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi_django.egg-info/entry_points.txt +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi_django.egg-info/requires.txt +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi_django.egg-info/top_level.txt +0 -0
- {easyapi_django-1.2.2 → easyapi_django-1.3.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: easyapi-django
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.1
|
|
4
4
|
Summary: Zero-effort MCP server (and REST API) generator from Django models.
|
|
5
5
|
Author-email: Stamatios Stamou Jr <bushier.outsets.0c@icloud.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -235,7 +235,8 @@ Defaults are demo-friendly. Production deployments should:
|
|
|
235
235
|
- **Tune session TTLs.** `SESSION_TTL` (cookies, default 1800s) and `API_SESSION_TTL` (api-key cache, default 300s) both slide on use. Pick numbers that match your security/UX trade-off.
|
|
236
236
|
- **Never ship `DEBUG = True`.** Under `DEBUG`, edge security (scanner blocking, 4xx flood detection), rate limiting, credential-path defense and abuse blocking are all bypassed for dev ergonomics. The framework logs a loud startup warning when the bypass is active. To decouple from `DEBUG` — e.g. a staging box mirroring production — set `MCP = {'SECURITY_BYPASS': False}` to force protections on (or `True` to bypass without `DEBUG`).
|
|
237
237
|
- **Trust your proxies explicitly.** IP-based rate limits and abuse blocking key on the client IP. `X-Forwarded-For` / `X-Real-IP` are only honored when `REMOTE_ADDR` is in `TRUSTED_PROXIES = ['10.0.0.0/8', ...]`; otherwise `REMOTE_ADDR` wins. Leave it unset behind a proxy and every request looks like it comes from the proxy.
|
|
238
|
-
- **Bound the metrics surface.** The `/metrics` endpoint queries any model in the registry by default. Set `METRICS_MODELS = ['app_Model', ...]` to allowlist which models are queryable. References to credential-named columns (in filters, groups, order or calc) are always rejected
|
|
238
|
+
- **Bound the metrics surface.** The `/metrics` endpoint queries any model in the registry by default. Set `METRICS_MODELS = ['app_Model', ...]` to allowlist which models are queryable. References to credential-named columns (in filters, groups, order or calc) are always rejected — the per-resource escape hatch does not reach `/metrics`.
|
|
239
|
+
- **Keep the escape hatch narrow.** Columns whose names look like credentials (`password`, `api_key`, `token`, `otp`, `secret`, …) are masked as `'*********'` on every response, on top of whatever `sensitive_fields` lists. When the heuristic catches a false positive, opt it out per surface: `edit_allow_sensitive_fields = ['token_count']` returns it verbatim on `GET /resource/<id>/` while LIST still masks it. `list_allow_sensitive_fields` does the same for LIST, where every row carries the value — reach for the detail-only form first.
|
|
239
240
|
- **OAuth clients are public by default.** MCP clients (Claude.ai, ChatGPT, ...) authenticate with PKCE — list them under `MCP['OAUTH_CLIENTS']` with no `client_secret`. A client becomes confidential only with an explicit secret; `/register` never hands out a confidential secret (provision those out of band).
|
|
240
241
|
|
|
241
242
|
---
|
|
@@ -191,7 +191,8 @@ Defaults are demo-friendly. Production deployments should:
|
|
|
191
191
|
- **Tune session TTLs.** `SESSION_TTL` (cookies, default 1800s) and `API_SESSION_TTL` (api-key cache, default 300s) both slide on use. Pick numbers that match your security/UX trade-off.
|
|
192
192
|
- **Never ship `DEBUG = True`.** Under `DEBUG`, edge security (scanner blocking, 4xx flood detection), rate limiting, credential-path defense and abuse blocking are all bypassed for dev ergonomics. The framework logs a loud startup warning when the bypass is active. To decouple from `DEBUG` — e.g. a staging box mirroring production — set `MCP = {'SECURITY_BYPASS': False}` to force protections on (or `True` to bypass without `DEBUG`).
|
|
193
193
|
- **Trust your proxies explicitly.** IP-based rate limits and abuse blocking key on the client IP. `X-Forwarded-For` / `X-Real-IP` are only honored when `REMOTE_ADDR` is in `TRUSTED_PROXIES = ['10.0.0.0/8', ...]`; otherwise `REMOTE_ADDR` wins. Leave it unset behind a proxy and every request looks like it comes from the proxy.
|
|
194
|
-
- **Bound the metrics surface.** The `/metrics` endpoint queries any model in the registry by default. Set `METRICS_MODELS = ['app_Model', ...]` to allowlist which models are queryable. References to credential-named columns (in filters, groups, order or calc) are always rejected
|
|
194
|
+
- **Bound the metrics surface.** The `/metrics` endpoint queries any model in the registry by default. Set `METRICS_MODELS = ['app_Model', ...]` to allowlist which models are queryable. References to credential-named columns (in filters, groups, order or calc) are always rejected — the per-resource escape hatch does not reach `/metrics`.
|
|
195
|
+
- **Keep the escape hatch narrow.** Columns whose names look like credentials (`password`, `api_key`, `token`, `otp`, `secret`, …) are masked as `'*********'` on every response, on top of whatever `sensitive_fields` lists. When the heuristic catches a false positive, opt it out per surface: `edit_allow_sensitive_fields = ['token_count']` returns it verbatim on `GET /resource/<id>/` while LIST still masks it. `list_allow_sensitive_fields` does the same for LIST, where every row carries the value — reach for the detail-only form first.
|
|
195
196
|
- **OAuth clients are public by default.** MCP clients (Claude.ai, ChatGPT, ...) authenticate with PKCE — list them under `MCP['OAUTH_CLIENTS']` with no `client_secret`. A client becomes confidential only with an explicit secret; `/register` never hands out a confidential secret (provision those out of band).
|
|
196
197
|
|
|
197
198
|
---
|
|
@@ -40,7 +40,7 @@ from .helpers import (
|
|
|
40
40
|
search_regex,
|
|
41
41
|
)
|
|
42
42
|
from .schemas import is_pydantic_model, serialize_output, validate_input
|
|
43
|
-
from .sensitive import
|
|
43
|
+
from .sensitive import is_sensitive
|
|
44
44
|
from .serializer import CustomJSONEncoder
|
|
45
45
|
from .util import validate_session_key
|
|
46
46
|
from .rate_limit import RateLimiter
|
|
@@ -186,13 +186,21 @@ class BaseResource(CoreResource):
|
|
|
186
186
|
description = None
|
|
187
187
|
|
|
188
188
|
# Field names that should never leave the server. Listed names are
|
|
189
|
-
#
|
|
190
|
-
#
|
|
191
|
-
#
|
|
192
|
-
#
|
|
193
|
-
#
|
|
189
|
+
# masked as ``*********`` on serialization. The framework already
|
|
190
|
+
# filters a baseline of obviously sensitive names (``password``,
|
|
191
|
+
# ``api_key``, ``token``, ``otp``, ``secret``, …) — use this for
|
|
192
|
+
# project-specific columns (audit fields, internal flags,
|
|
193
|
+
# server-assigned IDs).
|
|
194
194
|
sensitive_fields = None
|
|
195
195
|
|
|
196
|
+
# Escape hatch: names here are serialized verbatim, overriding both
|
|
197
|
+
# ``sensitive_fields`` and the name heuristic. Per surface, because a
|
|
198
|
+
# column safe to return on a single-object read is rarely safe to
|
|
199
|
+
# return on every row of an unbounded list. ``edit_*`` governs the
|
|
200
|
+
# detail payload (``_get_obj``), not the write path.
|
|
201
|
+
list_allow_sensitive_fields = None
|
|
202
|
+
edit_allow_sensitive_fields = None
|
|
203
|
+
|
|
196
204
|
account_db = 'default'
|
|
197
205
|
cache = False
|
|
198
206
|
# Default TTL: settings.CACHE_TTL when set, else 120s.
|
|
@@ -307,6 +315,12 @@ class BaseResource(CoreResource):
|
|
|
307
315
|
# Normalise once so downstream serialisation can use a set
|
|
308
316
|
# without recomputing per-row.
|
|
309
317
|
self.sensitive_fields = set(self.sensitive_fields) if self.sensitive_fields else set()
|
|
318
|
+
self.list_allow_sensitive_fields = (
|
|
319
|
+
set(self.list_allow_sensitive_fields) if self.list_allow_sensitive_fields else set()
|
|
320
|
+
)
|
|
321
|
+
self.edit_allow_sensitive_fields = (
|
|
322
|
+
set(self.edit_allow_sensitive_fields) if self.edit_allow_sensitive_fields else set()
|
|
323
|
+
)
|
|
310
324
|
self.update_fields = list(self.update_fields) if self.update_fields else []
|
|
311
325
|
self.create_fields = list(self.create_fields) if self.create_fields else []
|
|
312
326
|
self.routes = list(self.routes) if self.routes else []
|
|
@@ -762,6 +776,8 @@ class BaseResource(CoreResource):
|
|
|
762
776
|
if is_pydantic_model(schema):
|
|
763
777
|
self.body = validate_input(schema, self.body)
|
|
764
778
|
await self.hydrate(self.body)
|
|
779
|
+
elif request.content_type == 'multipart/form-data':
|
|
780
|
+
self.body = None
|
|
765
781
|
else:
|
|
766
782
|
raise HTTPException(400, 'Invalid body')
|
|
767
783
|
else:
|
|
@@ -1325,7 +1341,7 @@ class BaseResource(CoreResource):
|
|
|
1325
1341
|
if field in self.list_exclude_fields:
|
|
1326
1342
|
continue
|
|
1327
1343
|
|
|
1328
|
-
if
|
|
1344
|
+
if is_sensitive(field, self.sensitive_fields, self.list_allow_sensitive_fields):
|
|
1329
1345
|
result[field] = '*********'
|
|
1330
1346
|
else:
|
|
1331
1347
|
result[field] = getattr(row, field, None)
|
|
@@ -1449,7 +1465,7 @@ class BaseResource(CoreResource):
|
|
|
1449
1465
|
result[key].append(item)
|
|
1450
1466
|
|
|
1451
1467
|
for field in self.edit_fields:
|
|
1452
|
-
if
|
|
1468
|
+
if is_sensitive(field, self.sensitive_fields, self.edit_allow_sensitive_fields):
|
|
1453
1469
|
result[field] = '*********'
|
|
1454
1470
|
else:
|
|
1455
1471
|
result[field] = getattr(self.obj, field, None)
|
|
@@ -31,7 +31,7 @@ import logging
|
|
|
31
31
|
|
|
32
32
|
from pydantic import BaseModel, ValidationError
|
|
33
33
|
|
|
34
|
-
from ..sensitive import
|
|
34
|
+
from ..sensitive import is_sensitive
|
|
35
35
|
from .schema import validate_input
|
|
36
36
|
|
|
37
37
|
logger = logging.getLogger(__name__)
|
|
@@ -75,7 +75,7 @@ def _public_tools(tools):
|
|
|
75
75
|
for t in tools]
|
|
76
76
|
|
|
77
77
|
|
|
78
|
-
def _serialize_result(value, sensitive_fields=()):
|
|
78
|
+
def _serialize_result(value, sensitive_fields=(), allow_sensitive_fields=()):
|
|
79
79
|
if isinstance(value, BaseModel):
|
|
80
80
|
payload = value.model_dump()
|
|
81
81
|
elif isinstance(value, list) and value and isinstance(value[0], BaseModel):
|
|
@@ -84,20 +84,20 @@ def _serialize_result(value, sensitive_fields=()):
|
|
|
84
84
|
payload = value
|
|
85
85
|
# Always scrub: the credential-name baseline applies even when the
|
|
86
86
|
# toolset declares no ``sensitive_fields``.
|
|
87
|
-
_scrub(payload, set(sensitive_fields))
|
|
87
|
+
_scrub(payload, set(sensitive_fields), set(allow_sensitive_fields))
|
|
88
88
|
return payload
|
|
89
89
|
|
|
90
90
|
|
|
91
|
-
def _scrub(obj, blocked):
|
|
91
|
+
def _scrub(obj, blocked, allowed):
|
|
92
92
|
if isinstance(obj, dict):
|
|
93
93
|
for key in list(obj.keys()):
|
|
94
|
-
if key
|
|
94
|
+
if is_sensitive(key, blocked, allowed):
|
|
95
95
|
obj.pop(key)
|
|
96
96
|
else:
|
|
97
|
-
_scrub(obj[key], blocked)
|
|
97
|
+
_scrub(obj[key], blocked, allowed)
|
|
98
98
|
elif isinstance(obj, list):
|
|
99
99
|
for item in obj:
|
|
100
|
-
_scrub(item, blocked)
|
|
100
|
+
_scrub(item, blocked, allowed)
|
|
101
101
|
|
|
102
102
|
|
|
103
103
|
def _caller_scopes(request):
|
|
@@ -327,7 +327,8 @@ async def _dispatch_tool_call(params, tools, request):
|
|
|
327
327
|
logger.exception('mcp.after_call.error tool=%s', tool_name)
|
|
328
328
|
|
|
329
329
|
sensitive = getattr(toolset_cls, 'sensitive_fields', None) or ()
|
|
330
|
-
|
|
330
|
+
allowed = getattr(toolset_cls, 'allow_sensitive_fields', None) or ()
|
|
331
|
+
payload = _serialize_result(ret, sensitive_fields=sensitive, allow_sensitive_fields=allowed)
|
|
331
332
|
return _tool_response(
|
|
332
333
|
{'tool': tool_name, 'code': 'OK', 'data': payload},
|
|
333
334
|
is_error=False,
|
|
@@ -35,3 +35,15 @@ def looks_sensitive(column_name):
|
|
|
35
35
|
if any(name.endswith(s) for s in _SENSITIVE_SUFFIXES):
|
|
36
36
|
return True
|
|
37
37
|
return False
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def is_sensitive(field, sensitive_fields=(), allow_sensitive_fields=()):
|
|
41
|
+
"""Whether ``field`` must be withheld on the surface the caller serves.
|
|
42
|
+
|
|
43
|
+
``allow_sensitive_fields`` is an escape hatch and wins over both the
|
|
44
|
+
per-resource deny list and the name heuristic — an explicit opt-in is
|
|
45
|
+
the only way a credential-named column ever leaves the server.
|
|
46
|
+
"""
|
|
47
|
+
if field in allow_sensitive_fields:
|
|
48
|
+
return False
|
|
49
|
+
return looks_sensitive(field) or field in sensitive_fields
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: easyapi-django
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.1
|
|
4
4
|
Summary: Zero-effort MCP server (and REST API) generator from Django models.
|
|
5
5
|
Author-email: Stamatios Stamou Jr <bushier.outsets.0c@icloud.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -235,7 +235,8 @@ Defaults are demo-friendly. Production deployments should:
|
|
|
235
235
|
- **Tune session TTLs.** `SESSION_TTL` (cookies, default 1800s) and `API_SESSION_TTL` (api-key cache, default 300s) both slide on use. Pick numbers that match your security/UX trade-off.
|
|
236
236
|
- **Never ship `DEBUG = True`.** Under `DEBUG`, edge security (scanner blocking, 4xx flood detection), rate limiting, credential-path defense and abuse blocking are all bypassed for dev ergonomics. The framework logs a loud startup warning when the bypass is active. To decouple from `DEBUG` — e.g. a staging box mirroring production — set `MCP = {'SECURITY_BYPASS': False}` to force protections on (or `True` to bypass without `DEBUG`).
|
|
237
237
|
- **Trust your proxies explicitly.** IP-based rate limits and abuse blocking key on the client IP. `X-Forwarded-For` / `X-Real-IP` are only honored when `REMOTE_ADDR` is in `TRUSTED_PROXIES = ['10.0.0.0/8', ...]`; otherwise `REMOTE_ADDR` wins. Leave it unset behind a proxy and every request looks like it comes from the proxy.
|
|
238
|
-
- **Bound the metrics surface.** The `/metrics` endpoint queries any model in the registry by default. Set `METRICS_MODELS = ['app_Model', ...]` to allowlist which models are queryable. References to credential-named columns (in filters, groups, order or calc) are always rejected
|
|
238
|
+
- **Bound the metrics surface.** The `/metrics` endpoint queries any model in the registry by default. Set `METRICS_MODELS = ['app_Model', ...]` to allowlist which models are queryable. References to credential-named columns (in filters, groups, order or calc) are always rejected — the per-resource escape hatch does not reach `/metrics`.
|
|
239
|
+
- **Keep the escape hatch narrow.** Columns whose names look like credentials (`password`, `api_key`, `token`, `otp`, `secret`, …) are masked as `'*********'` on every response, on top of whatever `sensitive_fields` lists. When the heuristic catches a false positive, opt it out per surface: `edit_allow_sensitive_fields = ['token_count']` returns it verbatim on `GET /resource/<id>/` while LIST still masks it. `list_allow_sensitive_fields` does the same for LIST, where every row carries the value — reach for the detail-only form first.
|
|
239
240
|
- **OAuth clients are public by default.** MCP clients (Claude.ai, ChatGPT, ...) authenticate with PKCE — list them under `MCP['OAUTH_CLIENTS']` with no `client_secret`. A client becomes confidential only with an explicit secret; `/register` never hands out a confidential secret (provision those out of band).
|
|
240
241
|
|
|
241
242
|
---
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/resources/device_authorization.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/templates/zeromcp_oauth/consent.html
RENAMED
|
File without changes
|
{easyapi_django-1.2.2 → easyapi_django-1.3.1}/easyapi/oauth/templates/zeromcp_oauth/device.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|