@spotify/backstage-plugin-rbac 0.7.8 → 0.7.10

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,28 @@
1
1
  # @spotify/backstage-plugin-rbac
2
2
 
3
+ ## 0.7.10
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependency `backstage` to `1.42.0`.
8
+ - Updated dependencies
9
+ - Updated dependencies
10
+ - @spotify/backstage-plugin-core@0.8.10
11
+ - @spotify/backstage-plugin-rbac-common@0.6.9
12
+
13
+ ## 0.7.9
14
+
15
+ ### Patch Changes
16
+
17
+ - Fixed a bug that could cause the RBAC UI to crash when attempting to add a conditional permission decision to one or more resource permissions which have no associated rules.
18
+ - Updated dependency `@types/jest` to `^30.0.0`.
19
+ - Updated dependency `backstage` to `1.41.1`.
20
+ - Updated dependencies
21
+ - Updated dependencies
22
+ - Updated dependencies
23
+ - @spotify/backstage-plugin-core@0.8.9
24
+ - @spotify/backstage-plugin-rbac-common@0.6.8
25
+
3
26
  ## 0.7.8
4
27
 
5
28
  ### Patch Changes
@@ -1,2 +1,2 @@
1
- import{ApiBlueprint as i,createApiFactory as r,fetchApiRef as e,discoveryApiRef as c}from"@backstage/frontend-plugin-api";import{rbacApiRef as o,RBACApi as t}from"../api.esm.js";const a=i.make({params:{factory:r({api:o,deps:{fetchApi:e,discoveryApi:c},factory:p=>new t(p)})}});export{a as rbacApi};
1
+ import{ApiBlueprint as e,fetchApiRef as r,discoveryApiRef as o}from"@backstage/frontend-plugin-api";import{rbacApiRef as A,RBACApi as c}from"../api.esm.js";const f=e.make({params:p=>p({api:A,deps:{fetchApi:r,discoveryApi:o},factory:i=>new c(i)})});export{f as rbacApi};
2
2
  //# sourceMappingURL=apis.esm.js.map
@@ -1,2 +1,2 @@
1
- import{jsx as r}from"react/jsx-runtime";import{convertLegacyRouteRef as t,compatWrapper as e}from"@backstage/core-compat-api";import{PageBlueprint as a}from"@backstage/frontend-plugin-api";import{rootRouteRef as m}from"../routes.esm.js";const p=a.make({params:{routeRef:t(m),defaultPath:"/rbac",loader:()=>import("../components/Root.esm.js").then(o=>e(r(o.RBACRoot,{})))}});export{p as rbacPage};
1
+ import{jsx as r}from"react/jsx-runtime";import{convertLegacyRouteRef as t,compatWrapper as e}from"@backstage/core-compat-api";import{PageBlueprint as a}from"@backstage/frontend-plugin-api";import{rootRouteRef as m}from"../routes.esm.js";const p=a.make({params:{routeRef:t(m),path:"/rbac",loader:()=>import("../components/Root.esm.js").then(o=>e(r(o.RBACRoot,{})))}});export{p as rbacPage};
2
2
  //# sourceMappingURL=pages.esm.js.map
@@ -1,2 +1,2 @@
1
- import{createFrontendPlugin as r}from"@backstage/frontend-plugin-api";import{rbacApi as o}from"./apis.esm.js";import{rbacPage as a}from"./pages.esm.js";var e=r({id:"rbac",extensions:[a,o]});export{e as default};
1
+ import{createFrontendPlugin as r}from"@backstage/frontend-plugin-api";import{rbacApi as o}from"./apis.esm.js";import{rbacPage as a}from"./pages.esm.js";var e=r({pluginId:"rbac",extensions:[a,o]});export{e as default};
2
2
  //# sourceMappingURL=plugin.esm.js.map
package/dist/alpha.d.ts CHANGED
@@ -5,19 +5,17 @@ import * as _backstage_core_plugin_api from '@backstage/core-plugin-api';
5
5
  /**
6
6
  * @public
7
7
  */
8
- declare const _default: _backstage_frontend_plugin_api.FrontendPlugin<{}, {}, {
9
- [x: `api:${string}`]: _backstage_frontend_plugin_api.ExtensionDefinition<{
8
+ declare const _default: _backstage_frontend_plugin_api.OverridableFrontendPlugin<{}, {}, {
9
+ "api:rbac": _backstage_frontend_plugin_api.ExtensionDefinition<{
10
10
  kind: "api";
11
11
  name: undefined;
12
12
  config: {};
13
13
  configInput: {};
14
- output: _backstage_frontend_plugin_api.ConfigurableExtensionDataRef<_backstage_core_plugin_api.AnyApiFactory, "core.api.factory", {}>;
14
+ output: _backstage_frontend_plugin_api.ExtensionDataRef<_backstage_core_plugin_api.AnyApiFactory, "core.api.factory", {}>;
15
15
  inputs: {};
16
- params: {
17
- factory: _backstage_core_plugin_api.AnyApiFactory;
18
- };
16
+ params: <TApi, TImpl extends TApi, TDeps extends { [name in string]: unknown; }>(params: _backstage_core_plugin_api.ApiFactory<TApi, TImpl, TDeps>) => _backstage_frontend_plugin_api.ExtensionBlueprintParams<_backstage_core_plugin_api.AnyApiFactory>;
19
17
  }>;
20
- [x: `page:${string}`]: _backstage_frontend_plugin_api.ExtensionDefinition<{
18
+ "page:rbac": _backstage_frontend_plugin_api.ExtensionDefinition<{
21
19
  kind: "page";
22
20
  name: undefined;
23
21
  config: {
@@ -26,12 +24,13 @@ declare const _default: _backstage_frontend_plugin_api.FrontendPlugin<{}, {}, {
26
24
  configInput: {
27
25
  path?: string | undefined;
28
26
  };
29
- output: _backstage_frontend_plugin_api.ConfigurableExtensionDataRef<react.JSX.Element, "core.reactElement", {}> | _backstage_frontend_plugin_api.ConfigurableExtensionDataRef<string, "core.routing.path", {}> | _backstage_frontend_plugin_api.ConfigurableExtensionDataRef<_backstage_frontend_plugin_api.RouteRef<_backstage_frontend_plugin_api.AnyRouteRefParams>, "core.routing.ref", {
27
+ output: _backstage_frontend_plugin_api.ExtensionDataRef<string, "core.routing.path", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<react.JSX.Element, "core.reactElement", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<_backstage_frontend_plugin_api.RouteRef<_backstage_frontend_plugin_api.AnyRouteRefParams>, "core.routing.ref", {
30
28
  optional: true;
31
29
  }>;
32
30
  inputs: {};
33
31
  params: {
34
- defaultPath: string;
32
+ defaultPath?: [Error: `Use the 'path' param instead`];
33
+ path: string;
35
34
  loader: () => Promise<JSX.Element>;
36
35
  routeRef?: _backstage_frontend_plugin_api.RouteRef;
37
36
  };
package/dist/api.esm.js CHANGED
@@ -1,2 +1,2 @@
1
- import{createApiRef as p}from"@backstage/core-plugin-api";import{ResponseError as h}from"@backstage/errors";import{PolicyResponseParser as l,PolicyParser as c,SearchMemberResponseParser as y,AuthorizeResponseParser as f,DraftResponseParser as o,TestPolicyDecisionResponseParser as d}from"@spotify/backstage-plugin-rbac-common";const w=p({id:"plugin.rbac"});class A{fetchApi;discoveryApi;constructor(i){this.fetchApi=i.fetchApi,this.discoveryApi=i.discoveryApi}async getPolicies(){const{fetch:i}=this.fetchApi,t=await i(`${await this.discoveryApi.getBaseUrl("rbac")}/policies`),s=await this.#i(t);return l.parse(s)}async getPolicy(i){const{fetch:t}=this.fetchApi,s=await t(`${await this.discoveryApi.getBaseUrl("rbac")}/policies/${i}`),e=await this.#i(s);return c.parse(e)}async getActivePolicy(){const{fetch:i}=this.fetchApi,t=await i(`${await this.discoveryApi.getBaseUrl("rbac")}/policies/active`),s=await this.#i(t);return c.parse(s)}async getFallbackPolicy(){const{fetch:i}=this.fetchApi,t=await i(`${await this.discoveryApi.getBaseUrl("rbac")}/policies/fallback`);if(t.status!==404)return await this.#i(t)}async searchMembers(i){const{fetch:t}=this.fetchApi,s=await t(`${await this.discoveryApi.getBaseUrl("rbac")}/members?query=${encodeURIComponent(i.query)}`),e=await this.#i(s);return y.parse(e)}async authorize(){const{fetch:i}=this.fetchApi,t=await i(`${await this.discoveryApi.getBaseUrl("rbac")}/authorize`),s=await this.#i(t);return f.parse(s)}async createDraft(i){const{fetch:t}=this.fetchApi,s=await t(`${await this.discoveryApi.getBaseUrl("rbac")}/policies`,{method:"POST",body:JSON.stringify(i),headers:{"Content-Type":"application/json"}}),e=await this.#i(s);return o.parse(e)}async updateDraft(i,t){const{fetch:s}=this.fetchApi,e=await s(`${await this.discoveryApi.getBaseUrl("rbac")}/policies/${i}`,{method:"PATCH",body:JSON.stringify(t),headers:{"Content-Type":"application/json"}}),a=await this.#i(e);return o.parse(a)}async deleteDraft(i){const{fetch:t}=this.fetchApi,s=await t(`${await this.discoveryApi.getBaseUrl("rbac")}/policies/${i}`,{method:"DELETE"});return await this.#i(s)}async publishPolicy(i,t){const{fetch:s}=this.fetchApi,e=await s(`${await this.discoveryApi.getBaseUrl("rbac")}/policies/${i}/publish`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});return await this.#i(e)}async testPolicyDecisions(i){return await Promise.all(i.map(async t=>{const{policyConfig:s,roleIds:e,permission:a}=t,{decision:r,decisionOrigin:n}=await this.#t(s,e,a);return{decision:r,permission:a,decisionOrigin:n}}))}async#t(i,t,s){const{fetch:e}=this.fetchApi,a=await e(`${await this.discoveryApi.getBaseUrl("rbac")}/policies/test-policy-decision`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({policyConfig:i,roleIds:t,permission:s})}),r=await this.#i(a);return d.parse(r)}async fetchAllPermissionMetadata(i){const t=await Promise.all(i.map(s=>this.#s(s)));return{permissions:t.flatMap(({permissions:s})=>s),rules:t.flatMap(({rules:s})=>s)}}async#s(i){const t=await this.discoveryApi.getBaseUrl(i);let s;try{const r=await this.fetchApi.fetch(`${t}/.well-known/backstage/permissions/metadata`);s=await this.#i(r)}catch{return{permissions:[],rules:[]}}const e=s.permissions?.filter(Boolean)??[],a=s.rules?.filter(Boolean)??[];return{permissions:e,rules:a.map(r=>({...r,pluginId:i}))}}async#i(i){if(i.ok)return i.status===204?void 0:i.json();const t=await h.fromResponse(i);throw t.message=t.body.error.message??t.message??"Unknown error",t}}export{A as RBACApi,w as rbacApiRef};
1
+ import{createApiRef as p}from"@backstage/core-plugin-api";import{ResponseError as h}from"@backstage/errors";import{PolicyResponseParser as l,PolicyParser as o,SearchMemberResponseParser as y,AuthorizeResponseParser as f,DraftResponseParser as c,TestPolicyDecisionResponseParser as d}from"@spotify/backstage-plugin-rbac-common";const w=p({id:"plugin.rbac"});class A{fetchApi;discoveryApi;constructor(i){this.fetchApi=i.fetchApi,this.discoveryApi=i.discoveryApi}async getPolicies(){const{fetch:i}=this.fetchApi,t=await i(`${await this.discoveryApi.getBaseUrl("rbac")}/policies`),s=await this.#i(t);return l.parse(s)}async getPolicy(i){const{fetch:t}=this.fetchApi,s=await t(`${await this.discoveryApi.getBaseUrl("rbac")}/policies/${i}`),e=await this.#i(s);return o.parse(e)}async getActivePolicy(){const{fetch:i}=this.fetchApi,t=await i(`${await this.discoveryApi.getBaseUrl("rbac")}/policies/active`),s=await this.#i(t);return o.parse(s)}async getFallbackPolicy(){const{fetch:i}=this.fetchApi,t=await i(`${await this.discoveryApi.getBaseUrl("rbac")}/policies/fallback`);if(t.status!==404)return await this.#i(t)}async searchMembers(i){const{fetch:t}=this.fetchApi,s=await t(`${await this.discoveryApi.getBaseUrl("rbac")}/members?query=${encodeURIComponent(i.query)}`),e=await this.#i(s);return y.parse(e)}async authorize(){const{fetch:i}=this.fetchApi,t=await i(`${await this.discoveryApi.getBaseUrl("rbac")}/authorize`),s=await this.#i(t);return f.parse(s)}async createDraft(i){const{fetch:t}=this.fetchApi,s=await t(`${await this.discoveryApi.getBaseUrl("rbac")}/policies`,{method:"POST",body:JSON.stringify(i),headers:{"Content-Type":"application/json"}}),e=await this.#i(s);return c.parse(e)}async updateDraft(i,t){const{fetch:s}=this.fetchApi,e=await s(`${await this.discoveryApi.getBaseUrl("rbac")}/policies/${i}`,{method:"PATCH",body:JSON.stringify(t),headers:{"Content-Type":"application/json"}}),r=await this.#i(e);return c.parse(r)}async deleteDraft(i){const{fetch:t}=this.fetchApi,s=await t(`${await this.discoveryApi.getBaseUrl("rbac")}/policies/${i}`,{method:"DELETE"});return await this.#i(s)}async publishPolicy(i,t){const{fetch:s}=this.fetchApi,e=await s(`${await this.discoveryApi.getBaseUrl("rbac")}/policies/${i}/publish`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});return await this.#i(e)}async testPolicyDecisions(i){return await Promise.all(i.map(async t=>{const{policyConfig:s,roleIds:e,permission:r}=t,{decision:a,decisionOrigin:n}=await this.#t(s,e,r);return{decision:a,permission:r,decisionOrigin:n}}))}async#t(i,t,s){const{fetch:e}=this.fetchApi,r=await e(`${await this.discoveryApi.getBaseUrl("rbac")}/policies/test-policy-decision`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({policyConfig:i,roleIds:t,permission:s})}),a=await this.#i(r);return d.parse(a)}async fetchAllPermissionMetadata(i){const t=await Promise.all(i.map(s=>this.#s(s)));return{permissions:t.flatMap(({permissions:s})=>s),rules:t.flatMap(({rules:s})=>s)}}async#s(i){const t=await this.discoveryApi.getBaseUrl(i);let s;try{const a=await this.fetchApi.fetch(`${t}/.well-known/backstage/permissions/metadata`);s=await this.#i(a)}catch{return{permissions:[],rules:[]}}const e=s.permissions?.filter(Boolean)??[],r=s.rules?.filter(Boolean)??[];return{permissions:e.map(a=>({...a,pluginId:i})),rules:r.map(a=>({...a,pluginId:i}))}}async#i(i){if(i.ok)return i.status===204?void 0:i.json();const t=await h.fromResponse(i);throw t.message=t.body.error.message??t.message??"Unknown error",t}}export{A as RBACApi,w as rbacApiRef};
2
2
  //# sourceMappingURL=api.esm.js.map
@@ -1,2 +1,2 @@
1
- import{jsxs as l,jsx as i,Fragment as D}from"react/jsx-runtime";import{makeStyles as A,Dialog as k,DialogTitle as F,Typography as M,Button as w,DialogContent as E,Box as a,Divider as V,FormHelperText as G,Tooltip as H}from"@material-ui/core";import L from"@material-ui/icons/Close";import O from"@material-ui/icons/Info";import{ToggleButton as b}from"@material-ui/lab";import{useState as x,useMemo as f,useEffect as U}from"react";import{usePermissions as Y,useAggregatedRules as q,useResourceTypeInfo as z}from"../Permissions/PermissionsMetadataContext.esm.js";import J from"./computeMatchResult.esm.js";import K from"./ConditionalDecisionForm/ConditionalDecisionForm.esm.js";import{getInitialValues as Q,validateCriteria as X,toRolePermission as Z,getConditions as _}from"./decisionDataMappers.esm.js";import ee from"./MatchByActions.esm.js";import ie from"./MatchByName.esm.js";import oe from"./MatchByResourceType.esm.js";import ne from"./ToggleButtonGroup.esm.js";import{VerticalSelection as re}from"./VerticalSelection.esm.js";const le=A(s=>({dialogTitle:{display:"flex",alignItems:"center",justifyContent:"space-between"},fullWidth:{width:"100%","& > *":{flexGrow:1}},infoIcon:{position:"absolute",right:s.spacing(3)},content:{padding:s.spacing(7)},saveButton:{paddingLeft:s.spacing(5),paddingRight:s.spacing(5)},contentContainerConditional:{display:"flex",gap:s.spacing(5),maxWidth:1480}})),se={specificPermission:"Choose a specific permission",filter:"Filter by permission properties",all:"Match all permissions"},I="Choose the name of a resource permission or filter by resource type to set a conditional decision.";function te({rolePermission:s,readonly:r=!1,onClose:v,onSave:N}){const c=le(),{permissions:m}=Y(),R=q(),y=z(),p=Q(s),[e,h]=x(p.match),[t,g]=x(p.decisionType),[u,P]=x(p.criteria),o=f(()=>J(m,e),[m,e]),d=t==="conditional",S=d?"xl":"sm";U(()=>{d&&m&&!o.commonResourceType&&g("allow")},[d,m,o.commonResourceType,g]);const $=n=>{n!==null&&g(n)},T=f(()=>e.method==="specificPermission"&&!e.name||e.method==="filter"&&!e.resourceType&&!e.actions,[e]),j=f(()=>o.permissions.length===0?!1:!T,[o,T]),B=f(()=>t!=="conditional"?!0:!(!o.commonResourceType||!y?.[o.commonResourceType].pluginId||!X(u,R)),[t,o,y,u,R]),W=()=>{N?.(Z(p.id,e,t==="conditional"?{resourceType:o.commonResourceType,pluginId:y[o.commonResourceType].pluginId,conditions:_(u)}:t))};return l(k,{open:!0,maxWidth:S,onClose:v,fullWidth:!0,children:[l(F,{disableTypography:!0,className:c.dialogTitle,children:[l(M,{variant:"h4",component:"h2",children:[r?"":"New"," Permission Decision"]}),i(w,{startIcon:i(L,{}),onClick:v,children:"Close"})]}),l(E,{dividers:!0,className:c.content,children:[l(a,{className:d?c.contentContainerConditional:void 0,children:[i(a,{mb:5,flex:1,children:i(re,{options:Object.entries(se).map(([n,C])=>({id:n,displayText:C})),selected:e.method,onChange:n=>h({method:n}),readonly:r})}),l(a,{mb:5,height:189,flex:1,children:[e.method==="specificPermission"&&i(ie,{name:e.name,readonly:r,onNameChange:n=>h({...e,name:n})}),e.method==="filter"&&l(D,{children:[i(oe,{resourceType:e.resourceType,readonly:r,onResourceTypeChange:n=>h({...e,resourceType:n})}),i(ee,{actions:e.actions,readonly:r,resourceSelected:e.resourceType!==void 0,onActionsChange:n=>h({...e,actions:n.length?n:void 0})}),T?null:l(D,{children:[i(a,{marginY:2,children:i(V,{variant:"middle"})}),i(G,{children:`Matched ${o.permissions.length} ${o.permissions.length===1?"permission":"permissions"}`})]})]})]}),i(a,{mb:5,flex:1,children:l(a,{display:"flex",alignItems:"center",children:[l(ne,{value:t,exclusive:!0,fullWidth:!0,"aria-label":"Decision result",onChange:(n,C)=>$(C),children:[i(b,{value:"allow","aria-label":"Decision allowed",disabled:r,children:"Allow"}),i(b,{value:"deny","aria-label":"Decision denied",disabled:r,children:"Deny"}),i(b,{value:"conditional","aria-label":"Decision conditionally allowed",disabled:!o.commonResourceType||r,children:"Conditional"})]}),!d&&!r&&i(H,{title:I,children:i(O,{className:c.infoIcon,"aria-label":I})})]})})]}),t==="conditional"&&!!o.commonResourceType&&i(K,{permissionCriteria:u,resourceType:o.commonResourceType,onUpdate:P,readonly:r}),!r&&i(a,{display:"flex",justifyContent:"flex-end",children:i(w,{variant:"contained",color:"primary",className:c.saveButton,onClick:W,disabled:!j||!B,children:"Save"})})]})]})}export{te as DecisionDialog};
1
+ import{jsxs as l,jsx as o,Fragment as D}from"react/jsx-runtime";import{makeStyles as k,Dialog as F,DialogTitle as M,Typography as V,Button as I,DialogContent as E,Box as t,Divider as G,FormHelperText as H,Tooltip as L}from"@material-ui/core";import O from"@material-ui/icons/Close";import U from"@material-ui/icons/Info";import{ToggleButton as b}from"@material-ui/lab";import{useState as v,useMemo as m,useEffect as Y}from"react";import{usePermissions as q,useAggregatedRules as z,useResourceTypeInfo as J}from"../Permissions/PermissionsMetadataContext.esm.js";import K from"./computeMatchResult.esm.js";import Q from"./ConditionalDecisionForm/ConditionalDecisionForm.esm.js";import{getInitialValues as X,validateCriteria as Z,toRolePermission as _,getConditions as ee}from"./decisionDataMappers.esm.js";import oe from"./MatchByActions.esm.js";import ie from"./MatchByName.esm.js";import ne from"./MatchByResourceType.esm.js";import re from"./ToggleButtonGroup.esm.js";import{VerticalSelection as le}from"./VerticalSelection.esm.js";const se=k(s=>({dialogTitle:{display:"flex",alignItems:"center",justifyContent:"space-between"},fullWidth:{width:"100%","& > *":{flexGrow:1}},infoIcon:{position:"absolute",right:s.spacing(3)},content:{padding:s.spacing(7)},saveButton:{paddingLeft:s.spacing(5),paddingRight:s.spacing(5)},contentContainerConditional:{display:"flex",gap:s.spacing(5),maxWidth:1480}})),ae={specificPermission:"Choose a specific permission",filter:"Filter by permission properties",all:"Match all permissions"},w="Choose the name of a resource permission or filter by resource type to set a conditional decision.";function te({rolePermission:s,readonly:r=!1,onClose:R,onSave:N}){const c=se(),{permissions:p}=q(),h=z(),g=J(),u=X(s),[e,y]=v(u.match),[a,T]=v(u.decisionType),[f,P]=v(u.criteria),i=m(()=>K(p,e),[p,e]),d=a==="conditional",S=d?"xl":"sm";Y(()=>{d&&p&&!i.commonResourceType&&T("allow")},[d,p,i.commonResourceType,T]);const $=n=>{n!==null&&T(n)},C=m(()=>e.method==="specificPermission"&&!e.name||e.method==="filter"&&!e.resourceType&&!e.actions,[e]),j=m(()=>i.permissions.length===0?!1:!C,[i,C]),B=m(()=>i.commonResourceType!==void 0&&h?.findIndex(n=>n.resourceType===i.commonResourceType)!==-1,[i,h]),W=m(()=>a!=="conditional"?!0:!(!i.commonResourceType||!g?.[i.commonResourceType].pluginId||!Z(f,h)),[a,i,g,f,h]),A=()=>{N?.(_(u.id,e,a==="conditional"?{resourceType:i.commonResourceType,pluginId:g[i.commonResourceType].pluginId,conditions:ee(f)}:a))};return l(F,{open:!0,maxWidth:S,onClose:R,fullWidth:!0,children:[l(M,{disableTypography:!0,className:c.dialogTitle,children:[l(V,{variant:"h4",component:"h2",children:[r?"":"New"," Permission Decision"]}),o(I,{startIcon:o(O,{}),onClick:R,children:"Close"})]}),l(E,{dividers:!0,className:c.content,children:[l(t,{className:d?c.contentContainerConditional:void 0,children:[o(t,{mb:5,flex:1,children:o(le,{options:Object.entries(ae).map(([n,x])=>({id:n,displayText:x})),selected:e.method,onChange:n=>y({method:n}),readonly:r})}),l(t,{mb:5,height:189,flex:1,children:[e.method==="specificPermission"&&o(ie,{name:e.name,readonly:r,onNameChange:n=>y({...e,name:n})}),e.method==="filter"&&l(D,{children:[o(ne,{resourceType:e.resourceType,readonly:r,onResourceTypeChange:n=>y({...e,resourceType:n})}),o(oe,{actions:e.actions,readonly:r,resourceSelected:e.resourceType!==void 0,onActionsChange:n=>y({...e,actions:n.length?n:void 0})}),C?null:l(D,{children:[o(t,{marginY:2,children:o(G,{variant:"middle"})}),o(H,{children:`Matched ${i.permissions.length} ${i.permissions.length===1?"permission":"permissions"}`})]})]})]}),o(t,{mb:5,flex:1,children:l(t,{display:"flex",alignItems:"center",children:[l(re,{value:a,exclusive:!0,fullWidth:!0,"aria-label":"Decision result",onChange:(n,x)=>$(x),children:[o(b,{value:"allow","aria-label":"Decision allowed",disabled:r,children:"Allow"}),o(b,{value:"deny","aria-label":"Decision denied",disabled:r,children:"Deny"}),o(b,{value:"conditional","aria-label":"Decision conditionally allowed",disabled:!B||r,children:"Conditional"})]}),!d&&!r&&o(L,{title:w,children:o(U,{className:c.infoIcon,"aria-label":w})})]})})]}),a==="conditional"&&!!i.commonResourceType&&o(Q,{permissionCriteria:f,resourceType:i.commonResourceType,onUpdate:P,readonly:r}),!r&&o(t,{display:"flex",justifyContent:"flex-end",children:o(I,{variant:"contained",color:"primary",className:c.saveButton,onClick:A,disabled:!j||!W,children:"Save"})})]})]})}export{te as DecisionDialog};
2
2
  //# sourceMappingURL=DecisionDialog.esm.js.map
@@ -1,2 +1,2 @@
1
- import{jsx as g}from"react/jsx-runtime";import{useApi as a,configApiRef as y,alertApiRef as h}from"@backstage/core-plugin-api";import{isResourcePermission as P}from"@backstage/plugin-permission-common";import{forEach as v,mapValues as A,groupBy as R,uniq as T}from"lodash";import{createContext as x,useState as M,useCallback as b,useMemo as w,useContext as C}from"react";import I from"react-use/lib/useAsync";import{rbacApiRef as L}from"../../api.esm.js";const l=x(null),S=({children:e})=>{const r=a(y),t=a(L),n=a(h),[s,o]=M(null),c=b(async()=>{if(!s){const p=await t.fetchAllPermissionMetadata(r.getOptionalStringArray("permission.permissionedPlugins")??[]),m=O(p.rules);v(m,u=>{u.length!==1&&n.post({message:`The plugin(s) ${u.slice(1).join(", ")} expose rules which are conflicting with rules exposed by the ${u[0]} plugin. These rules will not be available for use. Please contact RBAC support if you need assistance resolving this issue.`,severity:"error"})});const d=k(p,m);return o(d),d}return s},[n,s,t,r]);return g(l.Provider,{value:w(()=>({getMetadata:c}),[c]),children:e})};function i(){const e=C(l),{value:r,loading:t}=I(async()=>e?.getMetadata(),[e]);return{metadata:r,isLoading:t}}function $(){const{metadata:e}=i();return e?.rules??null}function j(){const{metadata:e,isLoading:r}=i();return{permissions:e?.permissions??null,isLoading:r}}function f(){const{metadata:e}=i();return e?[...new Set(e.permissions.filter(r=>P(r)).map(({resourceType:r})=>r))]:null}function B(){const{metadata:e}=i(),r=f();return!e||!r?null:r.reduce((t,n)=>{const s=e.rules.find(o=>o.resourceType===n);return s&&(t[n]={pluginId:s.pluginId}),t},{})}function O(e){return A(R(e,"resourceType"),r=>T(r.map(({pluginId:t})=>t)))}function k(e,r){return{...e,rules:e.rules.filter(({resourceType:t,pluginId:n})=>{const s=r[t];return n===s[0]})}}export{S as PermissionMetadataProvider,l as PermissionsMetadataContext,$ as useAggregatedRules,i as usePermissionMetadata,j as usePermissions,B as useResourceTypeInfo,f as useResourceTypeOptions};
1
+ import{jsx as g}from"react/jsx-runtime";import{useApi as a,configApiRef as y,alertApiRef as h}from"@backstage/core-plugin-api";import{isResourcePermission as P}from"@backstage/plugin-permission-common";import{forEach as v,mapValues as T,groupBy as A,uniq as R}from"lodash";import{createContext as x,useState as M,useCallback as b,useMemo as w,useContext as C}from"react";import I from"react-use/lib/useAsync";import{rbacApiRef as L}from"../../api.esm.js";const l=x(null),S=({children:e})=>{const r=a(y),s=a(L),n=a(h),[t,o]=M(null),c=b(async()=>{if(!t){const p=await s.fetchAllPermissionMetadata(r.getOptionalStringArray("permission.permissionedPlugins")??[]),m=O(p.rules);v(m,u=>{u.length!==1&&n.post({message:`The plugin(s) ${u.slice(1).join(", ")} expose rules which are conflicting with rules exposed by the ${u[0]} plugin. These rules will not be available for use. Please contact RBAC support if you need assistance resolving this issue.`,severity:"error"})});const d=k(p,m);return o(d),d}return t},[n,t,s,r]);return g(l.Provider,{value:w(()=>({getMetadata:c}),[c]),children:e})};function i(){const e=C(l),{value:r,loading:s}=I(async()=>e?.getMetadata(),[e]);return{metadata:r,isLoading:s}}function $(){const{metadata:e}=i();return e?.rules??null}function j(){const{metadata:e,isLoading:r}=i();return{permissions:e?.permissions??null,isLoading:r}}function f(){const{metadata:e}=i();return e?[...new Set(e.permissions.map(r=>"resourceType"in r&&typeof r.resourceType=="string"?r.resourceType:"").filter(r=>r!==""))]:null}function B(){const{metadata:e}=i(),r=f();return!e||!r?null:r.reduce((s,n)=>{const t=e.permissions?.find(o=>P(o,n));return t&&(s[n]={pluginId:t.pluginId}),s},{})}function O(e){return T(A(e,"resourceType"),r=>R(r.map(({pluginId:s})=>s)))}function k(e,r){return{...e,rules:e.rules.filter(({resourceType:s,pluginId:n})=>{const t=r[s];return n===t[0]})}}export{S as PermissionMetadataProvider,l as PermissionsMetadataContext,$ as useAggregatedRules,i as usePermissionMetadata,j as usePermissions,B as useResourceTypeInfo,f as useResourceTypeOptions};
2
2
  //# sourceMappingURL=PermissionsMetadataContext.esm.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@spotify/backstage-plugin-rbac",
3
3
  "description": "Control access to actions and data in Backstage with ease.",
4
- "version": "0.7.8",
4
+ "version": "0.7.10",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "homepage": "https://backstage.spotify.com",
7
7
  "main": "./dist/index.esm.js",
@@ -56,22 +56,22 @@
56
56
  "a11y": "yarn dlx @lhci/cli@0.13.x --config ./.lighthouserc.js autorun"
57
57
  },
58
58
  "dependencies": {
59
- "@backstage/catalog-model": "^1.7.4",
60
- "@backstage/core-compat-api": "^0.4.2",
61
- "@backstage/core-components": "^0.17.2",
62
- "@backstage/core-plugin-api": "^1.10.7",
59
+ "@backstage/catalog-model": "^1.7.5",
60
+ "@backstage/core-compat-api": "^0.5.0",
61
+ "@backstage/core-components": "^0.17.5",
62
+ "@backstage/core-plugin-api": "^1.10.9",
63
63
  "@backstage/errors": "^1.2.7",
64
- "@backstage/frontend-plugin-api": "^0.10.2",
65
- "@backstage/plugin-catalog-react": "^1.18.0",
66
- "@backstage/plugin-permission-common": "^0.9.0",
67
- "@backstage/plugin-permission-react": "^0.4.34",
68
- "@backstage/theme": "^0.6.6",
64
+ "@backstage/frontend-plugin-api": "^0.11.0",
65
+ "@backstage/plugin-catalog-react": "^1.20.0",
66
+ "@backstage/plugin-permission-common": "^0.9.1",
67
+ "@backstage/plugin-permission-react": "^0.4.36",
68
+ "@backstage/theme": "^0.6.8",
69
69
  "@backstage/types": "^1.2.1",
70
70
  "@material-ui/core": "^4.12.2",
71
71
  "@material-ui/icons": "^4.9.1",
72
72
  "@material-ui/lab": "4.0.0-alpha.61",
73
- "@spotify/backstage-plugin-core": "^0.8.8",
74
- "@spotify/backstage-plugin-rbac-common": "^0.6.7",
73
+ "@spotify/backstage-plugin-core": "^0.8.10",
74
+ "@spotify/backstage-plugin-rbac-common": "^0.6.9",
75
75
  "ajv": "^8.11.2",
76
76
  "file-saver": "^2.0.5",
77
77
  "js-yaml": "^4.1.0",
@@ -90,19 +90,19 @@
90
90
  "react-router-dom": "6.0.0-beta.0 || ^6.3.0"
91
91
  },
92
92
  "devDependencies": {
93
- "@backstage/cli": "^0.32.1",
94
- "@backstage/core-app-api": "^1.17.0",
95
- "@backstage/dev-utils": "^1.1.10",
93
+ "@backstage/cli": "^0.34.0",
94
+ "@backstage/core-app-api": "^1.18.0",
95
+ "@backstage/dev-utils": "^1.1.13",
96
96
  "@backstage/e2e-test-utils": "^0.1.1",
97
- "@backstage/frontend-test-utils": "^0.3.2",
98
- "@backstage/test-utils": "^1.7.8",
97
+ "@backstage/frontend-test-utils": "^0.3.5",
98
+ "@backstage/test-utils": "^1.7.11",
99
99
  "@playwright/test": "^1.32.3",
100
100
  "@sp4b-dev/test-utils": "^0.0.13",
101
101
  "@testing-library/jest-dom": "^6.0.0",
102
102
  "@testing-library/react": "^14.0.0",
103
103
  "@testing-library/user-event": "^14.0.0",
104
104
  "@types/file-saver": "^2.0.5",
105
- "@types/jest": "^29.4.0",
105
+ "@types/jest": "^30.0.0",
106
106
  "@types/node": "^22.0.0",
107
107
  "@types/react-virtualized": "^9.21.21",
108
108
  "cross-fetch": "^4.0.0",