@spotify/backstage-plugin-rbac-common 0.5.8 → 0.5.9

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @spotify/backstage-plugin-rbac-common
2
2
 
3
+ ## 0.5.9
4
+
5
+ ### Patch Changes
6
+
7
+ - Added a policy tester for checking the behavior of RBAC policies. The policy tester is designed to display simulated policy decisions for a given set of permissions, helping you ensure that the configured policy returns the expected decisions. For each [Permission](https://backstage.io/docs/permissions/concepts#permission) selected in the UI, the policy tester will output the decision that would be returned by the policy in question for a user assigned to all of the selected policy roles. If the given permission request requires certain conditions to be met, the policy tester will list exactly which conditions would be required. You can find the policy tester in the Policy page of any active, draft, or previous policy.
8
+
3
9
  ## 0.5.8
4
10
 
5
11
  ### Patch Changes
package/dist/index.cjs.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var c=require("@backstage/catalog-model"),P=require("uuid"),s=require("zod"),I=require("@backstage/plugin-permission-common");const f=(i,e)=>`${i} must be at least ${e} characters`,p=(i,e)=>`${i} must be less than ${e} characters`,y="Untitled policy",v=":backstageUser",h=s.z.object({rule:s.z.string(),params:s.z.record(s.z.any()).optional()}),n=s.z.lazy(()=>s.z.union([s.z.object({allOf:s.z.array(n).nonempty()}).strict(),s.z.object({anyOf:s.z.array(n).nonempty()}).strict(),s.z.object({not:n}).strict(),h])),g=s.z.object({pluginId:s.z.string().min(1).describe("Plugin ID that defines the permission and rules used."),resourceType:s.z.string().min(1).describe("Resource type associated with the conditions used."),conditions:n}),z=s.z.union([s.z.literal("allow"),s.z.literal("deny")]),b=s.z.union([z,g]),R=s.z.union([s.z.literal("*"),s.z.object({name:s.z.string().optional().describe("Name that must match the incoming permission request."),actions:s.z.array(s.z.string()).optional().describe("Actions that must be present on the incoming permission's attributes."),resourceType:s.z.string().min(1).optional().describe("Resource type that must match that of the incoming permission request.")})]),C=s.z.object({id:s.z.string().default(()=>P.v4().split("-")[0]).describe("ID of the permission."),match:R.describe("Values used to match against the incoming permission request."),decision:b.describe("Authorization result or conditions to send if this role permission applies.")}).refine(({decision:i,match:e})=>O(i)?e!=="*"&&(e==null?void 0:e.resourceType)!==void 0:!0,{path:["match","resourceType"],message:"match.resourceType is required for conditional decisions."}),D=s.z.array(C).superRefine((i,e)=>{const r=d(i,"id");r>=0&&e.addIssue({code:s.z.ZodIssueCode.custom,message:"Permission ids must be unique",path:[r,"id"]})}).describe("Permission decisions used to determine authorization responses."),T=s.z.string().refine(E,{message:"Invalid entity ref for member.",path:[]}).transform(i=>c.stringifyEntityRef(c.parseEntityRef(i,{defaultKind:"group",defaultNamespace:"default"}))),q=s.z.object({name:s.z.string().min(1,{message:f("Role name",1)}).max(1024,{message:p("Role name",1024)}).describe("Name of the role."),id:s.z.string().default(()=>P.v4().split("-")[0]).describe("ID of the role."),members:s.z.union([s.z.literal("*"),s.z.array(T).min(1)]).describe("Entity references used to map users to this role. These entities don't need to exist in the catalog."),permissions:D}),l=s.z.array(q).default([]).superRefine((i,e)=>{const r=d(i,"name");r>=0&&e.addIssue({code:s.z.ZodIssueCode.custom,message:"Role names must be unique",path:[r,"name"]});const t=d(i,"id");t>=0&&e.addIssue({code:s.z.ZodIssueCode.custom,message:"Role ids must be unique",path:[t,"id"]})}),u=s.z.string().min(1,{message:f("Policy name",1)}).max(1024,{message:p("Policy name",1024)}).default(y).describe("Name of the policy."),j=s.z.union([s.z.literal("first-match"),s.z.literal("any-allow")]),m=s.z.object({resolutionStrategy:j}).default({resolutionStrategy:"first-match"}),a=s.z.object({name:u,options:m,roles:l}),N=a.default({roles:[]});function E(i){try{return c.parseEntityRef(i,{defaultKind:"group",defaultNamespace:"default"}),!0}catch{return!1}}function d(i,e){let r=0;const t=new Set;for(;r<i.length;){const o=i[r][e];if(t.has(o))return r;t.add(o),r++}return-1}function O(i){return typeof i!="string"}function M(i){const e=Object.entries(i);return e.length===1&&e[0][0]==="allOf"&&Array.isArray(e[0][1])}function U(i){const e=Object.entries(i);return e.length===1&&e[0][0]==="anyOf"&&Array.isArray(e[0][1])}function k(i){const e=Object.entries(i);return e.length===1&&e[0][0]==="not"&&!Array.isArray(e[0][1])}const x=a,A=a.extend({name:u.optional(),options:m.optional(),roles:l.optional()}),S=s.z.object({description:s.z.string().optional(),update:A.optional()}),$=(i,e)=>e?e===i.name:!0,w=(i,e)=>e?I.isResourcePermission(i)&&e===i.resourceType:!0,V=(i,e)=>e?e.some(r=>r===i.attributes.action):!0,Z=(i,e)=>e==="*"?!0:$(i,e.name)&&w(i,e.resourceType)&&V(i,e.actions),B=(i,e)=>i.kind!==e.kind||i.namespace!==e.namespace?!1:e.name==="*"?!0:i.name===e.name;function K(i,e){const r={};if(i)for(const[t,o]of Object.entries(i))r[t]=e(o);return r}exports.BackstageUserPlaceholder=v,exports.ConditionalDecisionParser=g,exports.CreateDraftRequestParser=x,exports.DefaultingPolicyConfigParser=N,exports.LiteralDecisionParser=z,exports.PermissionConditionParser=h,exports.PermissionDecisionParser=b,exports.PermissionMatchParser=R,exports.PolicyConfigOptionsParser=m,exports.PolicyConfigParser=a,exports.PolicyDefaultName=y,exports.PolicyRoleResolutionStrategyParser=j,exports.PolicyTitleParser=u,exports.PublishVersionRequestParser=S,exports.RoleParser=q,exports.RolePermissionParser=C,exports.RolePermissionsParser=D,exports.RolesParser=l,exports.UpdateDraftRequestParser=A,exports.isAllOfPermissionCriteria=M,exports.isAnyOfPermissionCriteria=U,exports.isConditionalDecision=O,exports.isMatchingPermission=Z,exports.isNotPermissionCriteria=k,exports.mapParams=K,exports.matchesEntityRef=B;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var c=require("@backstage/catalog-model"),f=require("uuid"),s=require("zod"),N=require("@backstage/plugin-permission-common");const h=(i,e)=>`${i} must be at least ${e} characters`,p=(i,e)=>`${i} must be less than ${e} characters`,y="Untitled policy",M=":backstageUser",g=s.z.object({rule:s.z.string(),params:s.z.record(s.z.any()).optional()}),n=s.z.lazy(()=>s.z.union([s.z.object({allOf:s.z.array(n).nonempty()}).strict(),s.z.object({anyOf:s.z.array(n).nonempty()}).strict(),s.z.object({not:n}).strict(),g])),z=s.z.object({pluginId:s.z.string().min(1).describe("Plugin ID that defines the permission and rules used."),resourceType:s.z.string().min(1).describe("Resource type associated with the conditions used."),conditions:n}),b=s.z.union([s.z.literal("allow"),s.z.literal("deny")]),l=s.z.union([b,z]),R=s.z.union([s.z.literal("*"),s.z.object({name:s.z.string().optional().describe("Name that must match the incoming permission request."),actions:s.z.array(s.z.string()).optional().describe("Actions that must be present on the incoming permission's attributes."),resourceType:s.z.string().min(1).optional().describe("Resource type that must match that of the incoming permission request.")})]),C=s.z.string().default(()=>f.v4().split("-")[0]).describe("ID of the permission."),D=s.z.object({id:C,match:R.describe("Values used to match against the incoming permission request."),decision:l.describe("Authorization result or conditions to send if this role permission applies.")}).refine(({decision:i,match:e})=>T(i)?e!=="*"&&(e==null?void 0:e.resourceType)!==void 0:!0,{path:["match","resourceType"],message:"match.resourceType is required for conditional decisions."}),j=s.z.array(D).superRefine((i,e)=>{const r=P(i,"id");r>=0&&e.addIssue({code:s.z.ZodIssueCode.custom,message:"Permission ids must be unique",path:[r,"id"]})}).describe("Permission decisions used to determine authorization responses."),E=s.z.string().refine(x,{message:"Invalid entity ref for member.",path:[]}).transform(i=>c.stringifyEntityRef(c.parseEntityRef(i,{defaultKind:"group",defaultNamespace:"default"}))),q=s.z.string().default(()=>f.v4().split("-")[0]).describe("ID of the role."),O=s.z.object({name:s.z.string().min(1,{message:h("Role name",1)}).max(1024,{message:p("Role name",1024)}).describe("Name of the role."),id:q,members:s.z.union([s.z.literal("*"),s.z.array(E).min(1)]).describe("Entity references used to map users to this role. These entities don't need to exist in the catalog."),permissions:j}),u=s.z.array(O).default([]).superRefine((i,e)=>{const r=P(i,"name");r>=0&&e.addIssue({code:s.z.ZodIssueCode.custom,message:"Role names must be unique",path:[r,"name"]});const t=P(i,"id");t>=0&&e.addIssue({code:s.z.ZodIssueCode.custom,message:"Role ids must be unique",path:[t,"id"]})}),U=s.z.object({decision:l.describe("The authorization result or conditions the corresponding role resulted in"),roleId:q.describe("The id of the role that resulted in the decision"),rolePermissionId:C.describe("The id of the role permission that resulted in the decisions.")}),m=s.z.string().min(1,{message:h("Policy name",1)}).max(1024,{message:p("Policy name",1024)}).default(y).describe("Name of the policy."),I=s.z.union([s.z.literal("first-match"),s.z.literal("any-allow")]),d=s.z.object({resolutionStrategy:I}).default({resolutionStrategy:"first-match"}),a=s.z.object({name:m,options:d,roles:u}),k=a.default({roles:[]});function x(i){try{return c.parseEntityRef(i,{defaultKind:"group",defaultNamespace:"default"}),!0}catch{return!1}}function P(i,e){let r=0;const t=new Set;for(;r<i.length;){const o=i[r][e];if(t.has(o))return r;t.add(o),r++}return-1}function T(i){return typeof i!="string"}function S(i){const e=Object.entries(i);return e.length===1&&e[0][0]==="allOf"&&Array.isArray(e[0][1])}function $(i){const e=Object.entries(i);return e.length===1&&e[0][0]==="anyOf"&&Array.isArray(e[0][1])}function w(i){const e=Object.entries(i);return e.length===1&&e[0][0]==="not"&&!Array.isArray(e[0][1])}const V=a,A=a.extend({name:m.optional(),options:d.optional(),roles:u.optional()}),Z=s.z.object({description:s.z.string().optional(),update:A.optional()}),B=(i,e)=>e?e===i.name:!0,K=(i,e)=>e?N.isResourcePermission(i)&&e===i.resourceType:!0,L=(i,e)=>e?e.some(r=>r===i.attributes.action):!0,v=(i,e)=>e==="*"?!0:B(i,e.name)&&K(i,e.resourceType)&&L(i,e.actions),_=(i,e)=>i.permissions.filter(({match:r})=>v(e,r)),F=(i,e)=>i.kind!==e.kind||i.namespace!==e.namespace?!1:e.name==="*"?!0:i.name===e.name;function G(i,e){const r={};if(i)for(const[t,o]of Object.entries(i))r[t]=e(o);return r}exports.BackstageUserPlaceholder=M,exports.ConditionalDecisionParser=z,exports.CreateDraftRequestParser=V,exports.DefaultingPolicyConfigParser=k,exports.LiteralDecisionParser=b,exports.PermissionConditionParser=g,exports.PermissionDecisionParser=l,exports.PermissionMatchParser=R,exports.PolicyConfigOptionsParser=d,exports.PolicyConfigParser=a,exports.PolicyDefaultName=y,exports.PolicyRoleResolutionStrategyParser=I,exports.PolicyTitleParser=m,exports.PublishVersionRequestParser=Z,exports.RoleDecisionParser=U,exports.RoleParser=O,exports.RolePermissionParser=D,exports.RolePermissionsParser=j,exports.RolesParser=u,exports.UpdateDraftRequestParser=A,exports.getMatchingRolePermissions=_,exports.isAllOfPermissionCriteria=S,exports.isAnyOfPermissionCriteria=$,exports.isConditionalDecision=T,exports.isMatchingPermission=v,exports.isNotPermissionCriteria=w,exports.mapParams=G,exports.matchesEntityRef=F;
2
2
  //# sourceMappingURL=index.cjs.js.map
package/dist/index.d.ts CHANGED
@@ -776,6 +776,48 @@ declare const RolesParser: z.ZodEffects<z.ZodDefault<z.ZodArray<z.ZodObject<{
776
776
  id?: string | undefined;
777
777
  }[] | undefined>;
778
778
  /** @public */
779
+ declare const RoleDecisionParser: z.ZodObject<{
780
+ decision: z.ZodUnion<[z.ZodUnion<[z.ZodLiteral<"allow">, z.ZodLiteral<"deny">]>, z.ZodObject<{
781
+ pluginId: z.ZodString;
782
+ resourceType: z.ZodString;
783
+ conditions: z.ZodType<PermissionCriteria<RBACPermissionCondition>, z.ZodTypeDef, PermissionCriteria<RBACPermissionCondition>>;
784
+ }, "strip", z.ZodTypeAny, {
785
+ pluginId: string;
786
+ conditions: PermissionCriteria<RBACPermissionCondition> & (PermissionCriteria<RBACPermissionCondition> | undefined);
787
+ resourceType: string;
788
+ }, {
789
+ pluginId: string;
790
+ conditions: PermissionCriteria<RBACPermissionCondition> & (PermissionCriteria<RBACPermissionCondition> | undefined);
791
+ resourceType: string;
792
+ }>]>;
793
+ roleId: z.ZodDefault<z.ZodString>;
794
+ rolePermissionId: z.ZodDefault<z.ZodString>;
795
+ }, "strip", z.ZodTypeAny, {
796
+ decision: ("allow" | {
797
+ pluginId: string;
798
+ conditions: PermissionCriteria<RBACPermissionCondition> & (PermissionCriteria<RBACPermissionCondition> | undefined);
799
+ resourceType: string;
800
+ } | "deny") & ("allow" | {
801
+ pluginId: string;
802
+ conditions: PermissionCriteria<RBACPermissionCondition> & (PermissionCriteria<RBACPermissionCondition> | undefined);
803
+ resourceType: string;
804
+ } | "deny" | undefined);
805
+ roleId: string;
806
+ rolePermissionId: string;
807
+ }, {
808
+ decision: ("allow" | {
809
+ pluginId: string;
810
+ conditions: PermissionCriteria<RBACPermissionCondition> & (PermissionCriteria<RBACPermissionCondition> | undefined);
811
+ resourceType: string;
812
+ } | "deny") & ("allow" | {
813
+ pluginId: string;
814
+ conditions: PermissionCriteria<RBACPermissionCondition> & (PermissionCriteria<RBACPermissionCondition> | undefined);
815
+ resourceType: string;
816
+ } | "deny" | undefined);
817
+ roleId?: string | undefined;
818
+ rolePermissionId?: string | undefined;
819
+ }>;
820
+ /** @public */
779
821
  declare const PolicyTitleParser: z.ZodDefault<z.ZodString>;
780
822
  /** @public */
781
823
  declare const PolicyRoleResolutionStrategyParser: z.ZodUnion<[z.ZodLiteral<"first-match">, z.ZodLiteral<"any-allow">]>;
@@ -1466,6 +1508,8 @@ type RawRole = z.input<typeof RoleParser>;
1466
1508
  /** @public */
1467
1509
  type Role = z.infer<typeof RoleParser>;
1468
1510
  /** @public */
1511
+ type RoleDecision = z.infer<typeof RoleDecisionParser>;
1512
+ /** @public */
1469
1513
  type RawPolicyConfig = z.input<typeof PolicyConfigParser>;
1470
1514
  /** @public */
1471
1515
  type PolicyConfig = z.infer<typeof PolicyConfigParser>;
@@ -2607,6 +2651,28 @@ type PoliciesResponse = PaginatedResponse<Policy>;
2607
2651
 
2608
2652
  /** @public */
2609
2653
  declare const isMatchingPermission: (permission: Permission, match: PermissionMatch) => boolean;
2654
+ /** @public */
2655
+ declare const getMatchingRolePermissions: (role: Role, permission: Permission) => {
2656
+ id: string;
2657
+ match: ("*" | {
2658
+ name?: string | undefined;
2659
+ actions?: string[] | undefined;
2660
+ resourceType?: string | undefined;
2661
+ }) & ("*" | {
2662
+ name?: string | undefined;
2663
+ actions?: string[] | undefined;
2664
+ resourceType?: string | undefined;
2665
+ } | undefined);
2666
+ decision: ("allow" | {
2667
+ pluginId: string;
2668
+ conditions: _backstage_plugin_permission_common.PermissionCriteria<RBACPermissionCondition> & (_backstage_plugin_permission_common.PermissionCriteria<RBACPermissionCondition> | undefined);
2669
+ resourceType: string;
2670
+ } | "deny") & ("allow" | {
2671
+ pluginId: string;
2672
+ conditions: _backstage_plugin_permission_common.PermissionCriteria<RBACPermissionCondition> & (_backstage_plugin_permission_common.PermissionCriteria<RBACPermissionCondition> | undefined);
2673
+ resourceType: string;
2674
+ } | "deny" | undefined);
2675
+ }[];
2610
2676
  /**
2611
2677
  * Compares a user entity ref to an entry from a list of
2612
2678
  * policy members. The two refs must either match exactly,
@@ -2622,4 +2688,4 @@ type MapParamsCallback = (param: PermissionRuleParam) => PermissionRuleParam;
2622
2688
  /** @public */
2623
2689
  declare function mapParams(params: PermissionRuleParams, cb: MapParamsCallback): PermissionRuleParams;
2624
2690
 
2625
- export { AuthorizeResponse, BackstageUserPlaceholder, ConditionalDecision, ConditionalDecisionParser, CreateDraftRequest, CreateDraftRequestParser, DefaultingPolicyConfigParser, LiteralDecision, LiteralDecisionParser, MapParamsCallback, MemberResponse, PaginatedResponse, PermissionConditionParser, PermissionDecision, PermissionDecisionParser, PermissionMatch, PermissionMatchParser, PoliciesResponse, Policy, PolicyConfig, PolicyConfigOptions, PolicyConfigOptionsParser, PolicyConfigParser, PolicyDefaultName, PolicyMember, PolicyRoleResolutionStrategy, PolicyRoleResolutionStrategyParser, PolicyTitleParser, PublishVersionRequest, PublishVersionRequestParser, RBACPermissionCondition, RawPolicyConfig, RawRole, Role, RoleParser, RolePermission, RolePermissionParser, RolePermissions, RolePermissionsParser, RolesParser, SearchMembersRequest, SearchMembersResponse, UpdateDraftRequest, UpdateDraftRequestParser, isAllOfPermissionCriteria, isAnyOfPermissionCriteria, isConditionalDecision, isMatchingPermission, isNotPermissionCriteria, mapParams, matchesEntityRef };
2691
+ export { AuthorizeResponse, BackstageUserPlaceholder, ConditionalDecision, ConditionalDecisionParser, CreateDraftRequest, CreateDraftRequestParser, DefaultingPolicyConfigParser, LiteralDecision, LiteralDecisionParser, MapParamsCallback, MemberResponse, PaginatedResponse, PermissionConditionParser, PermissionDecision, PermissionDecisionParser, PermissionMatch, PermissionMatchParser, PoliciesResponse, Policy, PolicyConfig, PolicyConfigOptions, PolicyConfigOptionsParser, PolicyConfigParser, PolicyDefaultName, PolicyMember, PolicyRoleResolutionStrategy, PolicyRoleResolutionStrategyParser, PolicyTitleParser, PublishVersionRequest, PublishVersionRequestParser, RBACPermissionCondition, RawPolicyConfig, RawRole, Role, RoleDecision, RoleDecisionParser, RoleParser, RolePermission, RolePermissionParser, RolePermissions, RolePermissionsParser, RolesParser, SearchMembersRequest, SearchMembersResponse, UpdateDraftRequest, UpdateDraftRequestParser, getMatchingRolePermissions, isAllOfPermissionCriteria, isAnyOfPermissionCriteria, isConditionalDecision, isMatchingPermission, isNotPermissionCriteria, mapParams, matchesEntityRef };
package/dist/index.esm.js CHANGED
@@ -1,2 +1,2 @@
1
- import{stringifyEntityRef as T,parseEntityRef as d}from"@backstage/catalog-model";import{v4 as p}from"uuid";import{z as s}from"zod";import{isResourcePermission as N}from"@backstage/plugin-permission-common";const f=(t,e)=>`${t} must be at least ${e} characters`,h=(t,e)=>`${t} must be less than ${e} characters`,y="Untitled policy",x=":backstageUser",g=s.object({rule:s.string(),params:s.record(s.any()).optional()}),n=s.lazy(()=>s.union([s.object({allOf:s.array(n).nonempty()}).strict(),s.object({anyOf:s.array(n).nonempty()}).strict(),s.object({not:n}).strict(),g])),P=s.object({pluginId:s.string().min(1).describe("Plugin ID that defines the permission and rules used."),resourceType:s.string().min(1).describe("Resource type associated with the conditions used."),conditions:n}),b=s.union([s.literal("allow"),s.literal("deny")]),R=s.union([b,P]),j=s.union([s.literal("*"),s.object({name:s.string().optional().describe("Name that must match the incoming permission request."),actions:s.array(s.string()).optional().describe("Actions that must be present on the incoming permission's attributes."),resourceType:s.string().min(1).optional().describe("Resource type that must match that of the incoming permission request.")})]),C=s.object({id:s.string().default(()=>p().split("-")[0]).describe("ID of the permission."),match:j.describe("Values used to match against the incoming permission request."),decision:R.describe("Authorization result or conditions to send if this role permission applies.")}).refine(({decision:t,match:e})=>q(t)?e!=="*"&&(e==null?void 0:e.resourceType)!==void 0:!0,{path:["match","resourceType"],message:"match.resourceType is required for conditional decisions."}),D=s.array(C).superRefine((t,e)=>{const i=u(t,"id");i>=0&&e.addIssue({code:s.ZodIssueCode.custom,message:"Permission ids must be unique",path:[i,"id"]})}).describe("Permission decisions used to determine authorization responses."),$=s.string().refine(v,{message:"Invalid entity ref for member.",path:[]}).transform(t=>T(d(t,{defaultKind:"group",defaultNamespace:"default"}))),I=s.object({name:s.string().min(1,{message:f("Role name",1)}).max(1024,{message:h("Role name",1024)}).describe("Name of the role."),id:s.string().default(()=>p().split("-")[0]).describe("ID of the role."),members:s.union([s.literal("*"),s.array($).min(1)]).describe("Entity references used to map users to this role. These entities don't need to exist in the catalog."),permissions:D}),c=s.array(I).default([]).superRefine((t,e)=>{const i=u(t,"name");i>=0&&e.addIssue({code:s.ZodIssueCode.custom,message:"Role names must be unique",path:[i,"name"]});const r=u(t,"id");r>=0&&e.addIssue({code:s.ZodIssueCode.custom,message:"Role ids must be unique",path:[r,"id"]})}),m=s.string().min(1,{message:f("Policy name",1)}).max(1024,{message:h("Policy name",1024)}).default(y).describe("Name of the policy."),O=s.union([s.literal("first-match"),s.literal("any-allow")]),l=s.object({resolutionStrategy:O}).default({resolutionStrategy:"first-match"}),a=s.object({name:m,options:l,roles:c}),k=a.default({roles:[]});function v(t){try{return d(t,{defaultKind:"group",defaultNamespace:"default"}),!0}catch{return!1}}function u(t,e){let i=0;const r=new Set;for(;i<t.length;){const o=t[i][e];if(r.has(o))return i;r.add(o),i++}return-1}function q(t){return typeof t!="string"}function w(t){const e=Object.entries(t);return e.length===1&&e[0][0]==="allOf"&&Array.isArray(e[0][1])}function z(t){const e=Object.entries(t);return e.length===1&&e[0][0]==="anyOf"&&Array.isArray(e[0][1])}function E(t){const e=Object.entries(t);return e.length===1&&e[0][0]==="not"&&!Array.isArray(e[0][1])}const S=a,A=a.extend({name:m.optional(),options:l.optional(),roles:c.optional()}),U=s.object({description:s.string().optional(),update:A.optional()}),Z=(t,e)=>e?e===t.name:!0,K=(t,e)=>e?N(t)&&e===t.resourceType:!0,M=(t,e)=>e?e.some(i=>i===t.attributes.action):!0,V=(t,e)=>e==="*"?!0:Z(t,e.name)&&K(t,e.resourceType)&&M(t,e.actions),B=(t,e)=>t.kind!==e.kind||t.namespace!==e.namespace?!1:e.name==="*"?!0:t.name===e.name;function L(t,e){const i={};if(t)for(const[r,o]of Object.entries(t))i[r]=e(o);return i}export{x as BackstageUserPlaceholder,P as ConditionalDecisionParser,S as CreateDraftRequestParser,k as DefaultingPolicyConfigParser,b as LiteralDecisionParser,g as PermissionConditionParser,R as PermissionDecisionParser,j as PermissionMatchParser,l as PolicyConfigOptionsParser,a as PolicyConfigParser,y as PolicyDefaultName,O as PolicyRoleResolutionStrategyParser,m as PolicyTitleParser,U as PublishVersionRequestParser,I as RoleParser,C as RolePermissionParser,D as RolePermissionsParser,c as RolesParser,A as UpdateDraftRequestParser,w as isAllOfPermissionCriteria,z as isAnyOfPermissionCriteria,q as isConditionalDecision,V as isMatchingPermission,E as isNotPermissionCriteria,L as mapParams,B as matchesEntityRef};
1
+ import{stringifyEntityRef as z,parseEntityRef as p}from"@backstage/catalog-model";import{v4 as f}from"uuid";import{z as s}from"zod";import{isResourcePermission as $}from"@backstage/plugin-permission-common";const h=(t,e)=>`${t} must be at least ${e} characters`,y=(t,e)=>`${t} must be less than ${e} characters`,g="Untitled policy",k=":backstageUser",P=s.object({rule:s.string(),params:s.record(s.any()).optional()}),n=s.lazy(()=>s.union([s.object({allOf:s.array(n).nonempty()}).strict(),s.object({anyOf:s.array(n).nonempty()}).strict(),s.object({not:n}).strict(),P])),b=s.object({pluginId:s.string().min(1).describe("Plugin ID that defines the permission and rules used."),resourceType:s.string().min(1).describe("Resource type associated with the conditions used."),conditions:n}),R=s.union([s.literal("allow"),s.literal("deny")]),c=s.union([R,b]),j=s.union([s.literal("*"),s.object({name:s.string().optional().describe("Name that must match the incoming permission request."),actions:s.array(s.string()).optional().describe("Actions that must be present on the incoming permission's attributes."),resourceType:s.string().min(1).optional().describe("Resource type that must match that of the incoming permission request.")})]),C=s.string().default(()=>f().split("-")[0]).describe("ID of the permission."),I=s.object({id:C,match:j.describe("Values used to match against the incoming permission request."),decision:c.describe("Authorization result or conditions to send if this role permission applies.")}).refine(({decision:t,match:e})=>A(t)?e!=="*"&&(e==null?void 0:e.resourceType)!==void 0:!0,{path:["match","resourceType"],message:"match.resourceType is required for conditional decisions."}),D=s.array(I).superRefine((t,e)=>{const i=d(t,"id");i>=0&&e.addIssue({code:s.ZodIssueCode.custom,message:"Permission ids must be unique",path:[i,"id"]})}).describe("Permission decisions used to determine authorization responses."),v=s.string().refine(S,{message:"Invalid entity ref for member.",path:[]}).transform(t=>z(p(t,{defaultKind:"group",defaultNamespace:"default"}))),T=s.string().default(()=>f().split("-")[0]).describe("ID of the role."),O=s.object({name:s.string().min(1,{message:h("Role name",1)}).max(1024,{message:y("Role name",1024)}).describe("Name of the role."),id:T,members:s.union([s.literal("*"),s.array(v).min(1)]).describe("Entity references used to map users to this role. These entities don't need to exist in the catalog."),permissions:D}),l=s.array(O).default([]).superRefine((t,e)=>{const i=d(t,"name");i>=0&&e.addIssue({code:s.ZodIssueCode.custom,message:"Role names must be unique",path:[i,"name"]});const r=d(t,"id");r>=0&&e.addIssue({code:s.ZodIssueCode.custom,message:"Role ids must be unique",path:[r,"id"]})}),w=s.object({decision:c.describe("The authorization result or conditions the corresponding role resulted in"),roleId:T.describe("The id of the role that resulted in the decision"),rolePermissionId:C.describe("The id of the role permission that resulted in the decisions.")}),m=s.string().min(1,{message:h("Policy name",1)}).max(1024,{message:y("Policy name",1024)}).default(g).describe("Name of the policy."),q=s.union([s.literal("first-match"),s.literal("any-allow")]),u=s.object({resolutionStrategy:q}).default({resolutionStrategy:"first-match"}),a=s.object({name:m,options:u,roles:l}),E=a.default({roles:[]});function S(t){try{return p(t,{defaultKind:"group",defaultNamespace:"default"}),!0}catch{return!1}}function d(t,e){let i=0;const r=new Set;for(;i<t.length;){const o=t[i][e];if(r.has(o))return i;r.add(o),i++}return-1}function A(t){return typeof t!="string"}function U(t){const e=Object.entries(t);return e.length===1&&e[0][0]==="allOf"&&Array.isArray(e[0][1])}function M(t){const e=Object.entries(t);return e.length===1&&e[0][0]==="anyOf"&&Array.isArray(e[0][1])}function Z(t){const e=Object.entries(t);return e.length===1&&e[0][0]==="not"&&!Array.isArray(e[0][1])}const K=a,N=a.extend({name:m.optional(),options:u.optional(),roles:l.optional()}),V=s.object({description:s.string().optional(),update:N.optional()}),B=(t,e)=>e?e===t.name:!0,L=(t,e)=>e?$(t)&&e===t.resourceType:!0,F=(t,e)=>e?e.some(i=>i===t.attributes.action):!0,x=(t,e)=>e==="*"?!0:B(t,e.name)&&L(t,e.resourceType)&&F(t,e.actions),G=(t,e)=>t.permissions.filter(({match:i})=>x(e,i)),H=(t,e)=>t.kind!==e.kind||t.namespace!==e.namespace?!1:e.name==="*"?!0:t.name===e.name;function J(t,e){const i={};if(t)for(const[r,o]of Object.entries(t))i[r]=e(o);return i}export{k as BackstageUserPlaceholder,b as ConditionalDecisionParser,K as CreateDraftRequestParser,E as DefaultingPolicyConfigParser,R as LiteralDecisionParser,P as PermissionConditionParser,c as PermissionDecisionParser,j as PermissionMatchParser,u as PolicyConfigOptionsParser,a as PolicyConfigParser,g as PolicyDefaultName,q as PolicyRoleResolutionStrategyParser,m as PolicyTitleParser,V as PublishVersionRequestParser,w as RoleDecisionParser,O as RoleParser,I as RolePermissionParser,D as RolePermissionsParser,l as RolesParser,N as UpdateDraftRequestParser,G as getMatchingRolePermissions,U as isAllOfPermissionCriteria,M as isAnyOfPermissionCriteria,A as isConditionalDecision,x as isMatchingPermission,Z as isNotPermissionCriteria,J as mapParams,H as matchesEntityRef};
2
2
  //# sourceMappingURL=index.esm.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@spotify/backstage-plugin-rbac-common",
3
3
  "description": "Control access to actions and data in Backstage with ease.",
4
- "version": "0.5.8",
4
+ "version": "0.5.9",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "homepage": "https://backstage.spotify.com",
7
7
  "main": "dist/index.cjs.js",
@@ -25,13 +25,13 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@backstage/catalog-model": "^1.4.3",
28
- "@backstage/plugin-permission-common": "^0.7.10",
28
+ "@backstage/plugin-permission-common": "^0.7.11",
29
29
  "@backstage/types": "^1.1.1",
30
30
  "uuid": "^9.0.0",
31
31
  "zod": "^3.20.0"
32
32
  },
33
33
  "devDependencies": {
34
- "@backstage/cli": "^0.24.0"
34
+ "@backstage/cli": "^0.25.0"
35
35
  },
36
36
  "files": [
37
37
  "dist",