diff --git a/plugins/permission-common/api-report.md b/plugins/permission-common/api-report.md index 4f1b84d5be..cbf1877c75 100644 --- a/plugins/permission-common/api-report.md +++ b/plugins/permission-common/api-report.md @@ -21,7 +21,7 @@ export type AuthorizeResponse = } | { result: AuthorizeResult.CONDITIONAL; - conditions: PermissionCriteria; + conditions: PermissionCriteria; }; // @public @@ -36,6 +36,11 @@ export type DiscoveryApi = { getBaseUrl(pluginId: string): Promise; }; +// @public +export type Identified = T & { + id: string; +}; + // @public export function isCreatePermission(permission: Permission): boolean; @@ -62,7 +67,7 @@ export type PermissionAttributes = { // @public export class PermissionClient { - constructor(options: { discoveryApi: DiscoveryApi }); + constructor(options: { discoveryApi: DiscoveryApi; enabled?: boolean }); authorize( requests: AuthorizeRequest[], options?: AuthorizeRequestOptions, diff --git a/plugins/permission-common/config.d.ts b/plugins/permission-common/config.d.ts new file mode 100644 index 0000000000..2378fa4a71 --- /dev/null +++ b/plugins/permission-common/config.d.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface Config { + /** Configuration options for Backstage permissions and authorization */ + permission?: { + /** + * Whether authorization is enabled in Backstage. Defaults to false, which means authorization + * requests will be automatically allowed without invoking the authorization policy. + * @visibility frontend + */ + enabled?: boolean; + }; +} diff --git a/plugins/permission-common/package.json b/plugins/permission-common/package.json index f6d66934e2..d2ebf85b36 100644 --- a/plugins/permission-common/package.json +++ b/plugins/permission-common/package.json @@ -21,8 +21,10 @@ ], "license": "Apache-2.0", "files": [ - "dist" + "dist", + "config.d.ts" ], + "configSchema": "config.d.ts", "scripts": { "build": "backstage-cli build", "lint": "backstage-cli lint", diff --git a/plugins/permission-common/src/PermissionClient.test.ts b/plugins/permission-common/src/PermissionClient.test.ts index a461078e1a..06e2ae0561 100644 --- a/plugins/permission-common/src/PermissionClient.test.ts +++ b/plugins/permission-common/src/PermissionClient.test.ts @@ -30,7 +30,10 @@ const discoveryApi: DiscoveryApi = { return mockBaseUrl; }, }; -const client: PermissionClient = new PermissionClient({ discoveryApi }); +const client: PermissionClient = new PermissionClient({ + discoveryApi, + enabled: true, +}); const mockPermission: Permission = { name: 'test.permission', @@ -141,5 +144,24 @@ describe('PermissionClient', () => { client.authorize([mockAuthorizeRequest], { token }), ).rejects.toThrowError(/invalid input/i); }); + + it('should allow all when authorization is not enabled', async () => { + mockAuthorizeHandler.mockImplementationOnce( + (req, res, { json }: RestContext) => { + const responses = req.body.map((a: Identified) => ({ + id: a.id, + outcome: AuthorizeResult.DENY, + })); + + return res(json(responses)); + }, + ); + const disabled = new PermissionClient({ discoveryApi, enabled: false }); + const response = await disabled.authorize([mockAuthorizeRequest]); + expect(response[0]).toEqual( + expect.objectContaining({ result: AuthorizeResult.ALLOW }), + ); + expect(mockAuthorizeHandler).not.toBeCalled(); + }); }); }); diff --git a/plugins/permission-common/src/PermissionClient.ts b/plugins/permission-common/src/PermissionClient.ts index 5c32ecb139..5b17ccd333 100644 --- a/plugins/permission-common/src/PermissionClient.ts +++ b/plugins/permission-common/src/PermissionClient.ts @@ -71,10 +71,12 @@ export type AuthorizeRequestOptions = { * @public */ export class PermissionClient { + private readonly enabled: boolean; private readonly discoveryApi: DiscoveryApi; - constructor(options: { discoveryApi: DiscoveryApi }) { + constructor(options: { discoveryApi: DiscoveryApi; enabled?: boolean }) { this.discoveryApi = options.discoveryApi; + this.enabled = options.enabled ?? false; } /** @@ -102,6 +104,10 @@ export class PermissionClient { // but no resourceRef. That way clients who aren't prepared to handle filtering according // to conditions can be guaranteed that they won't unexpectedly get a CONDITIONAL response. + if (!this.enabled) { + return requests.map(_ => ({ result: AuthorizeResult.ALLOW })); + } + const identifiedRequests: Identified[] = requests.map( request => ({ id: uuid.v4(), diff --git a/plugins/permission-common/src/types/api.ts b/plugins/permission-common/src/types/api.ts index 3cdf923978..7f4cd0730b 100644 --- a/plugins/permission-common/src/types/api.ts +++ b/plugins/permission-common/src/types/api.ts @@ -16,6 +16,11 @@ import { Permission } from './permission'; +/** + * A request with a UUID identifier, so that batched responses can be matched up with the original + * requests. + * @public + */ export type Identified = T & { id: string }; /** @@ -77,5 +82,5 @@ export type AuthorizeResponse = | { result: AuthorizeResult.ALLOW | AuthorizeResult.DENY } | { result: AuthorizeResult.CONDITIONAL; - conditions: PermissionCriteria; + conditions: PermissionCriteria; }; diff --git a/plugins/permission-common/src/types/index.ts b/plugins/permission-common/src/types/index.ts index 95b8fc2a83..e21fa19e8b 100644 --- a/plugins/permission-common/src/types/index.ts +++ b/plugins/permission-common/src/types/index.ts @@ -18,6 +18,7 @@ export { AuthorizeResult } from './api'; export type { AuthorizeRequest, AuthorizeResponse, + Identified, PermissionCondition, PermissionCriteria, } from './api';