Skip to content

Commit

Permalink
Rename permission backend request and response types
Browse files Browse the repository at this point in the history
Signed-off-by: Joe Porpeglia <[email protected]>
  • Loading branch information
joeporpeglia committed Mar 25, 2022
1 parent 970814e commit e43290c
Show file tree
Hide file tree
Showing 14 changed files with 113 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

import { PermissionApi } from '@backstage/plugin-permission-react';
import {
AuthorizeDecision,
AuthorizeQuery,
EvaluatePermissionResponse,
EvaluatePermissionRequest,
AuthorizeResult,
} from '@backstage/plugin-permission-common';

Expand All @@ -31,12 +31,14 @@ import {
export class MockPermissionApi implements PermissionApi {
constructor(
private readonly requestHandler: (
request: AuthorizeQuery,
request: EvaluatePermissionRequest,
) => AuthorizeResult.ALLOW | AuthorizeResult.DENY = () =>
AuthorizeResult.ALLOW,
) {}

async authorize(request: AuthorizeQuery): Promise<AuthorizeDecision> {
async authorize(
request: EvaluatePermissionRequest,
): Promise<EvaluatePermissionResponse> {
return { result: this.requestHandler(request) };
}
}
38 changes: 21 additions & 17 deletions plugins/permission-backend/src/service/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ import {
} from '@backstage/plugin-auth-node';
import {
AuthorizeResult,
AuthorizeDecision,
AuthorizeQuery,
EvaluatePermissionResponse,
EvaluatePermissionRequest,
IdentifiedPermissionMessage,
AuthorizeRequest,
AuthorizeResponse,
EvaluatePermissionRequestBatch,
EvaluatePermissionResponseBatch,
isResourcePermission,
PermissionAttributes,
} from '@backstage/plugin-permission-common';
Expand Down Expand Up @@ -73,17 +73,19 @@ const permissionSchema = z.union([
}),
]);

const querySchema: z.ZodSchema<IdentifiedPermissionMessage<AuthorizeQuery>> =
const evaluatePermissionRequestSchema: z.ZodSchema<
IdentifiedPermissionMessage<EvaluatePermissionRequest>
> = z.object({
id: z.string(),
resourceRef: z.string().optional(),
permission: permissionSchema,
});

const evaluatePermissionRequestBatchSchema: z.ZodSchema<EvaluatePermissionRequestBatch> =
z.object({
id: z.string(),
resourceRef: z.string().optional(),
permission: permissionSchema,
items: z.array(evaluatePermissionRequestSchema),
});

const requestSchema: z.ZodSchema<AuthorizeRequest> = z.object({
items: z.array(querySchema),
});

/**
* Options required when constructing a new {@link express#Router} using
* {@link createRouter}.
Expand All @@ -99,12 +101,12 @@ export interface RouterOptions {
}

const handleRequest = async (
requests: IdentifiedPermissionMessage<AuthorizeQuery>[],
requests: IdentifiedPermissionMessage<EvaluatePermissionRequest>[],
user: BackstageIdentityResponse | undefined,
policy: PermissionPolicy,
permissionIntegrationClient: PermissionIntegrationClient,
authHeader?: string,
): Promise<IdentifiedPermissionMessage<AuthorizeDecision>[]> => {
): Promise<IdentifiedPermissionMessage<EvaluatePermissionResponse>[]> => {
const applyConditionsLoaderFor = memoize((pluginId: string) => {
return new DataLoader<
ApplyConditionsRequestEntry,
Expand Down Expand Up @@ -184,15 +186,17 @@ export async function createRouter(
router.post(
'/authorize',
async (
req: Request<AuthorizeRequest>,
res: Response<AuthorizeResponse>,
req: Request<EvaluatePermissionRequestBatch>,
res: Response<EvaluatePermissionResponseBatch>,
) => {
const token = getBearerTokenFromAuthorizationHeader(
req.header('authorization'),
);
const user = token ? await identity.authenticate(token) : undefined;

const parseResult = requestSchema.safeParse(req.body);
const parseResult = evaluatePermissionRequestBatchSchema.safeParse(
req.body,
);

if (!parseResult.success) {
throw new InputError(parseResult.error.toString());
Expand Down
10 changes: 5 additions & 5 deletions plugins/permission-common/src/PermissionClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { setupServer } from 'msw/node';
import { ConfigReader } from '@backstage/config';
import { PermissionClient } from './PermissionClient';
import {
AuthorizeQuery,
EvaluatePermissionRequest,
AuthorizeResult,
IdentifiedPermissionMessage,
} from './types/api';
Expand Down Expand Up @@ -59,7 +59,7 @@ describe('PermissionClient', () => {

const mockAuthorizeHandler = jest.fn((req, res, { json }: RestContext) => {
const responses = req.body.items.map(
(a: IdentifiedPermissionMessage<AuthorizeQuery>) => ({
(a: IdentifiedPermissionMessage<EvaluatePermissionRequest>) => ({
id: a.id,
result: AuthorizeResult.ALLOW,
}),
Expand Down Expand Up @@ -147,7 +147,7 @@ describe('PermissionClient', () => {
mockAuthorizeHandler.mockImplementationOnce(
(req, res, { json }: RestContext) => {
const responses = req.body.items.map(
(a: IdentifiedPermissionMessage<AuthorizeQuery>) => ({
(a: IdentifiedPermissionMessage<EvaluatePermissionRequest>) => ({
id: a.id,
outcome: AuthorizeResult.ALLOW,
}),
Expand All @@ -165,7 +165,7 @@ describe('PermissionClient', () => {
mockAuthorizeHandler.mockImplementationOnce(
(req, res, { json }: RestContext) => {
const responses = req.body.map(
(a: IdentifiedPermissionMessage<AuthorizeQuery>) => ({
(a: IdentifiedPermissionMessage<EvaluatePermissionRequest>) => ({
id: a.id,
result: AuthorizeResult.DENY,
}),
Expand All @@ -189,7 +189,7 @@ describe('PermissionClient', () => {
mockAuthorizeHandler.mockImplementationOnce(
(req, res, { json }: RestContext) => {
const responses = req.body.map(
(a: IdentifiedPermissionMessage<AuthorizeQuery>) => ({
(a: IdentifiedPermissionMessage<EvaluatePermissionRequest>) => ({
id: a.id,
outcome: AuthorizeResult.DENY,
}),
Expand Down
20 changes: 10 additions & 10 deletions plugins/permission-common/src/PermissionClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ import * as uuid from 'uuid';
import { z } from 'zod';
import {
AuthorizeResult,
AuthorizeQuery,
AuthorizeDecision,
EvaluatePermissionRequest,
EvaluatePermissionResponse,
IdentifiedPermissionMessage,
PermissionCriteria,
PermissionCondition,
AuthorizeResponse,
AuthorizeRequest,
EvaluatePermissionResponseBatch,
EvaluatePermissionRequestBatch,
} from './types/api';
import { DiscoveryApi } from './types/discovery';
import {
Expand Down Expand Up @@ -107,9 +107,9 @@ export class PermissionClient implements PermissionAuthorizer {
* @public
*/
async authorize(
queries: AuthorizeQuery[],
queries: EvaluatePermissionRequest[],
options?: AuthorizeRequestOptions,
): Promise<AuthorizeDecision[]> {
): Promise<EvaluatePermissionResponse[]> {
// TODO(permissions): it would be great to provide some kind of typing guarantee that
// conditional responses will only ever be returned for requests containing a resourceType
// but no resourceRef. That way clients who aren't prepared to handle filtering according
Expand All @@ -119,7 +119,7 @@ export class PermissionClient implements PermissionAuthorizer {
return queries.map(_ => ({ result: AuthorizeResult.ALLOW }));
}

const request: AuthorizeRequest = {
const request: EvaluatePermissionRequestBatch = {
items: queries.map(query => ({
id: uuid.v4(),
...query,
Expand All @@ -145,7 +145,7 @@ export class PermissionClient implements PermissionAuthorizer {
const responsesById = responseBody.items.reduce((acc, r) => {
acc[r.id] = r;
return acc;
}, {} as Record<string, IdentifiedPermissionMessage<AuthorizeDecision>>);
}, {} as Record<string, IdentifiedPermissionMessage<EvaluatePermissionResponse>>);

return request.items.map(query => responsesById[query.id]);
}
Expand All @@ -155,9 +155,9 @@ export class PermissionClient implements PermissionAuthorizer {
}

private assertValidResponse(
request: AuthorizeRequest,
request: EvaluatePermissionRequestBatch,
json: any,
): asserts json is AuthorizeResponse {
): asserts json is EvaluatePermissionResponseBatch {
const authorizedResponses = responseSchema.parse(json);
const responseIds = authorizedResponses.items.map(r => r.id);
const hasAllRequestIds = request.items.every(r =>
Expand Down
40 changes: 21 additions & 19 deletions plugins/permission-common/src/types/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,21 +90,6 @@ export type PolicyDecision =
| DefinitivePolicyDecision
| ConditionalPolicyDecision;

/**
* An individual authorization request for {@link PermissionClient#authorize}.
* @public
*/
export type AuthorizeQuery = {
permission: Permission;
resourceRef?: string;
};

/**
* A batch of authorization requests from {@link PermissionClient#authorize}.
* @public
*/
export type AuthorizeRequest = PermissionMessageBatch<AuthorizeQuery>;

/**
* A condition returned with a CONDITIONAL authorization response.
*
Expand Down Expand Up @@ -159,18 +144,35 @@ export type PermissionCriteria<TQuery> =
| TQuery;

/**
* An individual authorization response from {@link PermissionClient#authorize}.
* An individual request sent to the permission backend.
* @public
*/
export type EvaluatePermissionRequest = {
permission: Permission;
resourceRef?: string;
};

/**
* A batch of requests sent to the permission backend.
* @public
*/
export type EvaluatePermissionRequestBatch =
PermissionMessageBatch<EvaluatePermissionRequest>;

/**
* An individual response from the permission backend.
* @public
*/
export type AuthorizeDecision =
export type EvaluatePermissionResponse =
| { result: AuthorizeResult.ALLOW | AuthorizeResult.DENY }
| {
result: AuthorizeResult.CONDITIONAL;
conditions: PermissionCriteria<PermissionCondition>;
};

/**
* A batch of authorization responses from {@link PermissionClient#authorize}.
* A batch of responses the permission backend.
* @public
*/
export type AuthorizeResponse = PermissionMessageBatch<AuthorizeDecision>;
export type EvaluatePermissionResponseBatch =
PermissionMessageBatch<EvaluatePermissionResponse>;
8 changes: 4 additions & 4 deletions plugins/permission-common/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@

export { AuthorizeResult } from './api';
export type {
AuthorizeQuery,
AuthorizeRequest,
AuthorizeDecision,
AuthorizeResponse,
EvaluatePermissionRequest,
EvaluatePermissionRequestBatch,
EvaluatePermissionResponse,
EvaluatePermissionResponseBatch,
IdentifiedPermissionMessage,
PermissionMessageBatch,
ConditionalPolicyDecision,
Expand Down
6 changes: 3 additions & 3 deletions plugins/permission-common/src/types/permission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import { AuthorizeQuery, AuthorizeDecision } from './api';
import { EvaluatePermissionRequest, EvaluatePermissionResponse } from './api';

/**
* The attributes related to a given permission; these should be generic and widely applicable to
Expand Down Expand Up @@ -94,9 +94,9 @@ export type ResourcePermission<TResourceType extends string = string> =
*/
export interface PermissionAuthorizer {
authorize(
queries: AuthorizeQuery[],
requests: EvaluatePermissionRequest[],
options?: AuthorizeRequestOptions,
): Promise<AuthorizeDecision[]>;
): Promise<EvaluatePermissionResponse[]>;
}

/**
Expand Down
4 changes: 2 additions & 2 deletions plugins/permission-node/src/ServerPermissionClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import { ServerPermissionClient } from './ServerPermissionClient';
import {
IdentifiedPermissionMessage,
AuthorizeQuery,
EvaluatePermissionRequest,
AuthorizeResult,
createPermission,
} from '@backstage/plugin-permission-common';
Expand All @@ -33,7 +33,7 @@ import { RestContext, rest } from 'msw';
const server = setupServer();
const mockAuthorizeHandler = jest.fn((req, res, { json }: RestContext) => {
const responses = req.body.items.map(
(r: IdentifiedPermissionMessage<AuthorizeQuery>) => ({
(r: IdentifiedPermissionMessage<EvaluatePermissionRequest>) => ({
id: r.id,
result: AuthorizeResult.ALLOW,
}),
Expand Down
12 changes: 6 additions & 6 deletions plugins/permission-node/src/ServerPermissionClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import {
} from '@backstage/backend-common';
import { Config } from '@backstage/config';
import {
AuthorizeQuery,
EvaluatePermissionRequest,
AuthorizeRequestOptions,
AuthorizeDecision,
EvaluatePermissionResponse,
AuthorizeResult,
PermissionClient,
PermissionAuthorizer,
Expand Down Expand Up @@ -78,9 +78,9 @@ export class ServerPermissionClient implements PermissionAuthorizer {
}

async authorize(
queries: AuthorizeQuery[],
requests: EvaluatePermissionRequest[],
options?: AuthorizeRequestOptions,
): Promise<AuthorizeDecision[]> {
): Promise<EvaluatePermissionResponse[]> {
// Check if permissions are enabled before validating the server token. That
// way when permissions are disabled, the noop token manager can be used
// without fouling up the logic inside the ServerPermissionClient, because
Expand All @@ -89,9 +89,9 @@ export class ServerPermissionClient implements PermissionAuthorizer {
!this.permissionEnabled ||
(await this.isValidServerToken(options?.token))
) {
return queries.map(_ => ({ result: AuthorizeResult.ALLOW }));
return requests.map(_ => ({ result: AuthorizeResult.ALLOW }));
}
return this.permissionClient.authorize(queries, options);
return this.permissionClient.authorize(requests, options);
}

private async isValidServerToken(
Expand Down
7 changes: 5 additions & 2 deletions plugins/permission-node/src/policy/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import {
AuthorizeQuery,
EvaluatePermissionRequest,
PolicyDecision,
} from '@backstage/plugin-permission-common';
import { BackstageIdentityResponse } from '@backstage/plugin-auth-node';
Expand All @@ -31,7 +31,10 @@ import { BackstageIdentityResponse } from '@backstage/plugin-auth-node';
*
* @public
*/
export type PolicyAuthorizeQuery = Omit<AuthorizeQuery, 'resourceRef'>;
export type PolicyAuthorizeQuery = Omit<
EvaluatePermissionRequest,
'resourceRef'
>;

/**
* A policy to evaluate authorization requests for any permissioned action performed in Backstage.
Expand Down
Loading

0 comments on commit e43290c

Please sign in to comment.