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

feat: add permission #53

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"dependencies": {
"@backstage/backend-common": "^0.22.0",
"@backstage/backend-defaults": "^0.2.18",
"@backstage/backend-plugin-api": "^0.8.0",
"@backstage/backend-tasks": "^0.5.23",
"@backstage/config": "^1.2.0",
"@backstage/plugin-app-backend": "^0.3.66",
Expand All @@ -39,6 +40,7 @@
"@backstage/plugin-search-backend-node": "^1.2.22",
"@backstage/plugin-techdocs-backend": "^1.10.5",
"@electrolux-oss/plugin-infrawallet-backend": "workspace:^",
"@electrolux-oss/plugin-infrawallet-common": "workspace:^",
"app": "link:../app",
"better-sqlite3": "^9.0.0",
"dockerode": "^3.3.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ backend.add(import('@backstage/plugin-catalog-backend-module-scaffolder-entity-m

// permission plugin
backend.add(import('@backstage/plugin-permission-backend/alpha'));
backend.add(import('@backstage/plugin-permission-backend-module-allow-all-policy'));
backend.add(import('./permissionPolicyModule'));

// search plugin
backend.add(import('@backstage/plugin-search-backend/alpha'));
Expand Down
32 changes: 32 additions & 0 deletions packages/backend/src/permissionPolicyModule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { createBackendModule } from '@backstage/backend-plugin-api';
import { BackstageIdentityResponse } from '@backstage/plugin-auth-node';
import { AuthorizeResult, isPermission, PolicyDecision } from '@backstage/plugin-permission-common';
import { PermissionPolicy, PolicyQuery } from '@backstage/plugin-permission-node';
import { policyExtensionPoint } from '@backstage/plugin-permission-node/alpha';
import { infraWalletReportRead } from '@electrolux-oss/plugin-infrawallet-common';

class DefaultPermissionPolicy implements PermissionPolicy {
async handle(request: PolicyQuery, _user?: BackstageIdentityResponse): Promise<PolicyDecision> {
// Example deny read report
if (isPermission(request.permission, infraWalletReportRead)) {
return { result: AuthorizeResult.ALLOW };
}

return { result: AuthorizeResult.ALLOW };
}
}

export default createBackendModule({
pluginId: 'permission',
moduleId: 'defaultPolicy',
register(env) {
env.registerInit({
deps: {
policy: policyExtensionPoint,
},
async init({ policy }) {
policy.setPolicy(new DefaultPermissionPolicy());
},
});
},
});
5 changes: 5 additions & 0 deletions plugins/infrawallet-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,20 @@
"@backstage/backend-defaults": "^0.2.18",
"@backstage/backend-plugin-api": "^0.6.18",
"@backstage/config": "^1.2.0",
"@backstage/errors": "^1.2.4",
"@backstage/plugin-permission-common": "^0.8.0",
"@backstage/plugin-permission-node": "^0.8.0",
"@backstage/types": "^1.1.1",
"@datadog/datadog-api-client": "^1.26.0",
"@electrolux-oss/plugin-infrawallet-common": "workspace:^",
"@google-cloud/bigquery": "7.7.1",
"@types/express": "*",
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"lodash": "^4.17.21",
"moment": "2.29.4",
"node-fetch": "^2.6.7",
"pg-format": "^1.0.4",
"winston": "^3.2.1",
"yn": "^4.0.0"
},
Expand Down
8 changes: 7 additions & 1 deletion plugins/infrawallet-backend/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,20 @@ export const infraWalletPlugin = createBackendPlugin({
config: coreServices.rootConfig,
cache: coreServices.cache,
database: coreServices.database,
permissions: coreServices.permissions,
discovery: coreServices.discovery,
httpAuth: coreServices.httpAuth,
},
async init({ httpRouter, logger, config, cache, database }) {
async init({ httpRouter, logger, config, cache, database, permissions, discovery, httpAuth }) {
httpRouter.use(
await createRouter({
logger,
config,
cache,
database,
permissions,
discovery,
httpAuth,
}),
);
httpRouter.addAuthPolicy({
Expand Down
75 changes: 71 additions & 4 deletions plugins/infrawallet-backend/src/service/router.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import { errorHandler } from '@backstage/backend-common';
import { CacheService, DatabaseService, LoggerService, resolvePackagePath } from '@backstage/backend-plugin-api';
import { createLegacyAuthAdapters, errorHandler } from '@backstage/backend-common';
import { NotAllowedError } from '@backstage/errors';
import {
AuthService,
CacheService,
DatabaseService,
DiscoveryService,
HttpAuthService,
LoggerService,
PermissionsService,
resolvePackagePath,
} from '@backstage/backend-plugin-api';
import { Config } from '@backstage/config';
import express from 'express';
import Router from 'express-promise-router';
Expand All @@ -13,12 +23,23 @@ import { InfraWalletClient } from './InfraWalletClient';
import { MetricProvider } from './MetricProvider';
import { COST_CLIENT_MAPPINGS, METRIC_PROVIDER_MAPPINGS } from './consts';
import { CloudProviderError, Metric, MetricSetting, Report } from './types';
import {
AuthorizePermissionRequest,
AuthorizeResult,
QueryPermissionRequest,
} from '@backstage/plugin-permission-common';
import { permissions } from '@electrolux-oss/plugin-infrawallet-common';
import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node';

export interface RouterOptions {
logger: LoggerService;
config: Config;
cache: CacheService;
database: DatabaseService;
permissions: PermissionsService;
discovery: DiscoveryService;
auth?: AuthService;
httpAuth?: HttpAuthService;
}

async function setUpDatabase(database: DatabaseService) {
Expand All @@ -37,19 +58,49 @@ async function setUpDatabase(database: DatabaseService) {
}

export async function createRouter(options: RouterOptions): Promise<express.Router> {
const { logger, config, cache, database } = options;
const { logger, config, cache, database, permissions: permissionEvaluator } = options;
const { httpAuth } = createLegacyAuthAdapters(options);

const evaluateRequestPermission = async (
request: express.Request,
permission: AuthorizePermissionRequest | QueryPermissionRequest,
) => {
const credentials = await httpAuth.credentials(request, {
allow: ['user'],
});

const decision = permissions
? (await permissionEvaluator.authorize([permission as AuthorizePermissionRequest], { credentials }))[0]
: undefined;

if (decision && decision.result === AuthorizeResult.DENY) {
throw new NotAllowedError('Unauthorized');
}

return { decision, user: credentials.principal };
};

// do database migrations here to support the legacy backend system
await setUpDatabase(database);

const router = Router();
router.use(express.json());
router.use(
createPermissionIntegrationRouter({
permissions: Object.values(permissions),
}),
);

router.get('/health', (_, response) => {
logger.info('PONG!');
response.json({ status: 'ok' });
});

router.get('/reports', async (request, response) => {
await evaluateRequestPermission(request, {
permission: permissions.infraWalletReportRead,
});

const filters = request.query.filters as string;
const groups = request.query.groups as string;
const granularity = request.query.granularity as string;
Expand Down Expand Up @@ -161,12 +212,20 @@ export async function createRouter(options: RouterOptions): Promise<express.Rout
});

router.get('/:walletName/metrics_setting', async (request, response) => {
await evaluateRequestPermission(request, {
permission: permissions.infraWalletMetricSettingsRead,
});

const walletName = request.params.walletName;
const metricSettings = await getWalletMetricSettings(database, walletName);
response.json({ data: metricSettings, status: 200 });
});

router.get('/metric/metric_configs', async (_request, response) => {
router.get('/metric/metric_configs', async (request, response) => {
await evaluateRequestPermission(request, {
permission: permissions.infraWalletMetricSettingsRead,
});

const conf = config.getConfig('backend.infraWallet.metricProviders');
const configNames: { metric_provider: string; config_name: string }[] = [];
conf.keys().forEach((provider: string) => {
Expand All @@ -182,6 +241,10 @@ export async function createRouter(options: RouterOptions): Promise<express.Rout
});

router.put('/:walletName/metrics_setting', async (request, response) => {
await evaluateRequestPermission(request, {
permission: permissions.infraWalletMetricSettingsCreate,
});

const readOnly = config.getOptionalBoolean('infraWallet.settings.readOnly') ?? false;

if (readOnly) {
Expand All @@ -194,6 +257,10 @@ export async function createRouter(options: RouterOptions): Promise<express.Rout
});

router.delete('/:walletName/metrics_setting', async (request, response) => {
await evaluateRequestPermission(request, {
permission: permissions.infraWalletMetricSettingsDelete,
});

const readOnly = config.getOptionalBoolean('infraWallet.settings.readOnly') ?? false;

if (readOnly) {
Expand Down
1 change: 1 addition & 0 deletions plugins/infrawallet-common/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
3 changes: 3 additions & 0 deletions plugins/infrawallet-common/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# InfraWallet Common

Common functionalities, types, and permissions for the infrawallet plugin.
40 changes: 40 additions & 0 deletions plugins/infrawallet-common/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "@electrolux-oss/plugin-infrawallet-common",
"version": "0.1.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"homepage": "https://opensource.electrolux.one",
"repository": {
"type": "git",
"url": "https://github.com/electrolux-oss/infrawallet",
"directory": "plugins/infrawallet-common"
},
"backstage": {
"role": "common-library"
},
"sideEffects": false,
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"clean": "backstage-cli package clean",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack"
},
"dependencies": {
"@backstage/plugin-permission-common": "^0.8.0"
},
"devDependencies": {
"@backstage/cli": "^0.26.11"
},
"files": [
"dist"
]
}
1 change: 1 addition & 0 deletions plugins/infrawallet-common/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './permissions';
54 changes: 54 additions & 0 deletions plugins/infrawallet-common/src/permissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { createPermission } from '@backstage/plugin-permission-common';

/**
* @public
*/
export const infraWalletReportRead = createPermission({
name: 'infrawallet.report.read',
attributes: { action: 'read' },
});

/**
* @public
*/
export const infraWalletMetricSettingsRead = createPermission({
name: 'infrawallet.metric.settings.read',
attributes: { action: 'read' },
});

/**
* @public
*/
export const infraWalletMetricSettingsCreate = createPermission({
name: 'infrawallet.metric.settings.create',
attributes: { action: 'create' },
});

/**
* @public
*/
export const infraWalletMetricSettingsUpdate = createPermission({
name: 'infrawallet.metric.settings.update',
attributes: { action: 'update' },
});

/**
* @public
*/
export const infraWalletMetricSettingsDelete = createPermission({
name: 'infrawallet.metric.settings.delete',
attributes: { action: 'delete' },
});

/**
* List of all permissions
*
* @public
*/
export const permissions = {
infraWalletReportRead,
infraWalletMetricSettingsRead,
infraWalletMetricSettingsCreate,
infraWalletMetricSettingsUpdate,
infraWalletMetricSettingsDelete,
};
1 change: 1 addition & 0 deletions plugins/infrawallet-common/src/setupTests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {};
2 changes: 2 additions & 0 deletions plugins/infrawallet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@
"@backstage/config": "^1.2.0",
"@backstage/core-components": "^0.14.7",
"@backstage/core-plugin-api": "^1.9.2",
"@backstage/plugin-permission-react": "^0.4.24",
"@backstage/theme": "^0.5.4",
"@electrolux-oss/plugin-infrawallet-common": "workspace:^",
"@material-ui/core": "^4.9.13",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "^4.0.0-alpha.61",
Expand Down
Loading