@digitalbazaar/ezcap-express 4.2.0 → 4.3.0
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 +12 -0
- package/lib/revoke.js +55 -48
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @digitalbazaar/ezcap-express Changelog
|
|
2
2
|
|
|
3
|
+
## 4.3.0 - 2021-12-10
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Allow any controller in a delegated zcap's chain to revoke it. This authority
|
|
7
|
+
is inherent in delegation and is now reflected in code. This feature gives
|
|
8
|
+
delegators more fine-grained control to revoke zcaps that they did not
|
|
9
|
+
delegate directly but one of their delegates did, allowing them to stop
|
|
10
|
+
specific zcap usage without having to revoke more of the chain. It also
|
|
11
|
+
gives zcap controllers the ability to revoke their own zcaps (if desired)
|
|
12
|
+
and adds a sanity check to prevent the revocation of root zcaps that use
|
|
13
|
+
the `urn:zcap:root:` ID scheme.
|
|
14
|
+
|
|
3
15
|
## 4.2.0 - 2021-08-26
|
|
4
16
|
|
|
5
17
|
### Added
|
package/lib/revoke.js
CHANGED
|
@@ -48,32 +48,18 @@ export function authorizeZcapRevocation({
|
|
|
48
48
|
expectedHost, expectedAction, getExpectedTarget,
|
|
49
49
|
getExpectedRootCapabilityId, onError
|
|
50
50
|
}),
|
|
51
|
-
|
|
51
|
+
verifyCapabilityDelegation({
|
|
52
52
|
documentLoader, getRootController, suiteFactory, inspectCapabilityChain,
|
|
53
53
|
onError
|
|
54
54
|
}),
|
|
55
55
|
authorizeZcapInvocationAfterParse({
|
|
56
56
|
documentLoader, getRootController: _getRevocationRootController,
|
|
57
57
|
suiteFactory, allowTargetAttenuation, inspectCapabilityChain, onError
|
|
58
|
-
})
|
|
59
|
-
function(req, res, next) {
|
|
60
|
-
// ensure that the invoker of the write capability is the delegator
|
|
61
|
-
// of the capability to be revoked
|
|
62
|
-
const {zcapRevocation: {delegator}} = req;
|
|
63
|
-
const invoker = req.zcap.controller || req.zcap.invoker;
|
|
64
|
-
if(delegator !== invoker) {
|
|
65
|
-
const error = new Error(
|
|
66
|
-
'Permission denied; the zcap invoker must be the delegator of the ' +
|
|
67
|
-
'zcap that is to be revoked.');
|
|
68
|
-
error.name = 'NotAllowedError';
|
|
69
|
-
return next(error);
|
|
70
|
-
}
|
|
71
|
-
next();
|
|
72
|
-
}
|
|
58
|
+
})
|
|
73
59
|
];
|
|
74
60
|
}
|
|
75
61
|
|
|
76
|
-
function
|
|
62
|
+
function verifyCapabilityDelegation({
|
|
77
63
|
documentLoader, getRootController, inspectCapabilityChain, suiteFactory,
|
|
78
64
|
onError
|
|
79
65
|
}) {
|
|
@@ -82,9 +68,18 @@ function getDelegator({
|
|
|
82
68
|
expectedAction, expectedHost, expectedTarget, expectedRootCapability
|
|
83
69
|
} = req.ezcap;
|
|
84
70
|
|
|
85
|
-
// verify CapabilityDelegation
|
|
86
71
|
const {body: capability} = req;
|
|
72
|
+
|
|
73
|
+
// early-disallow revocation of root zcaps that follow ID convention
|
|
74
|
+
if(capability.id.startsWith(helpers.ZCAP_ROOT_PREFIX)) {
|
|
75
|
+
const error = new Error('A root capability cannot be revoked.');
|
|
76
|
+
error.name = 'NotAllowedError';
|
|
77
|
+
return helpers.handleError({error, onError});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// verify CapabilityDelegation
|
|
87
81
|
let delegator;
|
|
82
|
+
const chainControllers = [];
|
|
88
83
|
try {
|
|
89
84
|
const results = await _verifyDelegation({
|
|
90
85
|
capability,
|
|
@@ -93,7 +88,10 @@ function getDelegator({
|
|
|
93
88
|
getRootController
|
|
94
89
|
}),
|
|
95
90
|
expectedRootCapability,
|
|
96
|
-
inspectCapabilityChain
|
|
91
|
+
inspectCapabilityChain: _captureChainControllers({
|
|
92
|
+
inspectCapabilityChain,
|
|
93
|
+
chainControllers
|
|
94
|
+
}),
|
|
97
95
|
suiteFactory
|
|
98
96
|
});
|
|
99
97
|
({delegator} = results[0].purposeResult);
|
|
@@ -105,7 +103,7 @@ function getDelegator({
|
|
|
105
103
|
return helpers.handleError({error, onError});
|
|
106
104
|
}
|
|
107
105
|
|
|
108
|
-
req.zcapRevocation = {delegator};
|
|
106
|
+
req.zcapRevocation = {delegator, chainControllers};
|
|
109
107
|
|
|
110
108
|
// proceed to next middleware on next tick to prevent subsequent
|
|
111
109
|
// middleware from potentially throwing here
|
|
@@ -144,39 +142,48 @@ async function _getRevocationRootController({
|
|
|
144
142
|
}
|
|
145
143
|
|
|
146
144
|
/* Note: If the invocation target is a zcap-specific revocation endpoint,
|
|
147
|
-
we use
|
|
145
|
+
we use all zcap controllers from the submitted zcap's chain as the root
|
|
146
|
+
controller value for the target.
|
|
148
147
|
|
|
149
|
-
This approach allows any party that has delegated a zcap
|
|
150
|
-
it for revocation. Subsequent code (in the revocation
|
|
151
|
-
confirm that the delegation is proper and the zcap from
|
|
152
|
-
delegated has not itself been revoked.
|
|
148
|
+
This approach allows any party that has delegated a zcap or received one
|
|
149
|
+
to be able to send it for revocation. Subsequent code (in the revocation
|
|
150
|
+
route handler) will confirm that the delegation is proper and the zcap from
|
|
151
|
+
which it was delegated has not itself been revoked.
|
|
153
152
|
|
|
154
153
|
To be clear, if the delegation chain is:
|
|
155
154
|
|
|
156
155
|
root -> A -> B
|
|
157
156
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
`B`
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
'that the capability is sent to a URL that includes its ID.');
|
|
172
|
-
error.name = 'URLMismatchError';
|
|
173
|
-
error.details = {
|
|
174
|
-
rootInvocationTarget,
|
|
175
|
-
requestUrl
|
|
176
|
-
};
|
|
177
|
-
throw error;
|
|
178
|
-
}
|
|
157
|
+
Any zcap controller in the chain of B may invoke a root zcap with a
|
|
158
|
+
`target` of `<baseUrl>/revocations/<ID of B>` (and an ID of
|
|
159
|
+
`urn:zcap:root:encodeURIComponent(<baseUrl>/revocations/<ID of B>)`). This
|
|
160
|
+
means that `root`, `A`, or `B` may revoke `B`.
|
|
161
|
+
|
|
162
|
+
As long no other zcap in the chain of `B` (e.g., `A`) has already been
|
|
163
|
+
revoked, then `B` will be revoked and stored as a revocation until `B`
|
|
164
|
+
expires. */
|
|
165
|
+
|
|
166
|
+
// use all `chainControllers`
|
|
167
|
+
// presumes `verifyCapabilityDelegation` middleware already called
|
|
168
|
+
return req.zcapRevocation.chainControllers;
|
|
169
|
+
}
|
|
179
170
|
|
|
180
|
-
|
|
181
|
-
return
|
|
171
|
+
function _captureChainControllers({inspectCapabilityChain, chainControllers}) {
|
|
172
|
+
return async function _inspectCapabilityChain(chainDetails) {
|
|
173
|
+
// collect every controller in the chain
|
|
174
|
+
const {capabilityChain} = chainDetails;
|
|
175
|
+
for(const capability of capabilityChain.values()) {
|
|
176
|
+
chainControllers.push(..._getCapabilityControllers({capability}));
|
|
177
|
+
}
|
|
178
|
+
return inspectCapabilityChain(chainDetails);
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function _getCapabilityControllers({capability}) {
|
|
183
|
+
const {controller, id} = capability;
|
|
184
|
+
const result = controller || id;
|
|
185
|
+
if(!result) {
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
return Array.isArray(result) ? result : [result];
|
|
182
189
|
}
|