Skip to content

Commit

Permalink
feat(rbac): provide a way to connect rbac providers
Browse files Browse the repository at this point in the history
  • Loading branch information
PatAKnight committed Aug 12, 2024
1 parent 5a74861 commit 11521ab
Show file tree
Hide file tree
Showing 5 changed files with 832 additions and 27 deletions.
26 changes: 26 additions & 0 deletions plugins/rbac-backend/src/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,39 @@ export function policyToString(policy: string[]): string {
return `[${policy.join(', ')}]`;
}

export function groupPolicyToString(policy: string[]): string {
return `g, ${policy.join(', ')}`;
}

export function permissionPolicyToString(policy: string[]): string {
return `p, ${policy.join(', ')}`;
}

export function policiesToString(policies: string[][]): string {
const policiesString = policies
.map(policy => policyToString(policy))
.join(',');
return `[${policiesString}]`;
}

export function groupPoliciesToString(policies: string[][]): string {
const policiesString = policies
.map(policy => groupPolicyToString(policy))
.join('\n');
return `
${policiesString}
`;
}

export function permissionPoliciesToString(policies: string[][]): string {
const policiesString = policies
.map(policy => permissionPolicyToString(policy))
.join('\n');
return `
${policiesString}
`;
}

export function metadataStringToPolicy(policy: string): string[] {
return policy.replace('[', '').replace(']', '').split(', ');
}
Expand Down
13 changes: 13 additions & 0 deletions plugins/rbac-backend/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
PluginIdProvider,
PluginIdProviderExtensionPoint,
pluginIdProviderExtensionPoint,
RBACProvider,
rbacProviderExtensionPoint,
} from '@janus-idp/backstage-plugin-rbac-node';

