Payment methos migration + add flags
Enforce own user authorization filter for payment
Create flag auth depends on the user that is beeing modified
Adapt auth definition for payment method requirement
Add flags for the GUI
MODIFY
src/authorization/AuthorizationsDefinition.ts
src/authorization/AuthorizationsDefinition.ts
@@ -692,8 +692,26 @@ export const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
}
}
},
- { resource: Entity.PAYMENT_METHOD, action: Action.LIST },
- { resource: Entity.PAYMENT_METHOD, action: [Action.READ, Action.CREATE, Action.DELETE] },
+ {
+ resource: Entity.PAYMENT_METHOD, action: Action.LIST,
+ condition: {
+ Fn: 'custom:dynamicAuthorizations',
+ args: {
+ asserts: [],
+ filters: ['OwnUser']
+ }
+ }
+ },
+ {
+ resource: Entity.PAYMENT_METHOD, action: [Action.READ, Action.CREATE, Action.DELETE],
+ condition: {
+ Fn: 'custom:dynamicAuthorizations',
+ args: {
+ asserts: [],
+ filters: ['OwnUser']
+ }
+ }
+ },
{
resource: Entity.SITE, action: Action.LIST,
condition: {

MODIFY
src/server/rest/v1/service/AuthorizationService.ts
src/server/rest/v1/service/AuthorizationService.ts
import { Action, AuthorizationActions, AuthorizationContext, AuthorizationFilter, Entity } from '../../../../types/Authorization';
-import { AssetDataResult, BillingInvoiceDataResult, CarCatalogDataResult, CarDataResult, CompanyDataResult, LogDataResult, PricingDefinitionDataResult, RegistrationTokenDataResult, SiteAreaDataResult, SiteDataResult, TagDataResult, UserDataResult } from '../../../../types/DataResult';
+import { AssetDataResult, BillingInvoiceDataResult, BillingPaymentMethodDataResult, CarCatalogDataResult, CarDataResult, CompanyDataResult, LogDataResult, PricingDefinitionDataResult, RegistrationTokenDataResult, SiteAreaDataResult, SiteDataResult, TagDataResult, UserDataResult } from '../../../../types/DataResult';
+import { BillingInvoice, BillingPaymentMethod } from '../../../../types/Billing';
import { Car, CarCatalog } from '../../../../types/Car';
import { HttpAssetRequest, HttpAssetsRequest } from '../../../../types/requests/HttpAssetRequest';
-import { HttpBillingInvoiceRequest, HttpBillingInvoicesRequest } from '../../../../types/requests/HttpBillingRequest';
+import { HttpBillingInvoiceRequest, HttpBillingInvoicesRequest, HttpDeletePaymentMethod, HttpPaymentMethods, HttpSetupPaymentMethod } from '../../../../types/requests/HttpBillingRequest';
import { HttpCarCatalogRequest, HttpCarCatalogsRequest, HttpCarRequest, HttpCarsRequest } from '../../../../types/requests/HttpCarRequest';
import { HttpChargingStationRequest, HttpChargingStationsRequest } from '../../../../types/requests/HttpChargingStationRequest';
import { HttpCompaniesRequest, HttpCompanyRequest } from '../../../../types/requests/HttpCompanyRequest';
@@ -18,7 +19,6 @@ import User, { UserRole } from '../../../../types/User';
import AppAuthError from '../../../../exception/AppAuthError';
import Asset from '../../../../types/Asset';
import Authorizations from '../../../../authorization/Authorizations';
-import { BillingInvoice } from '../../../../types/Billing';
import Company from '../../../../types/Company';
import DynamicAuthorizationFactory from '../../../../authorization/DynamicAuthorizationFactory';
import { EntityData } from '../../../../types/GlobalType';
@@ -617,6 +617,7 @@ export default class AuthorizationService {
authorizationFilter: AuthorizationFilter): Promise<void> {
// Add Meta Data
billingInvoices.metadata = authorizationFilter.metadata;
+ billingInvoices.canListUsers = await AuthorizationService.canPerformAuthorizationAction(tenant, userToken, Entity.USER, Action.LIST, authorizationFilter);
for (const billingInvoice of billingInvoices.result) {
await AuthorizationService.addInvoiceAuthorizations(tenant, userToken, billingInvoice, authorizationFilter);
}
@@ -630,6 +631,54 @@ export default class AuthorizationService {
}
// end invoices
+ // payment
+ public static async checkAndGetPaymentMethodsAuthorizations(tenant: Tenant, userToken: UserToken,
+ filteredRequest: Partial<HttpPaymentMethods>): Promise<AuthorizationFilter> {
+ const authorizationFilters: AuthorizationFilter = {
+ filters: {},
+ dataSources: new Map(),
+ projectFields: [],
+ authorized: false
+ };
+ // Check static & dynamic authorization
+ await AuthorizationService.canPerformAuthorizationAction(tenant, userToken, Entity.PAYMENT_METHOD, Action.LIST,
+ authorizationFilters, filteredRequest.userID ? { UserID: filteredRequest.userID } : {}, null, true);
+ return authorizationFilters;
+ }
+
+ // Entity data is not usable as is since we use API calls
+ public static async checkAndGetPaymentMethodAuthorizations(tenant: Tenant, userToken: UserToken,
+ filteredRequest: Partial<HttpSetupPaymentMethod | HttpDeletePaymentMethod>, authAction: Action, entityData?: EntityData): Promise<AuthorizationFilter> {
+ return AuthorizationService.checkAndGetEntityAuthorizations(
+ tenant, Entity.PAYMENT_METHOD, userToken, filteredRequest, filteredRequest.userID ? { UserID: filteredRequest.userID } : {},
+ authAction, entityData);
+ }
+
+ // SPECIAL CASE: to apply dynamic filters we need the filtered request as it contains the userID (can be different from the userToken)
+ public static async addPaymentMethodsAuthorizations(tenant: Tenant, userToken: UserToken, billingPaymentMethods: BillingPaymentMethodDataResult,
+ authorizationFilter: AuthorizationFilter, filteredRequest: Partial<HttpPaymentMethods>): Promise<void> {
+ // Add Meta Data
+ billingPaymentMethods.metadata = authorizationFilter.metadata;
+ // Add Authorizations
+ billingPaymentMethods.canCreate = await AuthorizationService.canPerformAuthorizationAction(tenant, userToken, Entity.PAYMENT_METHOD,
+ Action.CREATE, authorizationFilter, filteredRequest.userID ? { UserID: filteredRequest.userID } : {});
+ for (const billingPaymentMethod of billingPaymentMethods.result) {
+ await AuthorizationService.addPaymentMethodAuthorizations(tenant, userToken, billingPaymentMethod, authorizationFilter);
+ }
+ }
+
+ public static async addPaymentMethodAuthorizations(tenant: Tenant, userToken: UserToken, billingPaymentMethod: BillingPaymentMethod,
+ authorizationFilter: AuthorizationFilter): Promise<void> {
+ billingPaymentMethod.canRead = true; // Always true as it should be filtered upfront
+ // Cannot delete default payment
+ billingPaymentMethod.canDelete = !billingPaymentMethod.isDefault && await AuthorizationService.canPerformAuthorizationAction(
+ tenant, userToken, Entity.PAYMENT_METHOD, Action.DELETE, authorizationFilter, { billingPaymentMethodID: billingPaymentMethod.id }, billingPaymentMethod);
+ // Optimize data over the net
+ Utils.removeCanPropertiesWithFalseValue(billingPaymentMethod);
+ }
+
+ // end payment
+
public static async checkAndGetCarsAuthorizations(tenant: Tenant, userToken: UserToken, filteredRequest: Partial<HttpCarsRequest>): Promise<AuthorizationFilter> {
const authorizationFilters: AuthorizationFilter = {
filters: {},

MODIFY
src/server/rest/v1/service/BillingService.ts
src/server/rest/v1/service/BillingService.ts
import { Action, Entity } from '../../../../types/Authorization';
-import { BillingInvoiceDataResult, DataResult } from '../../../../types/DataResult';
+import { BillingInvoiceDataResult, BillingPaymentMethodDataResult } from '../../../../types/DataResult';
import { BillingInvoiceStatus, BillingOperationResult, BillingPaymentMethod } from '../../../../types/Billing';
import { HTTPAuthError, HTTPError } from '../../../../types/HTTPError';
import { NextFunction, Request, Response } from 'express';
@@ -251,14 +251,8 @@ export default class BillingService {
Action.BILLING_SETUP_PAYMENT_METHOD, Entity.BILLING, MODULE_NAME, 'handleSetupSetupPaymentMethod');
// Filter
const filteredRequest = BillingValidator.getInstance().validateBillingSetupUserPaymentMethodReq(req.body);
- if (!await Authorizations.canCreatePaymentMethod(req.user, filteredRequest.userID)) {
- throw new AppAuthError({
- errorCode: HTTPAuthError.FORBIDDEN,
- user: req.user,
- action: Action.CREATE, entity: Entity.PAYMENT_METHOD,
- module: MODULE_NAME, method: 'handleBillingSetupPaymentMethod'
- });
- }
+ // Dynamic auth
+ await AuthorizationService.checkAndGetPaymentMethodAuthorizations(req.tenant, req.user, filteredRequest, Action.CREATE);
// Get the billing impl
const billingImpl = await BillingFactory.getBillingImpl(req.tenant);
if (!billingImpl) {
@@ -270,10 +264,8 @@ export default class BillingService {
user: req.user
});
}
- // Get user - ACHTUNG user !== req.user
- const user: User = await UserStorage.getUser(req.tenant, filteredRequest.userID);
- UtilsService.assertObjectExists(action, user, `User ID '${filteredRequest.userID}' does not exist`,
- MODULE_NAME, 'handleSetupPaymentMethod', req.user);
+ // Check and get user for whom we wish to update the payment method
+ const user: User = await UtilsService.checkAndGetUserAuthorization(req.tenant, req.user, filteredRequest.userID, Action.READ, action);
// Invoke the billing implementation
const paymentMethodId: string = filteredRequest.paymentMethodId;
const operationResult: BillingOperationResult = await billingImpl.setupPaymentMethod(user, paymentMethodId);
@@ -290,11 +282,12 @@ export default class BillingService {
// Check if component is active
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.BILLING,
Action.BILLING_PAYMENT_METHODS, Entity.BILLING, MODULE_NAME, 'handleBillingGetPaymentMethods');
- if (!await Authorizations.canListPaymentMethod(req.user)) {
+ const authorizations = await AuthorizationService.checkAndGetPaymentMethodsAuthorizations(req.tenant, req.user,filteredRequest);
+ if (!authorizations.authorized) {
throw new AppAuthError({
errorCode: HTTPAuthError.FORBIDDEN,
user: req.user,
- action: Action.CREATE, entity: Entity.PAYMENT_METHOD,
+ action: Action.LIST, entity: Entity.PAYMENT_METHOD,
module: MODULE_NAME, method: 'handleBillingGetPaymentMethods'
});
}
@@ -310,9 +303,7 @@ export default class BillingService {
});
}
// Get user - ACHTUNG user !== req.user
- const user: User = await UserStorage.getUser(req.tenant, filteredRequest.userID);
- UtilsService.assertObjectExists(action, user, `User ID '${filteredRequest.userID}' does not exist`,
- MODULE_NAME, 'handleBillingGetPaymentMethods', req.user);
+ const user: User = await UtilsService.checkAndGetUserAuthorization(req.tenant, req.user, filteredRequest.userID, Action.READ, action);
// Invoke the billing implementation
const paymentMethods: BillingPaymentMethod[] = await billingImpl.getPaymentMethods(user);
await Logging.logInfo({
@@ -322,10 +313,11 @@ export default class BillingService {
module: MODULE_NAME, method: 'getPaymentMethods',
message: `Number of payment methods: ${paymentMethods?.length}`
});
- const dataResult: DataResult<BillingPaymentMethod> = {
+ const dataResult: BillingPaymentMethodDataResult = {
count: paymentMethods.length,
result: paymentMethods
};
+ await AuthorizationService.addPaymentMethodsAuthorizations(req.tenant, req.user, dataResult, authorizations, filteredRequest);
res.json(dataResult);
next();
}
@@ -336,14 +328,8 @@ export default class BillingService {
// Check if component is active
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.BILLING,
Action.BILLING_PAYMENT_METHODS, Entity.BILLING, MODULE_NAME, 'handleBillingDeletePaymentMethod');
- if (!await Authorizations.canDeletePaymentMethod(req.user)) {
- throw new AppAuthError({
- errorCode: HTTPAuthError.FORBIDDEN,
- user: req.user,
- action: Action.DELETE, entity: Entity.PAYMENT_METHOD,
- module: MODULE_NAME, method: 'handleBillingDeletePaymentMethod'
- });
- }
+ // Dynamic auth
+ await AuthorizationService.checkAndGetPaymentMethodAuthorizations(req.tenant, req.user, filteredRequest, Action.DELETE);
// Get the billing impl
const billingImpl = await BillingFactory.getBillingImpl(req.tenant);
if (!billingImpl) {
@@ -355,9 +341,8 @@ export default class BillingService {
user: req.user
});
}
- const userID = filteredRequest.userID;
- const user: User = await UserStorage.getUser(req.tenant, userID);
- UtilsService.assertObjectExists(action, user, `User ID '${userID}' does not exist`,
+ const user: User = await UtilsService.checkAndGetUserAuthorization(req.tenant, req.user, filteredRequest.userID, Action.READ, action);
+ UtilsService.assertObjectExists(action, user, `User ID '${filteredRequest.userID}' does not exist`,
MODULE_NAME, 'handleBillingDeletePaymentMethod', req.user);
// Invoke the billing implementation
const operationResult: BillingOperationResult = await billingImpl.deletePaymentMethod(user, filteredRequest.paymentMethodId);

MODIFY
src/types/Billing.ts
src/types/Billing.ts
+import { AuthorizationActions, BillingInvoiceAuthorizationActions } from './Authorization';
+
import { ActionsResponse } from './GlobalType';
-import { BillingInvoiceAuthorizationActions } from './Authorization';
import CreatedUpdatedProps from './CreatedUpdatedProps';
import { PricedConsumptionData } from './Pricing';
import User from './User';
@@ -114,7 +115,7 @@ export interface BillingOperationResult {
internalData?: unknown // an object returned by the concrete implementation - e.g.: STRIPE
}
-export interface BillingPaymentMethod {
+export interface BillingPaymentMethod extends AuthorizationActions {
id: string;
brand: string;
expiringOn: Date;

MODIFY
src/types/DataResult.ts
src/types/DataResult.ts
+import { BillingInvoice, BillingPaymentMethod } from './Billing';
import { Car, CarCatalog } from './Car';
import Transaction, { TransactionStats } from './Transaction';
import Asset from './Asset';
import { AuthorizationDefinitionFieldMetadata } from './Authorization';
-import { BillingInvoice } from './Billing';
import Company from './Company';
import { Log } from './Log';
import PricingDefinition from './Pricing';
@@ -81,8 +81,13 @@ export interface TransactionDataResult extends DataResult<Transaction> {
stats: TransactionStats;
}
-// eslint-disable-next-line @typescript-eslint/no-empty-interface
-export interface BillingInvoiceDataResult extends DataResult<BillingInvoice> {}
+export interface BillingInvoiceDataResult extends DataResult<BillingInvoice> {
+ canListUsers?: boolean;
+}
+
+export interface BillingPaymentMethodDataResult extends DataResult<BillingPaymentMethod> {
+ canCreate?: boolean;
+}
export interface TransactionRefundDataResult {
count: number;

MODIFY
src/types/GlobalType.ts
src/types/GlobalType.ts
+import { BillingInvoice, BillingPaymentMethod } from './Billing';
import { Car, CarCatalog } from './Car';
-import { BillingInvoice } from './Billing';
import ChargingStation from './ChargingStation';
import Company from './Company';
import JsonCentralSystemServer from '../server/ocpp/json/JsonCentralSystemServer';
@@ -75,7 +75,7 @@ export enum ImportStatus {
ERROR = 'E',
}
-export type EntityData = Car|User|Company|Site|SiteArea|Tag|CarCatalog|ChargingStation|PricingDefinition|Log|RegistrationToken|BillingInvoice;
+export type EntityData = Car|User|Company|Site|SiteArea|Tag|CarCatalog|ChargingStation|PricingDefinition|Log|RegistrationToken|BillingInvoice|BillingPaymentMethod;
interface TSGlobal extends Global {
database: MongoDBStorage;

Payment methos migration + add flags
- Enforce own user authorization filter for payment
- Create flag auth depends on the user that is beeing modified
- Adapt auth definition for payment method requirement
- Add flags for the GUI