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
486
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.6.0-next.0 to 0.6.0-next.1

9

CHANGELOG.md
# @backstage/plugin-permission-common
## 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

@@ -4,0 +13,0 @@

56

dist/index.cjs.js

@@ -61,2 +61,14 @@ 'use strict';

}
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);
}
};
}

@@ -88,11 +100,22 @@ function createPermission({

}).strict().or(zod.z.object({ anyOf: zod.z.array(permissionCriteriaSchema).nonempty() }).strict()).or(zod.z.object({ allOf: zod.z.array(permissionCriteriaSchema).nonempty() }).strict()).or(zod.z.object({ not: permissionCriteriaSchema }).strict()));
const responseSchema = zod.z.object({
items: zod.z.array(zod.z.object({
id: zod.z.string(),
const authorizePermissionResponseSchema = zod.z.object({
result: zod.z.literal(AuthorizeResult.ALLOW).or(zod.z.literal(AuthorizeResult.DENY))
});
const queryPermissionResponseSchema = zod.z.union([
zod.z.object({
result: zod.z.literal(AuthorizeResult.ALLOW).or(zod.z.literal(AuthorizeResult.DENY))
}).or(zod.z.object({
id: zod.z.string(),
}),
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"
})
});

@@ -105,3 +128,9 @@ class PermissionClient {

}
async authorize(queries, 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) {

@@ -129,4 +158,4 @@ return queries.map((_) => ({ result: AuthorizeResult.ALLOW }));

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

@@ -140,10 +169,2 @@ return acc;

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

@@ -160,2 +181,3 @@

exports.isUpdatePermission = isUpdatePermission;
exports.toPermissionEvaluator = toPermissionEvaluator;
//# sourceMappingURL=index.cjs.js.map

@@ -69,2 +69,3 @@ import { Config } from '@backstage/config';

* @public
* @deprecated Use {@link @backstage/plugin-permission-common#PermissionEvaluator} instead
*/

@@ -224,2 +225,60 @@ interface PermissionAuthorizer {

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;
} | {
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;
};

@@ -267,2 +326,8 @@ /**

declare function isDeletePermission(permission: Permission): boolean;
/**
* Convert {@link PermissionAuthorizer} to {@link PermissionEvaluator}.
*
* @public
*/
declare function toPermissionEvaluator(permissionAuthorizer: PermissionAuthorizer): PermissionEvaluator;

@@ -294,3 +359,3 @@ /**

*/
declare class PermissionClient implements PermissionAuthorizer {
declare class PermissionClient implements PermissionEvaluator {
private readonly enabled;

@@ -303,22 +368,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(queries: EvaluatePermissionRequest[], options?: AuthorizeRequestOptions): Promise<EvaluatePermissionResponse[]>;
authorize(requests: AuthorizePermissionRequest[], options?: EvaluatorRequestOptions): Promise<AuthorizePermissionResponse[]>;
/**
* {@inheritdoc PermissionEvaluator.authorizeConditional}
*/
authorizeConditional(queries: QueryPermissionRequest[], options?: EvaluatorRequestOptions): Promise<QueryPermissionResponse[]>;
private makeRequest;
private getAuthorizationHeader;
private assertValidResponse;
}
export { AllOfCriteria, AnyOfCriteria, AuthorizeRequestOptions, AuthorizeResult, BasicPermission, ConditionalPolicyDecision, DefinitivePolicyDecision, DiscoveryApi, EvaluatePermissionRequest, EvaluatePermissionRequestBatch, EvaluatePermissionResponse, EvaluatePermissionResponseBatch, IdentifiedPermissionMessage, NotCriteria, Permission, PermissionAttributes, PermissionAuthorizer, PermissionBase, PermissionClient, PermissionCondition, PermissionCriteria, PermissionMessageBatch, PolicyDecision, ResourcePermission, createPermission, isCreatePermission, isDeletePermission, isPermission, isReadPermission, isResourcePermission, 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 };

@@ -34,2 +34,14 @@ import { ResponseError } from '@backstage/errors';

}
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);
}
};
}

@@ -61,11 +73,22 @@ function createPermission({

}).strict().or(z.object({ anyOf: z.array(permissionCriteriaSchema).nonempty() }).strict()).or(z.object({ allOf: z.array(permissionCriteriaSchema).nonempty() }).strict()).or(z.object({ not: permissionCriteriaSchema }).strict()));
const responseSchema = z.object({
items: z.array(z.object({
id: z.string(),
const authorizePermissionResponseSchema = z.object({
result: z.literal(AuthorizeResult.ALLOW).or(z.literal(AuthorizeResult.DENY))
});
const queryPermissionResponseSchema = z.union([
z.object({
result: z.literal(AuthorizeResult.ALLOW).or(z.literal(AuthorizeResult.DENY))
}).or(z.object({
id: z.string(),
}),
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"
})
});

@@ -78,3 +101,9 @@ class PermissionClient {

}
async authorize(queries, 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) {

@@ -102,4 +131,4 @@ return queries.map((_) => ({ result: AuthorizeResult.ALLOW }));

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

@@ -113,13 +142,5 @@ return acc;

}
assertValidResponse(request, json) {
const authorizedResponses = responseSchema.parse(json);
const responseIds = authorizedResponses.items.map((r) => r.id);
const hasAllRequestIds = request.items.every((r) => responseIds.includes(r.id));
if (!hasAllRequestIds) {
throw new Error("Unexpected authorization response from permission-backend");
}
}
}
export { AuthorizeResult, PermissionClient, createPermission, isCreatePermission, isDeletePermission, isPermission, isReadPermission, isResourcePermission, 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.6.0-next.0",
"version": "0.6.0-next.1",
"main": "dist/index.cjs.js",

@@ -51,8 +51,8 @@ "types": "dist/index.d.ts",

"devDependencies": {
"@backstage/cli": "^0.17.0-next.1",
"@backstage/cli": "^0.17.0-next.3",
"@types/jest": "^26.0.7",
"msw": "^0.35.0"
},
"gitHead": "57d12dcc35aeb6c33b09e51d1efc3408c574f109",
"gitHead": "2eca57d93ef1081f4a76a19fc994a8e9e1a19e00",
"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