/**
Expand All @@ -32,6 +34,16 @@ export const rbacPlugin = createBackendPlugin({
pluginIdProviderExtensionPointImpl,
);

const rbacProviders = new Array<RBACProvider>();

env.registerExtensionPoint(rbacProviderExtensionPoint, {
addRBACProvider(
...providers: Array<RBACProvider | Array<RBACProvider>>
): void {
rbacProviders.push(...providers.flat());
},
});

env.registerInit({
deps: {
http: coreServices.httpRouter,
Expand Down Expand Up @@ -77,6 +89,7 @@ export const rbacPlugin = createBackendPlugin({
),
),
},
rbacProviders,
),
);
},
Expand Down
192 changes: 192 additions & 0 deletions plugins/rbac-backend/src/providers/connect-providers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import { LoggerService } from '@backstage/backend-plugin-api';

import { newEnforcer, newModelFromString, StringAdapter } from 'casbin';

import {
RBACProvider,
RBACProviderConnection,
} from '@janus-idp/backstage-plugin-rbac-node';

import { RoleMetadataStorage } from '../database/role-metadata';
import {
groupPoliciesToString,
permissionPoliciesToString,
transformArrayToPolicy,
} from '../helper';
import { EnforcerDelegate } from '../service/enforcer-delegate';
import { MODEL } from '../service/permission-model';
import {
validateGroupingPolicy,
validatePolicy,
validateSource,
} from '../validation/policies-validation';

class Connection implements RBACProviderConnection {
constructor(
private readonly id: string,
private readonly enforcer: EnforcerDelegate,
private readonly roleMetadataStorage: RoleMetadataStorage,
private readonly logger: LoggerService,
) {}

async applyRoles(roles: string[][]): Promise<void> {
const providerRoles: string[][] = [];

const stringPolicy = groupPoliciesToString(roles);

const tempEnforcer = await newEnforcer(
newModelFromString(MODEL),
new StringAdapter(stringPolicy),
);

const currentRoles = await this.roleMetadataStorage.filterRoleMetadata(
this.id,
);
const providerRoleMetadata = currentRoles.map(meta => meta.roleEntityRef);

// Get the roles for this provider coming from rbac plugin
for (const providerRole of providerRoleMetadata) {
providerRoles.push(
...(await this.enforcer.getFilteredGroupingPolicy(1, providerRole)),
);
}

// Remove role
// role exists in rbac but does not exist in provider
for (const role of providerRoles) {
if (!(await tempEnforcer.hasGroupingPolicy(...role))) {
const roleMeta = await this.roleMetadataStorage.findRoleMetadata(
role[1],
);

const currentRole = await this.enforcer.getFilteredGroupingPolicy(
1,
role[1],
);

if (!roleMeta) {
throw new Error('role does not exist');
}

// Only one role exists in rbac remove role metadata as well
if (roleMeta && currentRole.length === 1) {
await this.enforcer.removeGroupingPolicy(role, roleMeta);
continue; // Move on to the next role
}

await this.enforcer.removeGroupingPolicy(role, roleMeta, true);
}
}

// Add the role
// role exists in provider but does not exist in rbac
for (const role of roles) {
if (!(await this.enforcer.hasGroupingPolicy(...role))) {
const err = await validateGroupingPolicy(
role,
this.roleMetadataStorage,
this.id,
);

if (err) {
this.logger.warn(err.message);
continue; // Skip adding this role as there was an error
}

const roleMeta = await this.roleMetadataStorage.findRoleMetadata(
role[1],
);

// role does not exist in rbac, create the metadata for it
if (!roleMeta) {
const roleMetadata = {
modifiedBy: this.id,
source: this.id,
roleEntityRef: role[1],
};
await this.enforcer.addGroupingPolicy(role, roleMetadata); // <- TODO more knex trax issues
continue; // Move on to the next role
}

await this.enforcer.addGroupingPolicy(role, roleMeta);
}
}
}

async applyPermissions(permissions: string[][]): Promise<void> {
const stringPolicy = permissionPoliciesToString(permissions);

const providerPermissions: string[][] = [];

const tempEnforcer = await newEnforcer(
newModelFromString(MODEL),
new StringAdapter(stringPolicy),
);

const currentRoles = await this.roleMetadataStorage.filterRoleMetadata(
this.id,
);
const providerRoleMetadata = currentRoles.map(meta => meta.roleEntityRef);

// Get the roles for this provider coming from rbac plugin
for (const providerRole of providerRoleMetadata) {
providerPermissions.push(
...(await this.enforcer.getFilteredPolicy(0, providerRole)),
);
}

for (const permission of providerPermissions) {
if (!(await tempEnforcer.hasPolicy(...permission))) {
this.enforcer.removePolicy(permission);
}
}

for (const permission of permissions) {
if (!(await this.enforcer.hasPolicy(...permission))) {
const transformedPolicy = transformArrayToPolicy(permission);
const metadata = await this.roleMetadataStorage.findRoleMetadata(
permission[0],
);

let err = validatePolicy(transformedPolicy);
if (err) {
this.logger.warn(`Invalid permission policy, ${err}`);
continue; // Skip this invalid permission policy
}

err = await validateSource(this.id, metadata);
if (err) {
this.logger.warn(
`Unable to add policy ${permission}. Cause: ${err.message}`,
);
continue;
}

await this.enforcer.addPolicy(permission);
}
}
}

async refresh(): Promise<void> {
console.log('refresh for manually refreshing');
}
}

export async function connectRBACProviders(
providers: RBACProvider[],
enforcer: EnforcerDelegate,
roleMetadataStorage: RoleMetadataStorage,
logger: LoggerService,
) {
await Promise.all(
providers.map(async provider => {
const connection = new Connection(
provider.getProviderName(),
enforcer,
roleMetadataStorage,
logger,
);
return provider.connect(connection);
}),
);
}
14 changes: 13 additions & 1 deletion plugins/rbac-backend/src/service/policy-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@ import { newEnforcer, newModelFromString } from 'casbin';
import { Router } from 'express';

import { DefaultAuditLogger } from '@janus-idp/backstage-plugin-audit-log-node';
import { PluginIdProvider } from '@janus-idp/backstage-plugin-rbac-node';
import {
PluginIdProvider,
RBACProvider,
} from '@janus-idp/backstage-plugin-rbac-node';

import { CasbinDBAdapterFactory } from '../database/casbin-adapter-factory';
import { DataBaseConditionalStorage } from '../database/conditional-storage';
import { migrate } from '../database/migration';
import { DataBaseRoleMetadataStorage } from '../database/role-metadata';
import { connectRBACProviders } from '../providers/connect-providers';
import { BackstageRoleManager } from '../role-manager/role-manager';
import { EnforcerDelegate } from './enforcer-delegate';
import { MODEL } from './permission-model';
Expand All @@ -45,6 +49,7 @@ export class PolicyBuilder {
userInfo: UserInfoService;
},
pluginIdProvider: PluginIdProvider = { getPluginIds: () => [] },
rbacProviders: Array<RBACProvider>,
): Promise<Router> {
const isPluginEnabled = env.config.getOptionalBoolean('permission.enabled');
if (isPluginEnabled) {
Expand Down Expand Up @@ -106,6 +111,13 @@ export class PolicyBuilder {
httpAuthService: httpAuth,
});

await connectRBACProviders(
rbacProviders,
enforcerDelegate,
roleMetadataStorage,
env.logger,
);

const pluginIdsConfig = env.config.getOptionalStringArray(
'permission.rbac.pluginsWithPermission',
);
Expand Down
Loading

0 comments on commit 11521ab

Please sign in to comment.