@backstage/backend-app-api 0.7.6-next.0 → 0.7.6-next.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 +15 -0
- package/alpha/package.json +1 -1
- package/config.d.ts +121 -0
- package/dist/index.cjs.js +224 -45
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @backstage/backend-app-api
|
|
2
2
|
|
|
3
|
+
## 0.7.6-next.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 398b82a: Add support for JWKS tokens in ExternalTokenHandler.
|
|
8
|
+
- 9e63318: Added an optional `accessRestrictions` to external access service tokens and service principals in general, such that you can limit their access to certain plugins or permissions.
|
|
9
|
+
- Updated dependencies
|
|
10
|
+
- @backstage/backend-tasks@0.5.24-next.1
|
|
11
|
+
- @backstage/backend-plugin-api@0.6.19-next.1
|
|
12
|
+
- @backstage/plugin-permission-node@0.7.30-next.1
|
|
13
|
+
- @backstage/backend-common@0.23.0-next.1
|
|
14
|
+
- @backstage/cli-node@0.2.6-next.0
|
|
15
|
+
- @backstage/config-loader@1.8.0
|
|
16
|
+
- @backstage/plugin-auth-node@0.4.14-next.1
|
|
17
|
+
|
|
3
18
|
## 0.7.6-next.0
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
package/alpha/package.json
CHANGED
package/config.d.ts
CHANGED
|
@@ -88,6 +88,46 @@ export interface Config {
|
|
|
88
88
|
*/
|
|
89
89
|
subject: string;
|
|
90
90
|
};
|
|
91
|
+
/**
|
|
92
|
+
* Restricts what types of access that are permitted for this access
|
|
93
|
+
* method. If no access restrictions are given, it'll have unlimited
|
|
94
|
+
* access. This access restriction applies for the framework level;
|
|
95
|
+
* individual plugins may have their own access control mechanisms
|
|
96
|
+
* on top of this.
|
|
97
|
+
*/
|
|
98
|
+
accessRestrictions?: Array<{
|
|
99
|
+
/**
|
|
100
|
+
* Permit access to make requests to this plugin.
|
|
101
|
+
*
|
|
102
|
+
* Can be further refined by setting additional fields below.
|
|
103
|
+
*/
|
|
104
|
+
plugin: string;
|
|
105
|
+
/**
|
|
106
|
+
* If given, this method is limited to only performing actions
|
|
107
|
+
* with these named permissions in this plugin.
|
|
108
|
+
*
|
|
109
|
+
* Note that this only applies where permissions checks are
|
|
110
|
+
* enabled in the first place. Endpoints that are not protected by
|
|
111
|
+
* the permissions system at all, are not affected by this
|
|
112
|
+
* setting.
|
|
113
|
+
*/
|
|
114
|
+
permission?: string | Array<string>;
|
|
115
|
+
/**
|
|
116
|
+
* If given, this method is limited to only performing actions
|
|
117
|
+
* whose permissions have these attributes.
|
|
118
|
+
*
|
|
119
|
+
* Note that this only applies where permissions checks are
|
|
120
|
+
* enabled in the first place. Endpoints that are not protected by
|
|
121
|
+
* the permissions system at all, are not affected by this
|
|
122
|
+
* setting.
|
|
123
|
+
*/
|
|
124
|
+
permissionAttribute?: {
|
|
125
|
+
/**
|
|
126
|
+
* One of more of 'create', 'read', 'update', or 'delete'.
|
|
127
|
+
*/
|
|
128
|
+
action?: string | Array<string>;
|
|
129
|
+
};
|
|
130
|
+
}>;
|
|
91
131
|
}
|
|
92
132
|
| {
|
|
93
133
|
/**
|
|
@@ -130,6 +170,87 @@ export interface Config {
|
|
|
130
170
|
*/
|
|
131
171
|
subject: string;
|
|
132
172
|
};
|
|
173
|
+
/**
|
|
174
|
+
* Restricts what types of access that are permitted for this access
|
|
175
|
+
* method. If no access restrictions are given, it'll have unlimited
|
|
176
|
+
* access. This access restriction applies for the framework level;
|
|
177
|
+
* individual plugins may have their own access control mechanisms
|
|
178
|
+
* on top of this.
|
|
179
|
+
*/
|
|
180
|
+
accessRestrictions?: Array<{
|
|
181
|
+
/**
|
|
182
|
+
* Permit access to make requests to this plugin.
|
|
183
|
+
*
|
|
184
|
+
* Can be further refined by setting additional fields below.
|
|
185
|
+
*/
|
|
186
|
+
plugin: string;
|
|
187
|
+
/**
|
|
188
|
+
* If given, this method is limited to only performing actions
|
|
189
|
+
* with these named permissions in this plugin.
|
|
190
|
+
*
|
|
191
|
+
* Note that this only applies where permissions checks are
|
|
192
|
+
* enabled in the first place. Endpoints that are not protected by
|
|
193
|
+
* the permissions system at all, are not affected by this
|
|
194
|
+
* setting.
|
|
195
|
+
*/
|
|
196
|
+
permission?: string | Array<string>;
|
|
197
|
+
/**
|
|
198
|
+
* If given, this method is limited to only performing actions
|
|
199
|
+
* whose permissions have these attributes.
|
|
200
|
+
*
|
|
201
|
+
* Note that this only applies where permissions checks are
|
|
202
|
+
* enabled in the first place. Endpoints that are not protected by
|
|
203
|
+
* the permissions system at all, are not affected by this
|
|
204
|
+
* setting.
|
|
205
|
+
*/
|
|
206
|
+
permissionAttribute?: {
|
|
207
|
+
/**
|
|
208
|
+
* One of more of 'create', 'read', 'update', or 'delete'.
|
|
209
|
+
*/
|
|
210
|
+
action?: string | Array<string>;
|
|
211
|
+
};
|
|
212
|
+
}>;
|
|
213
|
+
}
|
|
214
|
+
| {
|
|
215
|
+
/**
|
|
216
|
+
* This access method consists of a JWKS endpoint that can be used to
|
|
217
|
+
* verify JWT tokens.
|
|
218
|
+
*
|
|
219
|
+
* Callers generate JWT tokens via 3rd party tooling
|
|
220
|
+
* and pass them in the Authorization header:
|
|
221
|
+
*
|
|
222
|
+
* ```
|
|
223
|
+
* Authorization: Bearer eZv5o+fW3KnR3kVabMW4ZcDNLPl8nmMW
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
type: 'jwks';
|
|
227
|
+
options: {
|
|
228
|
+
/**
|
|
229
|
+
* The full URL of the JWKS endpoint.
|
|
230
|
+
*/
|
|
231
|
+
url: string;
|
|
232
|
+
/**
|
|
233
|
+
* Sets the algorithm(s) that should be used to verify the JWT tokens.
|
|
234
|
+
* The passed JWTs must have been signed using one of the listed algorithms.
|
|
235
|
+
*/
|
|
236
|
+
algorithm?: string | string[];
|
|
237
|
+
/**
|
|
238
|
+
* Sets the issuer(s) that should be used to verify the JWT tokens.
|
|
239
|
+
* Passed JWTs must have an `iss` claim which matches one of the specified issuers.
|
|
240
|
+
*/
|
|
241
|
+
issuer?: string | string[];
|
|
242
|
+
/**
|
|
243
|
+
* Sets the audience(s) that should be used to verify the JWT tokens.
|
|
244
|
+
* The passed JWTs must have an "aud" claim that matches one of the audiences specified,
|
|
245
|
+
* or have no audience specified.
|
|
246
|
+
*/
|
|
247
|
+
audience?: string | string[];
|
|
248
|
+
/**
|
|
249
|
+
* Sets an optional subject prefix. Passes the subject to called plugins.
|
|
250
|
+
* Useful for debugging and tracking purposes.
|
|
251
|
+
*/
|
|
252
|
+
subjectPrefix?: string;
|
|
253
|
+
};
|
|
133
254
|
}
|
|
134
255
|
>;
|
|
135
256
|
};
|
package/dist/index.cjs.js
CHANGED
|
@@ -1697,14 +1697,15 @@ class DatabaseKeyStore {
|
|
|
1697
1697
|
}
|
|
1698
1698
|
}
|
|
1699
1699
|
|
|
1700
|
-
function createCredentialsWithServicePrincipal(sub, token) {
|
|
1700
|
+
function createCredentialsWithServicePrincipal(sub, token, accessRestrictions) {
|
|
1701
1701
|
return {
|
|
1702
1702
|
$$type: "@backstage/BackstageCredentials",
|
|
1703
1703
|
version: "v1",
|
|
1704
1704
|
token,
|
|
1705
1705
|
principal: {
|
|
1706
1706
|
type: "service",
|
|
1707
|
-
subject: sub
|
|
1707
|
+
subject: sub,
|
|
1708
|
+
accessRestrictions
|
|
1708
1709
|
}
|
|
1709
1710
|
};
|
|
1710
1711
|
}
|
|
@@ -1783,7 +1784,11 @@ class DefaultAuthService {
|
|
|
1783
1784
|
}
|
|
1784
1785
|
const externalResult = await this.externalTokenHandler.verifyToken(token);
|
|
1785
1786
|
if (externalResult) {
|
|
1786
|
-
return createCredentialsWithServicePrincipal(
|
|
1787
|
+
return createCredentialsWithServicePrincipal(
|
|
1788
|
+
externalResult.subject,
|
|
1789
|
+
void 0,
|
|
1790
|
+
externalResult.accessRestrictions
|
|
1791
|
+
);
|
|
1787
1792
|
}
|
|
1788
1793
|
throw new errors.AuthenticationError("Illegal token");
|
|
1789
1794
|
}
|
|
@@ -2197,18 +2202,112 @@ class UserTokenHandler {
|
|
|
2197
2202
|
}
|
|
2198
2203
|
}
|
|
2199
2204
|
|
|
2205
|
+
function readAccessRestrictionsFromConfig(externalAccessEntryConfig) {
|
|
2206
|
+
const configs = externalAccessEntryConfig.getOptionalConfigArray("accessRestrictions") ?? [];
|
|
2207
|
+
const result = /* @__PURE__ */ new Map();
|
|
2208
|
+
for (const config of configs) {
|
|
2209
|
+
const validKeys = ["plugin", "permission", "permissionAttribute"];
|
|
2210
|
+
for (const key of config.keys()) {
|
|
2211
|
+
if (!validKeys.includes(key)) {
|
|
2212
|
+
const valid = validKeys.map((k) => `'${k}'`).join(", ");
|
|
2213
|
+
throw new Error(
|
|
2214
|
+
`Invalid key '${key}' in 'accessRestrictions' config, expected one of ${valid}`
|
|
2215
|
+
);
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
const pluginId = config.getString("plugin");
|
|
2219
|
+
const permissionNames = readPermissionNames(config);
|
|
2220
|
+
const permissionAttributes = readPermissionAttributes(config);
|
|
2221
|
+
if (result.has(pluginId)) {
|
|
2222
|
+
throw new Error(
|
|
2223
|
+
`Attempted to declare 'accessRestrictions' twice for plugin '${pluginId}', which is not permitted`
|
|
2224
|
+
);
|
|
2225
|
+
}
|
|
2226
|
+
result.set(pluginId, {
|
|
2227
|
+
...permissionNames ? { permissionNames } : {},
|
|
2228
|
+
...permissionAttributes ? { permissionAttributes } : {}
|
|
2229
|
+
});
|
|
2230
|
+
}
|
|
2231
|
+
return result.size ? result : void 0;
|
|
2232
|
+
}
|
|
2233
|
+
function readStringOrStringArrayFromConfig(root, key, validValues) {
|
|
2234
|
+
if (!root.has(key)) {
|
|
2235
|
+
return void 0;
|
|
2236
|
+
}
|
|
2237
|
+
const rawValues = Array.isArray(root.get(key)) ? root.getStringArray(key) : [root.getString(key)];
|
|
2238
|
+
const values = [
|
|
2239
|
+
...new Set(
|
|
2240
|
+
rawValues.map((v) => v.split(/[ ,]/)).flat().filter(Boolean)
|
|
2241
|
+
)
|
|
2242
|
+
];
|
|
2243
|
+
if (!values.length) {
|
|
2244
|
+
return void 0;
|
|
2245
|
+
}
|
|
2246
|
+
if (validValues?.length) {
|
|
2247
|
+
for (const value of values) {
|
|
2248
|
+
if (!validValues.includes(value)) {
|
|
2249
|
+
const valid = validValues.map((k) => `'${k}'`).join(", ");
|
|
2250
|
+
throw new Error(
|
|
2251
|
+
`Invalid value '${value}' at '${key}' in 'permissionAttributes' config, valid values are ${valid}`
|
|
2252
|
+
);
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
return values;
|
|
2257
|
+
}
|
|
2258
|
+
function readPermissionNames(externalAccessEntryConfig) {
|
|
2259
|
+
return readStringOrStringArrayFromConfig(
|
|
2260
|
+
externalAccessEntryConfig,
|
|
2261
|
+
"permission"
|
|
2262
|
+
);
|
|
2263
|
+
}
|
|
2264
|
+
function readPermissionAttributes(externalAccessEntryConfig) {
|
|
2265
|
+
const config = externalAccessEntryConfig.getOptionalConfig(
|
|
2266
|
+
"permissionAttribute"
|
|
2267
|
+
);
|
|
2268
|
+
if (!config) {
|
|
2269
|
+
return void 0;
|
|
2270
|
+
}
|
|
2271
|
+
const validKeys = ["action"];
|
|
2272
|
+
for (const key of config.keys()) {
|
|
2273
|
+
if (!validKeys.includes(key)) {
|
|
2274
|
+
const valid = validKeys.map((k) => `'${k}'`).join(", ");
|
|
2275
|
+
throw new Error(
|
|
2276
|
+
`Invalid key '${key}' in 'permissionAttribute' config, expected ${valid}`
|
|
2277
|
+
);
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
const action = readStringOrStringArrayFromConfig(config, "action", [
|
|
2281
|
+
"create",
|
|
2282
|
+
"read",
|
|
2283
|
+
"update",
|
|
2284
|
+
"delete"
|
|
2285
|
+
]);
|
|
2286
|
+
const result = {
|
|
2287
|
+
...action ? { action } : {}
|
|
2288
|
+
};
|
|
2289
|
+
return Object.keys(result).length ? result : void 0;
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2200
2292
|
class LegacyTokenHandler {
|
|
2201
|
-
#entries =
|
|
2202
|
-
add(
|
|
2203
|
-
|
|
2293
|
+
#entries = new Array();
|
|
2294
|
+
add(config) {
|
|
2295
|
+
const allAccessRestrictions = readAccessRestrictionsFromConfig(config);
|
|
2296
|
+
this.#doAdd(
|
|
2297
|
+
config.getString("options.secret"),
|
|
2298
|
+
config.getString("options.subject"),
|
|
2299
|
+
allAccessRestrictions
|
|
2300
|
+
);
|
|
2204
2301
|
}
|
|
2205
2302
|
// used only for the old backend.auth.keys array
|
|
2206
|
-
addOld(
|
|
2207
|
-
this.#doAdd(
|
|
2303
|
+
addOld(config) {
|
|
2304
|
+
this.#doAdd(config.getString("secret"), "external:backstage-plugin");
|
|
2208
2305
|
}
|
|
2209
|
-
#doAdd(secret, subject) {
|
|
2306
|
+
#doAdd(secret, subject, allAccessRestrictions) {
|
|
2210
2307
|
if (!secret.match(/^\S+$/)) {
|
|
2211
2308
|
throw new Error("Illegal secret, must be a valid base64 string");
|
|
2309
|
+
} else if (!subject.match(/^\S+$/)) {
|
|
2310
|
+
throw new Error("Illegal subject, must be a set of non-space characters");
|
|
2212
2311
|
}
|
|
2213
2312
|
let key;
|
|
2214
2313
|
try {
|
|
@@ -2216,10 +2315,18 @@ class LegacyTokenHandler {
|
|
|
2216
2315
|
} catch {
|
|
2217
2316
|
throw new Error("Illegal secret, must be a valid base64 string");
|
|
2218
2317
|
}
|
|
2219
|
-
if (
|
|
2220
|
-
throw new Error(
|
|
2318
|
+
if (this.#entries.some((e) => e.key === key)) {
|
|
2319
|
+
throw new Error(
|
|
2320
|
+
"Legacy externalAccess token was declared more than once"
|
|
2321
|
+
);
|
|
2221
2322
|
}
|
|
2222
|
-
this.#entries.push({
|
|
2323
|
+
this.#entries.push({
|
|
2324
|
+
key,
|
|
2325
|
+
result: {
|
|
2326
|
+
subject,
|
|
2327
|
+
allAccessRestrictions
|
|
2328
|
+
}
|
|
2329
|
+
});
|
|
2223
2330
|
}
|
|
2224
2331
|
async verifyToken(token) {
|
|
2225
2332
|
try {
|
|
@@ -2234,10 +2341,10 @@ class LegacyTokenHandler {
|
|
|
2234
2341
|
} catch (e) {
|
|
2235
2342
|
return void 0;
|
|
2236
2343
|
}
|
|
2237
|
-
for (const
|
|
2344
|
+
for (const { key, result } of this.#entries) {
|
|
2238
2345
|
try {
|
|
2239
|
-
await jose.jwtVerify(token,
|
|
2240
|
-
return
|
|
2346
|
+
await jose.jwtVerify(token, key);
|
|
2347
|
+
return result;
|
|
2241
2348
|
} catch (e) {
|
|
2242
2349
|
if (e.code !== "ERR_JWS_SIGNATURE_VERIFICATION_FAILED") {
|
|
2243
2350
|
throw e;
|
|
@@ -2250,45 +2357,104 @@ class LegacyTokenHandler {
|
|
|
2250
2357
|
|
|
2251
2358
|
const MIN_TOKEN_LENGTH = 8;
|
|
2252
2359
|
class StaticTokenHandler {
|
|
2253
|
-
#entries =
|
|
2254
|
-
add(
|
|
2255
|
-
const token =
|
|
2360
|
+
#entries = /* @__PURE__ */ new Map();
|
|
2361
|
+
add(config) {
|
|
2362
|
+
const token = config.getString("options.token");
|
|
2363
|
+
const subject = config.getString("options.subject");
|
|
2364
|
+
const allAccessRestrictions = readAccessRestrictionsFromConfig(config);
|
|
2256
2365
|
if (!token.match(/^\S+$/)) {
|
|
2257
2366
|
throw new Error("Illegal token, must be a set of non-space characters");
|
|
2258
|
-
}
|
|
2259
|
-
if (token.length < MIN_TOKEN_LENGTH) {
|
|
2367
|
+
} else if (token.length < MIN_TOKEN_LENGTH) {
|
|
2260
2368
|
throw new Error(
|
|
2261
2369
|
`Illegal token, must be at least ${MIN_TOKEN_LENGTH} characters length`
|
|
2262
2370
|
);
|
|
2263
|
-
}
|
|
2264
|
-
const subject = options.getString("subject");
|
|
2265
|
-
if (!subject.match(/^\S+$/)) {
|
|
2371
|
+
} else if (!subject.match(/^\S+$/)) {
|
|
2266
2372
|
throw new Error("Illegal subject, must be a set of non-space characters");
|
|
2373
|
+
} else if (this.#entries.has(token)) {
|
|
2374
|
+
throw new Error(
|
|
2375
|
+
"Static externalAccess token was declared more than once"
|
|
2376
|
+
);
|
|
2267
2377
|
}
|
|
2268
|
-
this.#entries.
|
|
2378
|
+
this.#entries.set(token, { subject, allAccessRestrictions });
|
|
2269
2379
|
}
|
|
2270
2380
|
async verifyToken(token) {
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2381
|
+
return this.#entries.get(token);
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
class JWKSHandler {
|
|
2386
|
+
#entries = [];
|
|
2387
|
+
add(config) {
|
|
2388
|
+
if (!config.getString("options.url").match(/^\S+$/)) {
|
|
2389
|
+
throw new Error(
|
|
2390
|
+
"Illegal JWKS URL, must be a set of non-space characters"
|
|
2391
|
+
);
|
|
2274
2392
|
}
|
|
2275
|
-
|
|
2393
|
+
const algorithms = readStringOrStringArrayFromConfig(
|
|
2394
|
+
config,
|
|
2395
|
+
"options.algorithm"
|
|
2396
|
+
);
|
|
2397
|
+
const issuers = readStringOrStringArrayFromConfig(config, "options.issuer");
|
|
2398
|
+
const audiences = readStringOrStringArrayFromConfig(
|
|
2399
|
+
config,
|
|
2400
|
+
"options.audience"
|
|
2401
|
+
);
|
|
2402
|
+
const subjectPrefix = config.getOptionalString("options.subjectPrefix");
|
|
2403
|
+
const url = new URL(config.getString("options.url"));
|
|
2404
|
+
const jwks = jose.createRemoteJWKSet(url);
|
|
2405
|
+
const allAccessRestrictions = readAccessRestrictionsFromConfig(config);
|
|
2406
|
+
this.#entries.push({
|
|
2407
|
+
algorithms,
|
|
2408
|
+
audiences,
|
|
2409
|
+
issuers,
|
|
2410
|
+
jwks,
|
|
2411
|
+
subjectPrefix,
|
|
2412
|
+
url,
|
|
2413
|
+
allAccessRestrictions
|
|
2414
|
+
});
|
|
2415
|
+
}
|
|
2416
|
+
async verifyToken(token) {
|
|
2417
|
+
for (const entry of this.#entries) {
|
|
2418
|
+
try {
|
|
2419
|
+
const {
|
|
2420
|
+
payload: { sub }
|
|
2421
|
+
} = await jose.jwtVerify(token, entry.jwks, {
|
|
2422
|
+
algorithms: entry.algorithms,
|
|
2423
|
+
issuer: entry.issuers,
|
|
2424
|
+
audience: entry.audiences
|
|
2425
|
+
});
|
|
2426
|
+
if (sub) {
|
|
2427
|
+
const prefix = entry.subjectPrefix ? `external:${entry.subjectPrefix}:` : "external:";
|
|
2428
|
+
return {
|
|
2429
|
+
subject: `${prefix}${sub}`,
|
|
2430
|
+
allAccessRestrictions: entry.allAccessRestrictions
|
|
2431
|
+
};
|
|
2432
|
+
}
|
|
2433
|
+
} catch {
|
|
2434
|
+
continue;
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
return void 0;
|
|
2276
2438
|
}
|
|
2277
2439
|
}
|
|
2278
2440
|
|
|
2279
2441
|
const NEW_CONFIG_KEY = "backend.auth.externalAccess";
|
|
2280
2442
|
const OLD_CONFIG_KEY = "backend.auth.keys";
|
|
2443
|
+
let loggedDeprecationWarning = false;
|
|
2281
2444
|
class ExternalTokenHandler {
|
|
2282
|
-
constructor(handlers) {
|
|
2445
|
+
constructor(ownPluginId, handlers) {
|
|
2446
|
+
this.ownPluginId = ownPluginId;
|
|
2283
2447
|
this.handlers = handlers;
|
|
2284
2448
|
}
|
|
2285
2449
|
static create(options) {
|
|
2286
|
-
const { config, logger } = options;
|
|
2450
|
+
const { ownPluginId, config, logger } = options;
|
|
2287
2451
|
const staticHandler = new StaticTokenHandler();
|
|
2288
2452
|
const legacyHandler = new LegacyTokenHandler();
|
|
2453
|
+
const jwksHandler = new JWKSHandler();
|
|
2289
2454
|
const handlers = {
|
|
2290
2455
|
static: staticHandler,
|
|
2291
|
-
legacy: legacyHandler
|
|
2456
|
+
legacy: legacyHandler,
|
|
2457
|
+
jwks: jwksHandler
|
|
2292
2458
|
};
|
|
2293
2459
|
const handlerConfigs = config.getOptionalConfigArray(NEW_CONFIG_KEY) ?? [];
|
|
2294
2460
|
for (const handlerConfig of handlerConfigs) {
|
|
@@ -2300,10 +2466,11 @@ class ExternalTokenHandler {
|
|
|
2300
2466
|
`Unknown type '${type}' in ${NEW_CONFIG_KEY}, expected one of ${valid}`
|
|
2301
2467
|
);
|
|
2302
2468
|
}
|
|
2303
|
-
handler.add(handlerConfig
|
|
2469
|
+
handler.add(handlerConfig);
|
|
2304
2470
|
}
|
|
2305
2471
|
const legacyConfigs = config.getOptionalConfigArray(OLD_CONFIG_KEY) ?? [];
|
|
2306
|
-
if (legacyConfigs.length) {
|
|
2472
|
+
if (legacyConfigs.length && !loggedDeprecationWarning) {
|
|
2473
|
+
loggedDeprecationWarning = true;
|
|
2307
2474
|
logger.warn(
|
|
2308
2475
|
`DEPRECATION WARNING: The ${OLD_CONFIG_KEY} config has been replaced by ${NEW_CONFIG_KEY}, see https://backstage.io/docs/auth/service-to-service-auth`
|
|
2309
2476
|
);
|
|
@@ -2311,13 +2478,29 @@ class ExternalTokenHandler {
|
|
|
2311
2478
|
for (const handlerConfig of legacyConfigs) {
|
|
2312
2479
|
legacyHandler.addOld(handlerConfig);
|
|
2313
2480
|
}
|
|
2314
|
-
return new ExternalTokenHandler(Object.values(handlers));
|
|
2481
|
+
return new ExternalTokenHandler(ownPluginId, Object.values(handlers));
|
|
2315
2482
|
}
|
|
2316
2483
|
async verifyToken(token) {
|
|
2317
2484
|
for (const handler of this.handlers) {
|
|
2318
2485
|
const result = await handler.verifyToken(token);
|
|
2319
2486
|
if (result) {
|
|
2320
|
-
|
|
2487
|
+
const { allAccessRestrictions, ...rest } = result;
|
|
2488
|
+
if (allAccessRestrictions) {
|
|
2489
|
+
const accessRestrictions = allAccessRestrictions.get(
|
|
2490
|
+
this.ownPluginId
|
|
2491
|
+
);
|
|
2492
|
+
if (!accessRestrictions) {
|
|
2493
|
+
const valid = [...allAccessRestrictions.keys()].map((k) => `'${k}'`).join(", ");
|
|
2494
|
+
throw new errors.NotAllowedError(
|
|
2495
|
+
`This token's access is restricted to plugin(s) ${valid}`
|
|
2496
|
+
);
|
|
2497
|
+
}
|
|
2498
|
+
return {
|
|
2499
|
+
...rest,
|
|
2500
|
+
accessRestrictions
|
|
2501
|
+
};
|
|
2502
|
+
}
|
|
2503
|
+
return rest;
|
|
2321
2504
|
}
|
|
2322
2505
|
}
|
|
2323
2506
|
return void 0;
|
|
@@ -2338,16 +2521,7 @@ const authServiceFactory = backendPluginApi.createServiceFactory({
|
|
|
2338
2521
|
// new auth services in the new backend system.
|
|
2339
2522
|
tokenManager: backendPluginApi.coreServices.tokenManager
|
|
2340
2523
|
},
|
|
2341
|
-
async
|
|
2342
|
-
const externalTokens = ExternalTokenHandler.create({
|
|
2343
|
-
config,
|
|
2344
|
-
logger
|
|
2345
|
-
});
|
|
2346
|
-
return {
|
|
2347
|
-
externalTokens
|
|
2348
|
-
};
|
|
2349
|
-
},
|
|
2350
|
-
async factory({ config, discovery, plugin, tokenManager, logger, database }, { externalTokens }) {
|
|
2524
|
+
async factory({ config, discovery, plugin, tokenManager, logger, database }) {
|
|
2351
2525
|
const disableDefaultAuthPolicy = Boolean(
|
|
2352
2526
|
config.getOptionalBoolean(
|
|
2353
2527
|
"backend.auth.dangerouslyDisableDefaultAuthPolicy"
|
|
@@ -2367,6 +2541,11 @@ const authServiceFactory = backendPluginApi.createServiceFactory({
|
|
|
2367
2541
|
publicKeyStore,
|
|
2368
2542
|
discovery
|
|
2369
2543
|
});
|
|
2544
|
+
const externalTokens = ExternalTokenHandler.create({
|
|
2545
|
+
ownPluginId: plugin.getId(),
|
|
2546
|
+
config,
|
|
2547
|
+
logger
|
|
2548
|
+
});
|
|
2370
2549
|
return new DefaultAuthService(
|
|
2371
2550
|
userTokens,
|
|
2372
2551
|
pluginTokens,
|