Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@backstage/plugin-permission-common

Package Overview
Dependencies
Maintainers
4
Versions
500
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@backstage/plugin-permission-common - npm Package Compare versions

Comparing version 0.0.0-nightly-20220923026 to 0.0.0-nightly-20220923030237

223

CHANGELOG.md
# @backstage/plugin-permission-common
## 0.0.0-nightly-20220923030237
### Patch Changes
- Updated dependencies
- @backstage/config@0.0.0-nightly-20220923030237
- @backstage/errors@0.0.0-nightly-20220923030237
## 0.6.4
### Patch Changes
- 7d47def9c4: Removed dependency on `@types/jest`.
- 667d917488: Updated dependency `msw` to `^0.47.0`.
- 87ec2ba4d6: Updated dependency `msw` to `^0.46.0`.
- bf5e9030eb: Updated dependency `msw` to `^0.45.0`.
- Updated dependencies
- @backstage/config@1.0.2
- @backstage/errors@1.1.1
## 0.6.4-next.2
### Patch Changes
- 7d47def9c4: Removed dependency on `@types/jest`.
- Updated dependencies
- @backstage/config@1.0.2-next.0
- @backstage/errors@1.1.1-next.0
## 0.6.4-next.1
### Patch Changes
- 667d917488: Updated dependency `msw` to `^0.47.0`.
- 87ec2ba4d6: Updated dependency `msw` to `^0.46.0`.
## 0.6.4-next.0
### Patch Changes
- bf5e9030eb: Updated dependency `msw` to `^0.45.0`.
## 0.6.3
### Patch Changes
- a70869e775: Updated dependency `msw` to `^0.43.0`.
- 8006d0f9bf: Updated dependency `msw` to `^0.44.0`.
- Updated dependencies
- @backstage/errors@1.1.0
## 0.6.3-next.1
### Patch Changes
- a70869e775: Updated dependency `msw` to `^0.43.0`.
## 0.6.3-next.0
### Patch Changes
- Updated dependencies
- @backstage/errors@1.1.0-next.0
## 0.6.2
### Patch Changes
- 8f7b1835df: Updated dependency `msw` to `^0.41.0`.
## 0.6.2-next.0
### Patch Changes
- 8f7b1835df: Updated dependency `msw` to `^0.41.0`.
## 0.6.1
### Patch Changes
- Updated dependencies
- @backstage/config@1.0.1
## 0.6.1-next.0
### Patch Changes
- Updated dependencies
- @backstage/config@1.0.1-next.0
## 0.6.0
### Minor Changes
- 8012ac46a0: Add `resourceType` property to `PermissionCondition` type to allow matching them with `ResourcePermission` instances.
- c98d271466: Refactor api types into more specific, decoupled names.
- **BREAKING:**
- Renamed `AuthorizeDecision` to `EvaluatePermissionResponse`
- Renamed `AuthorizeQuery` to `EvaluatePermissionRequest`
- Renamed `AuthorizeRequest` to `EvaluatePermissionRequestBatch`
- Renamed `AuthorizeResponse` to `EvaluatePermissionResponseBatch`
- Renamed `Identified` to `IdentifiedPermissionMessage`
- Add `PermissionMessageBatch` helper type
- Add `ConditionalPolicyDecision`, `DefinitivePolicyDecision`, and `PolicyDecision` types from `@backstage/plugin-permission-node`
### Patch Changes
- 90754d4fa9: Removed [strict](https://github.com/colinhacks/zod#strict) validation from `PermissionCriteria` schemas to support backward-compatible changes.
- 2b07063d77: Added `PermissionEvaluator`, which will replace the existing `PermissionAuthorizer` interface. This new interface provides stronger type safety and validation by splitting `PermissionAuthorizer.authorize()` into two methods:
- `authorize()`: Used when the caller requires a definitive decision.
- `authorizeConditional()`: Used when the caller can optimize the evaluation of any conditional decisions. For example, a plugin backend may want to use conditions in a database query instead of evaluating each resource in memory.
- 8012ac46a0: Add `isPermission` helper method.
- 95284162d6: - Add more specific `Permission` types.
- Add `createPermission` helper to infer the appropriate type for some permission input.
- Add `isResourcePermission` helper to refine Permissions to ResourcePermissions.
## 0.6.0-next.1
### Patch Changes
- 2b07063d77: Added `PermissionEvaluator`, which will replace the existing `PermissionAuthorizer` interface. This new interface provides stronger type safety and validation by splitting `PermissionAuthorizer.authorize()` into two methods:
- `authorize()`: Used when the caller requires a definitive decision.
- `authorizeConditional()`: Used when the caller can optimize the evaluation of any conditional decisions. For example, a plugin backend may want to use conditions in a database query instead of evaluating each resource in memory.
## 0.6.0-next.0
### Minor Changes
- 8012ac46a0: Add `resourceType` property to `PermissionCondition` type to allow matching them with `ResourcePermission` instances.
- c98d271466: Refactor api types into more specific, decoupled names.
- **BREAKING:**
- Renamed `AuthorizeDecision` to `EvaluatePermissionResponse`
- Renamed `AuthorizeQuery` to `EvaluatePermissionRequest`
- Renamed `AuthorizeRequest` to `EvaluatePermissionRequestBatch`
- Renamed `AuthorizeResponse` to `EvaluatePermissionResponseBatch`
- Renamed `Identified` to `IdentifiedPermissionMessage`
- Add `PermissionMessageBatch` helper type
- Add `ConditionalPolicyDecision`, `DefinitivePolicyDecision`, and `PolicyDecision` types from `@backstage/plugin-permission-node`
### Patch Changes
- 8012ac46a0: Add `isPermission` helper method.
- 95284162d6: - Add more specific `Permission` types.
- Add `createPermission` helper to infer the appropriate type for some permission input.
- Add `isResourcePermission` helper to refine Permissions to ResourcePermissions.
## 0.5.3
### Patch Changes
- f24ef7864e: Minor typo fixes
- Updated dependencies
- @backstage/config@1.0.0
- @backstage/errors@1.0.0
## 0.5.2
### Patch Changes
- 79b9d8a861: Add api doc comments to `Permission` type properties.
## 0.5.1
### Patch Changes
- Fix for the previous release with missing type declarations.
- Updated dependencies
- @backstage/config@0.1.15
- @backstage/errors@0.2.2
## 0.5.0
### Minor Changes
- 8c646beb24: **BREAKING** `PermissionCriteria` now requires at least one condition in `anyOf` and `allOf` arrays. This addresses some ambiguous behavior outlined in #9280.
### Patch Changes
- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5
- c77c5c7eb6: Added `backstage.role` to `package.json`
- Updated dependencies
- @backstage/errors@0.2.1
- @backstage/config@0.1.14
## 0.4.0
### Minor Changes
- b768259244: **BREAKING**: Authorize API request and response types have been updated. The existing `AuthorizeRequest` and `AuthorizeResponse` types now match the entire request and response objects for the /authorize endpoint, and new types `AuthorizeQuery` and `AuthorizeDecision` have been introduced for individual items in the request and response batches respectively.
**BREAKING**: PermissionClient has been updated to use the new request and response format in the latest version of @backstage/permission-backend.
### Patch Changes
- Updated dependencies
- @backstage/config@0.1.13
## 0.4.0-next.0
### Minor Changes
- b768259244: **BREAKING**: Authorize API request and response types have been updated. The existing `AuthorizeRequest` and `AuthorizeResponse` types now match the entire request and response objects for the /authorize endpoint, and new types `AuthorizeQuery` and `AuthorizeDecision` have been introduced for individual items in the request and response batches respectively.
**BREAKING**: PermissionClient has been updated to use the new request and response format in the latest version of @backstage/permission-backend.
### Patch Changes
- Updated dependencies
- @backstage/config@0.1.13-next.0
## 0.3.1
### Patch Changes
- Updated dependencies
- @backstage/config@0.1.12
- @backstage/errors@0.2.0
## 0.3.0

@@ -4,0 +227,0 @@

134

dist/index.cjs.js

@@ -40,2 +40,11 @@ 'use strict';

function isPermission(permission, comparedPermission) {
return permission.name === comparedPermission.name;
}
function isResourcePermission(permission, resourceType) {
if (!("resourceType" in permission)) {
return false;
}
return !resourceType || permission.resourceType === resourceType;
}
function isCreatePermission(permission) {

@@ -53,15 +62,71 @@ return permission.attributes.action === "create";

}
function toPermissionEvaluator(permissionAuthorizer) {
return {
authorize: async (requests, options) => {
const response = await permissionAuthorizer.authorize(requests, options);
return response;
},
authorizeConditional(requests, options) {
const parsedRequests = requests;
return permissionAuthorizer.authorize(parsedRequests, options);
}
};
}
const permissionCriteriaSchema = zod.z.lazy(() => zod.z.object({
rule: zod.z.string(),
params: zod.z.array(zod.z.unknown())
}).or(zod.z.object({ anyOf: zod.z.array(permissionCriteriaSchema) })).or(zod.z.object({ allOf: zod.z.array(permissionCriteriaSchema) })).or(zod.z.object({ not: permissionCriteriaSchema })));
const responseSchema = zod.z.array(zod.z.object({
id: zod.z.string(),
function createPermission({
name,
attributes,
resourceType
}) {
if (resourceType) {
return {
type: "resource",
name,
attributes,
resourceType
};
}
return {
type: "basic",
name,
attributes
};
}
const permissionCriteriaSchema = zod.z.lazy(
() => zod.z.object({
rule: zod.z.string(),
resourceType: zod.z.string(),
params: zod.z.array(zod.z.unknown())
}).or(zod.z.object({ anyOf: zod.z.array(permissionCriteriaSchema).nonempty() })).or(zod.z.object({ allOf: zod.z.array(permissionCriteriaSchema).nonempty() })).or(zod.z.object({ not: permissionCriteriaSchema }))
);
const authorizePermissionResponseSchema = zod.z.object({
result: zod.z.literal(AuthorizeResult.ALLOW).or(zod.z.literal(AuthorizeResult.DENY))
}).or(zod.z.object({
id: zod.z.string(),
result: zod.z.literal(AuthorizeResult.CONDITIONAL),
conditions: permissionCriteriaSchema
})));
});
const queryPermissionResponseSchema = zod.z.union([
zod.z.object({
result: zod.z.literal(AuthorizeResult.ALLOW).or(zod.z.literal(AuthorizeResult.DENY))
}),
zod.z.object({
result: zod.z.literal(AuthorizeResult.CONDITIONAL),
pluginId: zod.z.string(),
resourceType: zod.z.string(),
conditions: permissionCriteriaSchema
})
]);
const responseSchema = (itemSchema, ids) => zod.z.object({
items: zod.z.array(
zod.z.intersection(
zod.z.object({
id: zod.z.string()
}),
itemSchema
)
).refine(
(items) => items.length === ids.size && items.every(({ id }) => ids.has(id)),
{
message: "Items in response do not match request"
}
)
});
class PermissionClient {

@@ -74,13 +139,25 @@ constructor(options) {

async authorize(requests, options) {
return this.makeRequest(
requests,
authorizePermissionResponseSchema,
options
);
}
async authorizeConditional(queries, options) {
return this.makeRequest(queries, queryPermissionResponseSchema, options);
}
async makeRequest(queries, itemSchema, options) {
if (!this.enabled) {
return requests.map((_) => ({ result: AuthorizeResult.ALLOW }));
return queries.map((_) => ({ result: AuthorizeResult.ALLOW }));
}
const identifiedRequests = requests.map((request) => ({
id: uuid__namespace.v4(),
...request
}));
const request = {
items: queries.map((query) => ({
id: uuid__namespace.v4(),
...query
}))
};
const permissionApi = await this.discovery.getBaseUrl("permission");
const response = await fetch__default["default"](`${permissionApi}/authorize`, {
method: "POST",
body: JSON.stringify(identifiedRequests),
body: JSON.stringify(request),
headers: {

@@ -94,9 +171,12 @@ ...this.getAuthorizationHeader(options == null ? void 0 : options.token),

}
const identifiedResponses = await response.json();
this.assertValidResponses(identifiedRequests, identifiedResponses);
const responsesById = identifiedResponses.reduce((acc, r) => {
const responseBody = await response.json();
const parsedResponse = responseSchema(
itemSchema,
new Set(request.items.map(({ id }) => id))
).parse(responseBody);
const responsesById = parsedResponse.items.reduce((acc, r) => {
acc[r.id] = r;
return acc;
}, {});
return identifiedRequests.map((request) => responsesById[request.id]);
return request.items.map((query) => responsesById[query.id]);
}

@@ -106,10 +186,2 @@ getAuthorizationHeader(token) {

}
assertValidResponses(requests, json) {
const authorizedResponses = responseSchema.parse(json);
const responseIds = authorizedResponses.map((r) => r.id);
const hasAllRequestIds = requests.every((r) => responseIds.includes(r.id));
if (!hasAllRequestIds) {
throw new Error("Unexpected authorization response from permission-backend");
}
}
}

@@ -119,6 +191,10 @@

exports.PermissionClient = PermissionClient;
exports.createPermission = createPermission;
exports.isCreatePermission = isCreatePermission;
exports.isDeletePermission = isDeletePermission;
exports.isPermission = isPermission;
exports.isReadPermission = isReadPermission;
exports.isResourcePermission = isResourcePermission;
exports.isUpdatePermission = isUpdatePermission;
exports.toPermissionEvaluator = toPermissionEvaluator;
//# sourceMappingURL=index.cjs.js.map

@@ -12,4 +12,30 @@ import { Config } from '@backstage/config';

/**
* Generic type for building {@link Permission} types.
* @public
*/
declare type PermissionBase<TType extends string, TFields extends object> = {
/**
* The name of the permission.
*/
name: string;
/**
* {@link PermissionAttributes} which describe characteristics of the permission, to help
* policy authors make consistent decisions for similar permissions without referring to them
* all by name.
*/
attributes: PermissionAttributes;
} & {
/**
* String value indicating the type of the permission (e.g. 'basic',
* 'resource'). The allowed authorization flows in the permission system
* depend on the type. For example, a `resourceRef` should only be provided
* when authorizing permissions of type 'resource'.
*/
type: TType;
} & TFields;
/**
* A permission that can be checked through authorization.
*
* @remarks
*
* Permissions are the "what" part of authorization, the action to be performed. This may be reading

@@ -23,13 +49,27 @@ * an entity from the catalog, executing a software template, or any other action a plugin author

*/
declare type Permission = {
name: string;
attributes: PermissionAttributes;
resourceType?: string;
};
declare type Permission = BasicPermission | ResourcePermission;
/**
* A standard {@link Permission} with no additional capabilities or restrictions.
* @public
*/
declare type BasicPermission = PermissionBase<'basic', {}>;
/**
* ResourcePermissions are {@link Permission}s that can be authorized based on
* characteristics of a resource such a catalog entity.
* @public
*/
declare type ResourcePermission<TResourceType extends string = string> = PermissionBase<'resource', {
/**
* Denotes the type of the resource whose resourceRef should be passed when
* authorizing.
*/
resourceType: TResourceType;
}>;
/**
* A client interacting with the permission backend can implement this authorizer interface.
* @public
* @deprecated Use {@link @backstage/plugin-permission-common#PermissionEvaluator} instead
*/
interface PermissionAuthorizer {
authorize(requests: AuthorizeRequest[], options?: AuthorizeRequestOptions): Promise<AuthorizeResponse[]>;
authorize(requests: EvaluatePermissionRequest[], options?: AuthorizeRequestOptions): Promise<EvaluatePermissionResponse[]>;
}

@@ -49,6 +89,13 @@ /**

*/
declare type Identified<T> = T & {
declare type IdentifiedPermissionMessage<T> = T & {
id: string;
};
/**
* A batch of request or response items.
* @public
*/
declare type PermissionMessageBatch<T> = {
items: IdentifiedPermissionMessage<T>[];
};
/**
* The result of an authorization request.

@@ -72,10 +119,37 @@ * @public

/**
* An authorization request for {@link PermissionClient#authorize}.
* A definitive decision returned by the {@link @backstage/plugin-permission-node#PermissionPolicy}.
*
* @remarks
*
* This indicates that the policy unconditionally allows (or denies) the request.
*
* @public
*/
declare type AuthorizeRequest = {
permission: Permission;
resourceRef?: string;
declare type DefinitivePolicyDecision = {
result: AuthorizeResult.ALLOW | AuthorizeResult.DENY;
};
/**
* A conditional decision returned by the {@link @backstage/plugin-permission-node#PermissionPolicy}.
*
* @remarks
*
* This indicates that the policy allows authorization for the request, given that the returned
* conditions hold when evaluated. The conditions will be evaluated by the corresponding plugin
* which knows about the referenced permission rules.
*
* @public
*/
declare type ConditionalPolicyDecision = {
result: AuthorizeResult.CONDITIONAL;
pluginId: string;
resourceType: string;
conditions: PermissionCriteria<PermissionCondition>;
};
/**
* A decision returned by the {@link @backstage/plugin-permission-node#PermissionPolicy}.
*
* @public
*/
declare type PolicyDecision = DefinitivePolicyDecision | ConditionalPolicyDecision;
/**
* A condition returned with a CONDITIONAL authorization response.

@@ -88,3 +162,4 @@ *

*/
declare type PermissionCondition<TParams extends unknown[] = unknown[]> = {
declare type PermissionCondition<TResourceType extends string = string, TParams extends unknown[] = unknown[]> = {
resourceType: TResourceType;
rule: string;

@@ -94,22 +169,120 @@ params: TParams;

/**
* Composes several {@link PermissionCondition}s as criteria with a nested AND/OR structure.
* Utility type to represent an array with 1 or more elements.
* @ignore
*/
declare type NonEmptyArray<T> = [T, ...T[]];
/**
* Represents a logical AND for the provided criteria.
* @public
*/
declare type PermissionCriteria<TQuery> = {
allOf: PermissionCriteria<TQuery>[];
} | {
anyOf: PermissionCriteria<TQuery>[];
} | {
declare type AllOfCriteria<TQuery> = {
allOf: NonEmptyArray<PermissionCriteria<TQuery>>;
};
/**
* Represents a logical OR for the provided criteria.
* @public
*/
declare type AnyOfCriteria<TQuery> = {
anyOf: NonEmptyArray<PermissionCriteria<TQuery>>;
};
/**
* Represents a negation of the provided criteria.
* @public
*/
declare type NotCriteria<TQuery> = {
not: PermissionCriteria<TQuery>;
} | TQuery;
};
/**
* An authorization response from {@link PermissionClient#authorize}.
* Composes several {@link PermissionCondition}s as criteria with a nested AND/OR structure.
* @public
*/
declare type AuthorizeResponse = {
result: AuthorizeResult.ALLOW | AuthorizeResult.DENY;
declare type PermissionCriteria<TQuery> = AllOfCriteria<TQuery> | AnyOfCriteria<TQuery> | NotCriteria<TQuery> | TQuery;
/**
* An individual request sent to the permission backend.
* @public
*/
declare type EvaluatePermissionRequest = {
permission: Permission;
resourceRef?: string;
};
/**
* A batch of requests sent to the permission backend.
* @public
*/
declare type EvaluatePermissionRequestBatch = PermissionMessageBatch<EvaluatePermissionRequest>;
/**
* An individual response from the permission backend.
*
* @remarks
*
* This response type is an alias of {@link PolicyDecision} to maintain separation between the
* {@link @backstage/plugin-permission-node#PermissionPolicy} interface and the permission backend
* api. They may diverge at some point in the future. The response
*
* @public
*/
declare type EvaluatePermissionResponse = PolicyDecision;
/**
* A batch of responses from the permission backend.
* @public
*/
declare type EvaluatePermissionResponseBatch = PermissionMessageBatch<EvaluatePermissionResponse>;
/**
* Request object for {@link PermissionEvaluator.authorize}. If a {@link ResourcePermission}
* is provided, it must include a corresponding `resourceRef`.
* @public
*/
declare type AuthorizePermissionRequest = {
permission: Exclude<Permission, ResourcePermission>;
resourceRef?: never;
} | {
result: AuthorizeResult.CONDITIONAL;
conditions: PermissionCriteria<PermissionCondition>;
permission: ResourcePermission;
resourceRef: string;
};
/**
* Response object for {@link PermissionEvaluator.authorize}.
* @public
*/
declare type AuthorizePermissionResponse = DefinitivePolicyDecision;
/**
* Request object for {@link PermissionEvaluator.authorizeConditional}.
* @public
*/
declare type QueryPermissionRequest = {
permission: ResourcePermission;
resourceRef?: never;
};
/**
* Response object for {@link PermissionEvaluator.authorizeConditional}.
* @public
*/
declare type QueryPermissionResponse = PolicyDecision;
/**
* A client interacting with the permission backend can implement this evaluator interface.
*
* @public
*/
interface PermissionEvaluator {
/**
* Evaluates {@link Permission | Permissions} and returns a definitive decision.
*/
authorize(requests: AuthorizePermissionRequest[], options?: EvaluatorRequestOptions): Promise<AuthorizePermissionResponse[]>;
/**
* Evaluates {@link ResourcePermission | ResourcePermissions} and returns both definitive and
* conditional decisions, depending on the configured
* {@link @backstage/plugin-permission-node#PermissionPolicy}. This method is useful when the
* caller needs more control over the processing of conditional decisions. For example, a plugin
* backend may want to use {@link PermissionCriteria | conditions} in a database query instead of
* evaluating each resource in memory.
*/
authorizeConditional(requests: QueryPermissionRequest[], options?: EvaluatorRequestOptions): Promise<QueryPermissionResponse[]>;
}
/**
* Options for {@link PermissionEvaluator} requests.
* The Backstage identity token should be defined if available.
* @public
*/
declare type EvaluatorRequestOptions = {
token?: string;
};

@@ -126,2 +299,14 @@ /**

/**
* Check if the two parameters are equivalent permissions.
* @public
*/
declare function isPermission<T extends Permission>(permission: Permission, comparedPermission: T): permission is T;
/**
* Check if a given permission is a {@link ResourcePermission}. When
* `resourceType` is supplied as the second parameter, also checks if
* the permission has the specified resource type.
* @public
*/
declare function isResourcePermission<T extends string = string>(permission: Permission, resourceType?: T): permission is ResourcePermission<T>;
/**
* Check if a given permission is related to a create action.

@@ -146,8 +331,35 @@ * @public

declare function isDeletePermission(permission: Permission): boolean;
/**
* Convert {@link PermissionAuthorizer} to {@link PermissionEvaluator}.
*
* @public
*/
declare function toPermissionEvaluator(permissionAuthorizer: PermissionAuthorizer): PermissionEvaluator;
/**
* Utility function for creating a valid {@link ResourcePermission}, inferring
* the appropriate type and resource type parameter.
*
* @public
*/
declare function createPermission<TResourceType extends string>(input: {
name: string;
attributes: PermissionAttributes;
resourceType: TResourceType;
}): ResourcePermission<TResourceType>;
/**
* Utility function for creating a valid {@link BasicPermission}.
*
* @public
*/
declare function createPermission(input: {
name: string;
attributes: PermissionAttributes;
}): BasicPermission;
/**
* An isomorphic client for requesting authorization for Backstage permissions.
* @public
*/
declare class PermissionClient implements PermissionAuthorizer {
declare class PermissionClient implements PermissionEvaluator {
private readonly enabled;

@@ -160,22 +372,13 @@ private readonly discovery;

/**
* Request authorization from the permission-backend for the given set of permissions.
*
* Authorization requests check that a given Backstage user can perform a protected operation,
* potentially for a specific resource (such as a catalog entity). The Backstage identity token
* should be included in the `options` if available.
*
* Permissions can be imported from plugins exposing them, such as `catalogEntityReadPermission`.
*
* The response will be either ALLOW or DENY when either the permission has no resourceType, or a
* resourceRef is provided in the request. For permissions with a resourceType, CONDITIONAL may be
* returned if no resourceRef is provided in the request. Conditional responses are intended only
* for backends which have access to the data source for permissioned resources, so that filters
* can be applied when loading collections of resources.
* @public
* {@inheritdoc PermissionEvaluator.authorize}
*/
authorize(requests: AuthorizeRequest[], options?: AuthorizeRequestOptions): Promise<AuthorizeResponse[]>;
authorize(requests: AuthorizePermissionRequest[], options?: EvaluatorRequestOptions): Promise<AuthorizePermissionResponse[]>;
/**
* {@inheritdoc PermissionEvaluator.authorizeConditional}
*/
authorizeConditional(queries: QueryPermissionRequest[], options?: EvaluatorRequestOptions): Promise<QueryPermissionResponse[]>;
private makeRequest;
private getAuthorizationHeader;
private assertValidResponses;
}
export { AuthorizeRequest, AuthorizeRequestOptions, AuthorizeResponse, AuthorizeResult, DiscoveryApi, Identified, Permission, PermissionAttributes, PermissionAuthorizer, PermissionClient, PermissionCondition, PermissionCriteria, isCreatePermission, isDeletePermission, isReadPermission, isUpdatePermission };
export { AllOfCriteria, AnyOfCriteria, AuthorizePermissionRequest, AuthorizePermissionResponse, AuthorizeRequestOptions, AuthorizeResult, BasicPermission, ConditionalPolicyDecision, DefinitivePolicyDecision, DiscoveryApi, EvaluatePermissionRequest, EvaluatePermissionRequestBatch, EvaluatePermissionResponse, EvaluatePermissionResponseBatch, EvaluatorRequestOptions, IdentifiedPermissionMessage, NotCriteria, Permission, PermissionAttributes, PermissionAuthorizer, PermissionBase, PermissionClient, PermissionCondition, PermissionCriteria, PermissionEvaluator, PermissionMessageBatch, PolicyDecision, QueryPermissionRequest, QueryPermissionResponse, ResourcePermission, createPermission, isCreatePermission, isDeletePermission, isPermission, isReadPermission, isResourcePermission, isUpdatePermission, toPermissionEvaluator };

@@ -13,2 +13,11 @@ import { ResponseError } from '@backstage/errors';

function isPermission(permission, comparedPermission) {
return permission.name === comparedPermission.name;
}
function isResourcePermission(permission, resourceType) {
if (!("resourceType" in permission)) {
return false;
}
return !resourceType || permission.resourceType === resourceType;
}
function isCreatePermission(permission) {

@@ -26,15 +35,71 @@ return permission.attributes.action === "create";

}
function toPermissionEvaluator(permissionAuthorizer) {
return {
authorize: async (requests, options) => {
const response = await permissionAuthorizer.authorize(requests, options);
return response;
},
authorizeConditional(requests, options) {
const parsedRequests = requests;
return permissionAuthorizer.authorize(parsedRequests, options);
}
};
}
const permissionCriteriaSchema = z.lazy(() => z.object({
rule: z.string(),
params: z.array(z.unknown())
}).or(z.object({ anyOf: z.array(permissionCriteriaSchema) })).or(z.object({ allOf: z.array(permissionCriteriaSchema) })).or(z.object({ not: permissionCriteriaSchema })));
const responseSchema = z.array(z.object({
id: z.string(),
function createPermission({
name,
attributes,
resourceType
}) {
if (resourceType) {
return {
type: "resource",
name,
attributes,
resourceType
};
}
return {
type: "basic",
name,
attributes
};
}
const permissionCriteriaSchema = z.lazy(
() => z.object({
rule: z.string(),
resourceType: z.string(),
params: z.array(z.unknown())
}).or(z.object({ anyOf: z.array(permissionCriteriaSchema).nonempty() })).or(z.object({ allOf: z.array(permissionCriteriaSchema).nonempty() })).or(z.object({ not: permissionCriteriaSchema }))
);
const authorizePermissionResponseSchema = z.object({
result: z.literal(AuthorizeResult.ALLOW).or(z.literal(AuthorizeResult.DENY))
}).or(z.object({
id: z.string(),
result: z.literal(AuthorizeResult.CONDITIONAL),
conditions: permissionCriteriaSchema
})));
});
const queryPermissionResponseSchema = z.union([
z.object({
result: z.literal(AuthorizeResult.ALLOW).or(z.literal(AuthorizeResult.DENY))
}),
z.object({
result: z.literal(AuthorizeResult.CONDITIONAL),
pluginId: z.string(),
resourceType: z.string(),
conditions: permissionCriteriaSchema
})
]);
const responseSchema = (itemSchema, ids) => z.object({
items: z.array(
z.intersection(
z.object({
id: z.string()
}),
itemSchema
)
).refine(
(items) => items.length === ids.size && items.every(({ id }) => ids.has(id)),
{
message: "Items in response do not match request"
}
)
});
class PermissionClient {

@@ -47,13 +112,25 @@ constructor(options) {

async authorize(requests, options) {
return this.makeRequest(
requests,
authorizePermissionResponseSchema,
options
);
}
async authorizeConditional(queries, options) {
return this.makeRequest(queries, queryPermissionResponseSchema, options);
}
async makeRequest(queries, itemSchema, options) {
if (!this.enabled) {
return requests.map((_) => ({ result: AuthorizeResult.ALLOW }));
return queries.map((_) => ({ result: AuthorizeResult.ALLOW }));
}
const identifiedRequests = requests.map((request) => ({
id: uuid.v4(),
...request
}));
const request = {
items: queries.map((query) => ({
id: uuid.v4(),
...query
}))
};
const permissionApi = await this.discovery.getBaseUrl("permission");
const response = await fetch(`${permissionApi}/authorize`, {
method: "POST",
body: JSON.stringify(identifiedRequests),
body: JSON.stringify(request),
headers: {

@@ -67,9 +144,12 @@ ...this.getAuthorizationHeader(options == null ? void 0 : options.token),

}
const identifiedResponses = await response.json();
this.assertValidResponses(identifiedRequests, identifiedResponses);
const responsesById = identifiedResponses.reduce((acc, r) => {
const responseBody = await response.json();
const parsedResponse = responseSchema(
itemSchema,
new Set(request.items.map(({ id }) => id))
).parse(responseBody);
const responsesById = parsedResponse.items.reduce((acc, r) => {
acc[r.id] = r;
return acc;
}, {});
return identifiedRequests.map((request) => responsesById[request.id]);
return request.items.map((query) => responsesById[query.id]);
}

@@ -79,13 +159,5 @@ getAuthorizationHeader(token) {

}
assertValidResponses(requests, json) {
const authorizedResponses = responseSchema.parse(json);
const responseIds = authorizedResponses.map((r) => r.id);
const hasAllRequestIds = requests.every((r) => responseIds.includes(r.id));
if (!hasAllRequestIds) {
throw new Error("Unexpected authorization response from permission-backend");
}
}
}
export { AuthorizeResult, PermissionClient, isCreatePermission, isDeletePermission, isReadPermission, isUpdatePermission };
export { AuthorizeResult, PermissionClient, createPermission, isCreatePermission, isDeletePermission, isPermission, isReadPermission, isResourcePermission, isUpdatePermission, toPermissionEvaluator };
//# sourceMappingURL=index.esm.js.map
{
"name": "@backstage/plugin-permission-common",
"description": "Isomorphic types and client for Backstage permissions and authorization",
"version": "0.0.0-nightly-20220923026",
"version": "0.0.0-nightly-20220923030237",
"main": "dist/index.cjs.js",

@@ -13,2 +13,5 @@ "types": "dist/index.d.ts",

},
"backstage": {
"role": "common-library"
},
"homepage": "https://backstage.io",

@@ -31,8 +34,8 @@ "repository": {

"scripts": {
"build": "backstage-cli build",
"lint": "backstage-cli lint",
"test": "backstage-cli test",
"prepack": "backstage-cli prepack",
"postpack": "backstage-cli postpack",
"clean": "backstage-cli clean"
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack",
"clean": "backstage-cli package clean"
},

@@ -43,5 +46,5 @@ "bugs": {

"dependencies": {
"@backstage/config": "^0.0.0-nightly-20220923026",
"@backstage/errors": "^0.0.0-nightly-20220923026",
"cross-fetch": "^3.0.6",
"@backstage/config": "^0.0.0-nightly-20220923030237",
"@backstage/errors": "^0.0.0-nightly-20220923030237",
"cross-fetch": "^3.1.5",
"uuid": "^8.0.0",

@@ -51,7 +54,6 @@ "zod": "^3.11.6"

"devDependencies": {
"@backstage/cli": "^0.0.0-nightly-20220923026",
"@types/jest": "^26.0.7",
"msw": "^0.35.0"
"@backstage/cli": "^0.0.0-nightly-20220923030237",
"msw": "^0.47.0"
},
"module": "dist/index.esm.js"
}
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc