Merge pull request #25604 from backstage/rugvip/policyuser
permission-node: update policy handler to work with new auth system
This commit is contained in:
@@ -66,9 +66,8 @@ import { catalogConditions, createCatalogConditionalDecision, createCatalogPermi
|
||||
/* highlight-remove-next-line */
|
||||
import { createConditionFactory } from '@backstage/plugin-permission-node';
|
||||
/* highlight-add-next-line */
|
||||
import { PermissionPolicy, PolicyQuery, createConditionFactory } from '@backstage/plugin-permission-node';
|
||||
import { PermissionPolicy, PolicyQuery, PolicyQueryUser, createConditionFactory } from '@backstage/plugin-permission-node';
|
||||
/* highlight-add-start */
|
||||
import { BackstageIdentityResponse } from '@backstage/plugin-auth-node';
|
||||
import { AuthorizeResult, PolicyDecision, isResourcePermission } from '@backstage/plugin-permission-common';
|
||||
/* highlight-add-end */
|
||||
...
|
||||
@@ -102,21 +101,21 @@ const isInSystem = createConditionFactory(isInSystemRule);
|
||||
class TestPermissionPolicy implements PermissionPolicy {
|
||||
async handle(
|
||||
request: PolicyQuery,
|
||||
user?: BackstageIdentityResponse,
|
||||
user?: PolicyQueryUser,
|
||||
): Promise<PolicyDecision> {
|
||||
if (isResourcePermission(request.permission, 'catalog-entity')) {
|
||||
return createCatalogConditionalDecision(
|
||||
request.permission,
|
||||
/* highlight-remove-start */
|
||||
catalogConditions.isEntityOwner({
|
||||
claims: user?.identity.ownershipEntityRefs ?? [],
|
||||
claims: user?.info.ownershipEntityRefs ?? [],
|
||||
}),
|
||||
/* highlight-remove-end */
|
||||
/* highlight-add-start */
|
||||
{
|
||||
anyOf: [
|
||||
catalogConditions.isEntityOwner({
|
||||
claims: user?.identity.ownershipEntityRefs ?? [],
|
||||
claims: user?.info.ownershipEntityRefs ?? [],
|
||||
}),
|
||||
isInSystem({ systemRef: 'interviewing' }),
|
||||
],
|
||||
|
||||
@@ -169,15 +169,12 @@ Before running this step, please make sure you followed the steps described in [
|
||||
In order to test the logic above, the integrators of your backstage instance need to change their permission policy to return `DENY` for our newly-created permission:
|
||||
|
||||
```ts title="packages/backend/src/plugins/permission.ts"
|
||||
/* highlight-add-start */
|
||||
import {
|
||||
BackstageIdentityResponse,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
/* highlight-add-end */
|
||||
import {
|
||||
PermissionPolicy,
|
||||
/* highlight-add-next-line */
|
||||
/* highlight-add-start */
|
||||
PolicyQuery,
|
||||
PolicyQueryUser,
|
||||
/* highlight-add-end */
|
||||
} from '@backstage/plugin-permission-node';
|
||||
/* highlight-add-start */
|
||||
import { isPermission } from '@backstage/plugin-permission-common';
|
||||
@@ -190,7 +187,7 @@ class TestPermissionPolicy implements PermissionPolicy {
|
||||
/* highlight-add-start */
|
||||
async handle(
|
||||
request: PolicyQuery,
|
||||
_user?: BackstageIdentityResponse,
|
||||
_user?: PolicyQueryUser,
|
||||
): Promise<PolicyDecision> {
|
||||
if (isPermission(request.permission, todoListCreatePermission)) {
|
||||
return {
|
||||
|
||||
@@ -237,12 +237,12 @@ Let's go back to the permission policy's handle function and try to authorize ou
|
||||
|
||||
```ts title="packages/backend/src/plugins/permission.ts"
|
||||
import {
|
||||
BackstageIdentityResponse,
|
||||
IdentityClient
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import {
|
||||
PermissionPolicy,
|
||||
PolicyQuery,
|
||||
PolicyQueryUser,
|
||||
} from '@backstage/plugin-permission-node';
|
||||
import { isPermission } from '@backstage/plugin-permission-common';
|
||||
/* highlight-remove-next-line */
|
||||
@@ -262,9 +262,9 @@ import {
|
||||
async handle(
|
||||
request: PolicyQuery,
|
||||
/* highlight-remove-next-line */
|
||||
_user?: BackstageIdentityResponse,
|
||||
_user?: PolicyQueryUser,
|
||||
/* highlight-add-next-line */
|
||||
user?: BackstageIdentityResponse,
|
||||
user?: PolicyQueryUser,
|
||||
): Promise<PolicyDecision> {
|
||||
if (isPermission(request.permission, todoListCreatePermission)) {
|
||||
return {
|
||||
@@ -276,7 +276,7 @@ async handle(
|
||||
return createTodoListConditionalDecision(
|
||||
request.permission,
|
||||
todoListConditions.isOwner({
|
||||
userId: user?.identity.userEntityRef ?? '',
|
||||
userId: user?.info.userEntityRef ?? '',
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,10 @@ That policy looked like this:
|
||||
|
||||
```typescript title="packages/backend/src/plugins/permission.ts"
|
||||
class TestPermissionPolicy implements PermissionPolicy {
|
||||
async handle(request: PolicyQuery): Promise<PolicyDecision> {
|
||||
async handle(
|
||||
request: PolicyQuery,
|
||||
_user?: PolicyQueryUser,
|
||||
): Promise<PolicyDecision> {
|
||||
if (request.permission.name === 'catalog.entity.delete') {
|
||||
return {
|
||||
result: AuthorizeResult.DENY,
|
||||
@@ -35,14 +38,6 @@ As we confirmed in the previous section, we know that this now prevents us from
|
||||
Let's change the policy to the following:
|
||||
|
||||
```ts
|
||||
/* highlight-remove-next-line */
|
||||
import { IdentityClient } from '@backstage/plugin-auth-node';
|
||||
/* highlight-add-start */
|
||||
import {
|
||||
BackstageIdentityResponse,
|
||||
IdentityClient
|
||||
} from '@backstage/plugin-auth-node';
|
||||
/* highlight-add-end */
|
||||
import {
|
||||
AuthorizeResult,
|
||||
PolicyDecision,
|
||||
@@ -65,7 +60,7 @@ class TestPermissionPolicy implements PermissionPolicy {
|
||||
/* highlight-add-start */
|
||||
async handle(
|
||||
request: PolicyQuery,
|
||||
user?: BackstageIdentityResponse,
|
||||
user?: PolicyQueryUser,
|
||||
): Promise<PolicyDecision> {
|
||||
/* highlight-add-end */
|
||||
/* highlight-remove-next-line */
|
||||
@@ -81,7 +76,7 @@ class TestPermissionPolicy implements PermissionPolicy {
|
||||
return createCatalogConditionalDecision(
|
||||
request.permission,
|
||||
catalogConditions.isEntityOwner({
|
||||
claims: user?.identity.ownershipEntityRefs ?? [],
|
||||
claims: user?.info.ownershipEntityRefs ?? [],
|
||||
}),
|
||||
);
|
||||
/* highlight-add-end */
|
||||
@@ -95,7 +90,7 @@ Let's walk through the new code that we just added.
|
||||
|
||||
Instead of returning an Definitive Policy Decision, we use factory methods to construct a [Conditional Policy Decision](https://backstage.io/docs/reference/plugin-permission-common.conditionalpolicydecision) (See the [Concepts page](./concepts.md) for more details). Since the policy doesn't have enough information to determine if `user` is the entity owner, this criteria is encapsulated within the conditional decision. However, `createCatalogConditionalDecision` will not compile unless `request.permission` is a catalog entity [`ResourcePermission`](https://backstage.io/docs/reference/plugin-permission-common.resourcepermission). This type constraint ensures that policies return conditional decisions that are compatible with the requested permission. To address this, we use [`isPermission`](https://backstage.io/docs/reference/plugin-permission-common.ispermission) to ["narrow"](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) the type of `request.permission` to `ResourcePermission<'catalog-entity'>`. This matches the runtime behavior that was in place before, but you'll notice that the type of `request.permission` has changed within the scope of that `if` statement.
|
||||
|
||||
The `catalogConditions` object contains all of the rules defined by the catalog plugin. These rules can be combined to form a [`PermissionCriteria`](https://backstage.io/docs/reference/plugin-permission-common.permissioncriteria) object, but for this case we only need to use the `isEntityOwner` rule. This rule accepts a list of entity refs that represent User identity and Group membership used to determine ownership. The second argument to `PermissionPolicy#handle` provides us with a `BackstageIdentityResponse` object, from which we can grab the user's `ownershipEntityRefs`. We provide an empty array as a fallback since the user may be anonymous.
|
||||
The `catalogConditions` object contains all of the rules defined by the catalog plugin. These rules can be combined to form a [`PermissionCriteria`](https://backstage.io/docs/reference/plugin-permission-common.permissioncriteria) object, but for this case we only need to use the `isEntityOwner` rule. This rule accepts a list of entity refs that represent User identity and Group membership used to determine ownership. The second argument to `PermissionPolicy#handle` provides us with a `PolicyQueryUser` object, from which we can grab the user's `ownershipEntityRefs`. We provide an empty array as a fallback since the user may be anonymous.
|
||||
|
||||
You should now be able to see in your Backstage app that the unregister entity button is enabled for entities that you own, but disabled for all other entities!
|
||||
|
||||
@@ -125,7 +120,7 @@ import {
|
||||
class TestPermissionPolicy implements PermissionPolicy {
|
||||
async handle(
|
||||
request: PolicyQuery,
|
||||
user?: BackstageIdentityResponse,
|
||||
user?: PolicyQueryUser,
|
||||
): Promise<PolicyDecision> {
|
||||
/* highlight-remove-next-line */
|
||||
if (isPermission(request.permission, catalogEntityDeletePermission)) {
|
||||
@@ -134,7 +129,7 @@ class TestPermissionPolicy implements PermissionPolicy {
|
||||
return createCatalogConditionalDecision(
|
||||
request.permission,
|
||||
catalogConditions.isEntityOwner({
|
||||
claims: user?.identity.ownershipEntityRefs ?? [],
|
||||
claims: user?.info.ownershipEntityRefs ?? [],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user