Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(rbac): move admin creation to its own file #2349

Merged
merged 9 commits into from
Oct 21, 2024
5 changes: 5 additions & 0 deletions .changeset/angry-ears-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@janus-idp/backstage-plugin-rbac-backend": patch
---

Refactors the rbac backend plugin to move the admin role and admin permission creation to a separate file
125 changes: 125 additions & 0 deletions plugins/rbac-backend/__fixtures__/mock-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { mockCredentials, mockServices } from '@backstage/backend-test-utils';

import type { Enforcer } from 'casbin';
import * as Knex from 'knex';
import { MockClient } from 'knex-mock-client';
import type TypeORMAdapter from 'typeorm-adapter';

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

import { resolve } from 'path';

import { CasbinDBAdapterFactory } from '../src/database/casbin-adapter-factory';
import { ConditionalStorage } from '../src/database/conditional-storage';
import { RoleMetadataStorage } from '../src/database/role-metadata';
import {
EnforcerDelegate,
RoleEventEmitter,
RoleEvents,
} from '../src/service/enforcer-delegate';
import { PluginPermissionMetadataCollector } from '../src/service/plugin-endpoints';

// TODO: Move to 'catalogServiceMock' from '@backstage/plugin-catalog-node/testUtils'
// once '@backstage/plugin-catalog-node' is upgraded
export const catalogApiMock = {
getEntityAncestors: jest.fn().mockImplementation(),
getLocationById: jest.fn().mockImplementation(),
getEntities: jest.fn().mockImplementation(),
getEntitiesByRefs: jest.fn().mockImplementation(),
queryEntities: jest.fn().mockImplementation(),
getEntityByRef: jest.fn().mockImplementation(),
refreshEntity: jest.fn().mockImplementation(),
getEntityFacets: jest.fn().mockImplementation(),
addLocation: jest.fn().mockImplementation(),
getLocationByRef: jest.fn().mockImplementation(),
removeLocationById: jest.fn().mockImplementation(),
removeEntityByUid: jest.fn().mockImplementation(),
validateEntity: jest.fn().mockImplementation(),
getLocationByEntity: jest.fn().mockImplementation(),
};

export const conditionalStorageMock: ConditionalStorage = {
filterConditions: jest.fn().mockImplementation(() => []),
createCondition: jest.fn().mockImplementation(),
checkConflictedConditions: jest.fn().mockImplementation(),
getCondition: jest.fn().mockImplementation(),
deleteCondition: jest.fn().mockImplementation(),
updateCondition: jest.fn().mockImplementation(),
};

export const roleMetadataStorageMock: RoleMetadataStorage = {
filterRoleMetadata: jest.fn().mockImplementation(() => []),
findRoleMetadata: jest.fn().mockImplementation(),
createRoleMetadata: jest.fn().mockImplementation(),
updateRoleMetadata: jest.fn().mockImplementation(),
removeRoleMetadata: jest.fn().mockImplementation(),
};

export const auditLoggerMock = {
getActorId: jest.fn().mockImplementation(),
createAuditLogDetails: jest.fn().mockImplementation(),
auditLog: jest.fn().mockImplementation(),
};

export const pluginMetadataCollectorMock: Partial<PluginPermissionMetadataCollector> =
{
getPluginConditionRules: jest.fn().mockImplementation(),
getPluginPolicies: jest.fn().mockImplementation(),
getMetadataByPluginId: jest.fn().mockImplementation(),
};

export const roleEventEmitterMock: RoleEventEmitter<RoleEvents> = {
on: jest.fn().mockImplementation(),
};

export const enforcerMock: Partial<Enforcer> = {
loadPolicy: jest.fn().mockImplementation(async () => {}),
enableAutoSave: jest.fn().mockImplementation(() => {}),
setRoleManager: jest.fn().mockImplementation(() => {}),
enableAutoBuildRoleLinks: jest.fn().mockImplementation(() => {}),
buildRoleLinks: jest.fn().mockImplementation(() => {}),
};

export const enforcerDelegateMock: Partial<EnforcerDelegate> = {
hasPolicy: jest.fn().mockImplementation(),
hasGroupingPolicy: jest.fn().mockImplementation(),
getPolicy: jest.fn().mockImplementation(),
getGroupingPolicy: jest.fn().mockImplementation(),
getFilteredPolicy: jest.fn().mockImplementation(),
getFilteredGroupingPolicy: jest.fn().mockImplementation(),
addPolicy: jest.fn().mockImplementation(),
addPolicies: jest.fn().mockImplementation(),
addGroupingPolicies: jest.fn().mockImplementation(),
removePolicy: jest.fn().mockImplementation(),
removePolicies: jest.fn().mockImplementation(),
removeGroupingPolicy: jest.fn().mockImplementation(),
removeGroupingPolicies: jest.fn().mockImplementation(),
updatePolicies: jest.fn().mockImplementation(),
updateGroupingPolicies: jest.fn().mockImplementation(),
};

