drupal-mcp-connector 2.5.0 → 2.6.1
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.
- package/CHANGELOG.md +70 -0
- package/README.md +3 -0
- package/bin/drupal-mcp-verify.js +147 -0
- package/config/config.example.json +31 -29
- package/package.json +7 -4
- package/src/index.js +17 -0
- package/src/lib/config.js +19 -1
- package/src/lib/load-secrets.js +158 -0
- package/src/lib/security.js +27 -4
- package/src/lib/verify.js +916 -0
- package/src/tools/site.js +32 -1
package/src/lib/security.js
CHANGED
|
@@ -343,16 +343,39 @@ export function assertConfigWriteAllowed(secConfig) {
|
|
|
343
343
|
}
|
|
344
344
|
|
|
345
345
|
/**
|
|
346
|
-
* Whether the site's OAuth token carries a given scope.
|
|
347
|
-
* no
|
|
348
|
-
*
|
|
346
|
+
* Whether the site's OAuth token carries a given scope. A governed setup that
|
|
347
|
+
* names no scopes carries none of them (#180) — an unnamed grant is not a
|
|
348
|
+
* wildcard. A preset-only, non-OAuth site keeps the permissive behaviour.
|
|
349
349
|
* @param {object} site Resolved site config.
|
|
350
350
|
* @param {string} scope OAuth scope machine id (e.g. "mcp_config").
|
|
351
351
|
* @returns {boolean} True if the scope is present, or no scopes are configured.
|
|
352
352
|
*/
|
|
353
353
|
export function hasScope(site, scope) {
|
|
354
354
|
const scopes = site?.oauth?.scopes ?? [];
|
|
355
|
-
|
|
355
|
+
if (scopes.length > 0) return scopes.includes(scope);
|
|
356
|
+
// Empty or absent scopes. For a GOVERNED product setup — a site that claims
|
|
357
|
+
// source governance, or configures OAuth at all — that is not "every scope";
|
|
358
|
+
// it is an unnamed grant, and treating it as a wildcard was a bypass of the
|
|
359
|
+
// very gate the scopes exist to be (#180). Deny, so the operator has to name
|
|
360
|
+
// the scopes the token actually carries.
|
|
361
|
+
//
|
|
362
|
+
// An ungoverned install (a plain apiToken or anonymous site, no OAuth block)
|
|
363
|
+
// has no scope vocabulary to name and keeps the historical permissive
|
|
364
|
+
// behaviour: preset semantics alone decide there.
|
|
365
|
+
return !isGovernedSetup(site);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Whether a site is part of the governed product path.
|
|
370
|
+
*
|
|
371
|
+
* Either it declares the source-governance requirement, or it authenticates
|
|
372
|
+
* with OAuth — both mean the server is deciding on scopes, so the connector
|
|
373
|
+
* must not invent one.
|
|
374
|
+
* @param {object} site Resolved site config.
|
|
375
|
+
* @returns {boolean}
|
|
376
|
+
*/
|
|
377
|
+
export function isGovernedSetup(site) {
|
|
378
|
+
return site?.requireGovernance === true || Boolean(site?.oauth);
|
|
356
379
|
}
|
|
357
380
|
|
|
358
381
|
/**
|