export const dataBaseAdapterFactoryMock: Partial<CasbinDBAdapterFactory> = {
createAdapter: jest.fn((): Promise<TypeORMAdapter> => {
return Promise.resolve({} as TypeORMAdapter);
}),
};

export const providerMock: RBACProvider = {
getProviderName: jest.fn().mockImplementation(),
connect: jest.fn().mockImplementation(),
refresh: jest.fn().mockImplementation(),
};

export const mockClientKnex = Knex.knex({ client: MockClient });

export const mockHttpAuth = mockServices.httpAuth();
export const mockAuthService = mockServices.auth();
export const credentials = mockCredentials.user();
export const mockLoggerService = mockServices.logger.mock();
export const mockUserInfoService = mockServices.userInfo();
export const mockDiscovery = mockServices.discovery.mock();

export const csvPermFile = resolve(
__dirname,
'./../__fixtures__/data/valid-csv/rbac-policy.csv',
);
142 changes: 142 additions & 0 deletions plugins/rbac-backend/__fixtures__/test-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import type { LoggerService } from '@backstage/backend-plugin-api';
import { mockServices } from '@backstage/backend-test-utils';
import { Config } from '@backstage/config';

import {
Adapter,
Enforcer,
Model,
newEnforcer,
newModelFromString,
} from 'casbin';
import * as Knex from 'knex';
import { MockClient } from 'knex-mock-client';

import { CasbinDBAdapterFactory } from '../src/database/casbin-adapter-factory';
import { RoleMetadataStorage } from '../src/database/role-metadata';
import { BackstageRoleManager } from '../src/role-manager/role-manager';
import { EnforcerDelegate } from '../src/service/enforcer-delegate';
import { MODEL } from '../src/service/permission-model';
import { RBACPermissionPolicy } from '../src/service/permission-policy';
import { PluginPermissionMetadataCollector } from '../src/service/plugin-endpoints';
import {
auditLoggerMock,
catalogApiMock,
conditionalStorageMock,
csvPermFile,
mockAuthService,
mockClientKnex,
pluginMetadataCollectorMock,
roleMetadataStorageMock,
} from './mock-utils';

export function newConfig(
permFile?: string,
users?: Array<{ name: string }>,
superUsers?: Array<{ name: string }>,
): Config {
const testUsers = [
{
name: 'user:default/guest',
},
{
name: 'group:default/guests',
},
];

return mockServices.rootConfig({
data: {
permission: {
rbac: {
'policies-csv-file': permFile || csvPermFile,
policyFileReload: true,
admin: {
users: users || testUsers,
superUsers: superUsers,
},
},
},
backend: {
database: {
client: 'better-sqlite3',
connection: ':memory:',
},
},
},
});
}

export async function newAdapter(config: Config): Promise<Adapter> {
return await new CasbinDBAdapterFactory(
config,
mockClientKnex,
).createAdapter();
}

export async function createEnforcer(
theModel: Model,
adapter: Adapter,
logger: LoggerService,
config: Config,
): Promise<Enforcer> {
const catalogDBClient = Knex.knex({ client: MockClient });
const rbacDBClient = Knex.knex({ client: MockClient });
const enf = await newEnforcer(theModel, adapter);

const rm = new BackstageRoleManager(
catalogApiMock,
logger,
catalogDBClient,
rbacDBClient,
config,
mockAuthService,
);
enf.setRoleManager(rm);
enf.enableAutoBuildRoleLinks(false);
await enf.buildRoleLinks();

return enf;
}

export async function newEnforcerDelegate(
adapter: Adapter,
config: Config,
storedPolicies?: string[][],
storedGroupingPolicies?: string[][],
): Promise<EnforcerDelegate> {
const theModel = newModelFromString(MODEL);
const logger = mockServices.logger.mock();

const enf = await createEnforcer(theModel, adapter, logger, config);

if (storedPolicies) {
await enf.addPolicies(storedPolicies);
}

if (storedGroupingPolicies) {
await enf.addGroupingPolicies(storedGroupingPolicies);
}

return new EnforcerDelegate(enf, roleMetadataStorageMock, mockClientKnex);
}

export async function newPermissionPolicy(
config: Config,
enfDelegate: EnforcerDelegate,
roleMock?: RoleMetadataStorage,
): Promise<RBACPermissionPolicy> {
const logger = mockServices.logger.mock();
const permissionPolicy = await RBACPermissionPolicy.build(
logger,
auditLoggerMock,
config,
conditionalStorageMock,
enfDelegate,
roleMock || roleMetadataStorageMock,
mockClientKnex,
pluginMetadataCollectorMock as PluginPermissionMetadataCollector,
mockAuthService,
);
auditLoggerMock.auditLog.mockReset();
return permissionPolicy;
}
Loading