Invoice migration
Use auth definition and remove admin
Add params in invoice storage get
Add authorizations functions check and refactoring
MODIFY
src/authorization/AuthorizationsDefinition.ts
src/authorization/AuthorizationsDefinition.ts
@@ -671,6 +671,13 @@ export const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
},
{
resource: Entity.INVOICE, action: [Action.LIST],
+ condition: {
+ Fn: 'custom:dynamicAuthorizations',
+ args: {
+ asserts: [],
+ filters: ['OwnUser']
+ }
+ },
attributes: [
'id', 'number', 'status', 'amount', 'createdOn', 'currency', 'downloadable', 'sessions', 'userID', 'user.id', 'user.name', 'user.firstName', 'user.email'
]

MODIFY
src/server/rest/v1/service/AuthorizationService.ts
src/server/rest/v1/service/AuthorizationService.ts
@@ -2,6 +2,7 @@ import { Action, AuthorizationActions, AuthorizationContext, AuthorizationFilter
import { AssetDataResult, BillingInvoiceDataResult, CarCatalogDataResult, CarDataResult, CompanyDataResult, LogDataResult, PricingDefinitionDataResult, RegistrationTokenDataResult, SiteAreaDataResult, SiteDataResult, TagDataResult, UserDataResult } from '../../../../types/DataResult';
import { Car, CarCatalog } from '../../../../types/Car';
import { HttpAssetRequest, HttpAssetsRequest } from '../../../../types/requests/HttpAssetRequest';
+import { HttpBillingInvoiceRequest, HttpBillingInvoicesRequest } 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';
@@ -22,7 +23,6 @@ import Company from '../../../../types/Company';
import DynamicAuthorizationFactory from '../../../../authorization/DynamicAuthorizationFactory';
import { EntityData } from '../../../../types/GlobalType';
import { HTTPAuthError } from '../../../../types/HTTPError';
-import { HttpBillingInvoicesRequest } from '../../../../types/requests/HttpBillingRequest';
import { HttpLogRequest } from '../../../../types/requests/HttpLoggingRequest';
import { HttpRegistrationTokenRequest } from '../../../../types/requests/HttpRegistrationToken';
import { Log } from '../../../../types/Log';
@@ -230,7 +230,7 @@ export default class AuthorizationService {
}
public static async checkAndGetUsersAuthorizations(tenant: Tenant, userToken: UserToken,
- filteredRequest: Partial<HttpUsersRequest>): Promise<AuthorizationFilter> {
+ filteredRequest: Partial<HttpUsersRequest>, failsWithException = true): Promise<AuthorizationFilter> {
const authorizationFilters: AuthorizationFilter = {
filters: {},
dataSources: new Map(),
@@ -239,7 +239,7 @@ export default class AuthorizationService {
};
// Check static & dynamic authorization
await AuthorizationService.canPerformAuthorizationAction(tenant, userToken, Entity.USER, Action.LIST,
- authorizationFilters, filteredRequest, null, true);
+ authorizationFilters, filteredRequest, null, failsWithException);
return authorizationFilters;
}
@@ -607,6 +607,12 @@ export default class AuthorizationService {
return authorizationFilters;
}
+ public static async checkAndGetInvoiceAuthorizations(tenant: Tenant, userToken: UserToken,
+ filteredRequest: Partial<HttpBillingInvoiceRequest>, authAction: Action, entityData?: EntityData): Promise<AuthorizationFilter> {
+ return AuthorizationService.checkAndGetEntityAuthorizations(
+ tenant, Entity.INVOICE, userToken, filteredRequest, filteredRequest.ID ? { invoiceID: filteredRequest.ID } : {}, authAction, entityData);
+ }
+
public static async addInvoicesAuthorizations(tenant: Tenant, userToken: UserToken, billingInvoices: BillingInvoiceDataResult,
authorizationFilter: AuthorizationFilter): Promise<void> {
// Add Meta Data

MODIFY
src/server/rest/v1/service/BillingService.ts
src/server/rest/v1/service/BillingService.ts
@@ -110,18 +110,13 @@ export default class BillingService {
}
public static async handleForceSynchronizeUser(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
- const filteredRequest = BillingSecurity.filterSynchronizeUserRequest(req.body);
- if (!(await Authorizations.canSynchronizeUserBilling(req.user)).authorized) {
- throw new AppAuthError({
- errorCode: HTTPAuthError.FORBIDDEN,
- user: req.user,
- entity: Entity.USER, action: Action.SYNCHRONIZE_BILLING_USER,
- module: MODULE_NAME, method: 'handleForceSynchronizeUser',
- });
- }
// Check if component is active
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.BILLING,
Action.SYNCHRONIZE_BILLING_USER, Entity.USER, MODULE_NAME, 'handleForceSynchronizeUser');
+ // Filter
+ const filteredRequest = BillingSecurity.filterSynchronizeUserRequest(req.body);
+ // Check dynamic authorization
+ await AuthorizationService.checkAndGetUserAuthorizations(req.tenant, req.user, {}, Action.SYNCHRONIZE_BILLING_USER);
const billingImpl = await BillingFactory.getBillingImpl(req.tenant);
if (!billingImpl) {
throw new AppError({
@@ -190,7 +185,7 @@ export default class BillingService {
const filteredRequest = BillingValidator.getInstance().validateBillingInvoicesGetReq(req.query);
// Check dynamic authorization
const authorizations = await AuthorizationService.checkAndGetInvoicesAuthorizations(req.tenant, req.user, filteredRequest);
- if (!await Authorizations.canListInvoicesBilling(req.user)) {
+ if (!authorizations.authorized) {
throw new AppAuthError({
errorCode: HTTPAuthError.FORBIDDEN,
user: req.user,
@@ -202,11 +197,12 @@ export default class BillingService {
// Get invoices
const invoices = await BillingStorage.getInvoices(req.tenant,
{
- userIDs: !Authorizations.isAdmin(req.user) ? [req.user.id] : (filteredRequest.UserID ? filteredRequest.UserID.split('|') : null),
+ userIDs: filteredRequest.UserID ? filteredRequest.UserID.split('|') : null,
invoiceStatus: filteredRequest.Status ? filteredRequest.Status.split('|') as BillingInvoiceStatus[] : null,
search: filteredRequest.Search ? filteredRequest.Search : null,
startDateTime: filteredRequest.StartDateTime ? filteredRequest.StartDateTime : null,
endDateTime: filteredRequest.EndDateTime ? filteredRequest.EndDateTime : null,
+ ...authorizations.filters
},
{
limit: filteredRequest.Limit,
@@ -233,28 +229,18 @@ export default class BillingService {
Action.LIST, Entity.INVOICE, MODULE_NAME, 'handleGetInvoice');
// Filter
const filteredRequest = BillingValidator.getInstance().validateBillingInvoiceGetReq(req.query);
- UtilsService.assertIdIsProvided(action, filteredRequest.ID, MODULE_NAME, 'handleGetInvoice', req.user);
- // Check Users
- let userProject: string[] = [];
- if ((await Authorizations.canListUsers(req.user)).authorized) {
- userProject = [ 'userID', 'user.id', 'user.name', 'user.firstName', 'user.email' ];
- }
- // Get invoice
- const invoice = await BillingStorage.getInvoice(req.tenant, filteredRequest.ID,
- [
- 'id', 'number', 'status', 'amount', 'createdOn', 'currency', 'downloadable', 'sessions',
- ...userProject
- ]);
- UtilsService.assertObjectExists(action, invoice, `Invoice ID '${filteredRequest.ID}' does not exist`, MODULE_NAME, 'handleGetInvoice', req.user);
- // Check auth
- if (!await Authorizations.canReadInvoiceBilling(req.user, invoice.userID)) {
- throw new AppAuthError({
- errorCode: HTTPAuthError.FORBIDDEN,
- user: req.user,
- entity: Entity.INVOICE, action: Action.READ,
- module: MODULE_NAME, method: 'handleGetInvoice',
- });
- }
+ // Get Users authorizations and retrieve projected fields
+ const authorizationUsersFilters = await AuthorizationService.checkAndGetUsersAuthorizations(req.tenant, req.user, filteredRequest, false);
+ // User projected field if authorized
+ let userProjectedField : string[] = [];
+ if (authorizationUsersFilters.authorized) {
+ userProjectedField = [ 'userID', 'user.id', 'user.name', 'user.firstName', 'user.email' ];
+ }
+ // Build dynamic projected fields based on user authorizations
+ const projectedFields = ['id', 'number', 'status', 'amount', 'createdOn', 'currency', 'downloadable', 'sessions', ...userProjectedField];
+ // Check and get invoice, pass custom projected fields
+ const invoice = await UtilsService.checkAndGetInvoiceAuthorization(req.tenant, req.user, filteredRequest.ID, Action.READ, action, null, {},
+ projectedFields, true);
res.json(invoice);
next();
}

MODIFY
src/server/rest/v1/service/UtilsService.ts
src/server/rest/v1/service/UtilsService.ts
@@ -13,6 +13,8 @@ import AssetStorage from '../../../../storage/mongodb/AssetStorage';
import AuthorizationService from './AuthorizationService';
import Authorizations from '../../../../authorization/Authorizations';
import AxiosFactory from '../../../../utils/AxiosFactory';
+import { BillingInvoice } from '../../../../types/Billing';
+import BillingStorage from '../../../../storage/mongodb/BillingStorage';
import CarStorage from '../../../../storage/mongodb/CarStorage';
import CentralSystemRestServiceConfiguration from '../../../../types/configuration/CentralSystemRestServiceConfiguration';
import { ChargingProfile } from '../../../../types/ChargingProfile';
@@ -845,6 +847,59 @@ export default class UtilsService {
return carCatalog;
}
+ public static async checkAndGetInvoiceAuthorization(tenant: Tenant, userToken: UserToken, invoiceID: string, authAction: Action,
+ action: ServerAction, entityData?: EntityData, additionalFilters: Record<string, any> = {}, additionalProjectedFields: string[] = [],
+ applyProjectFields = false): Promise<BillingInvoice> {
+ // Check mandatory fields
+ UtilsService.assertIdIsProvided(action, invoiceID, MODULE_NAME, 'checkAndGetInvoiceAuthorization', userToken);
+ // Get dynamic auth
+ const authorizationFilter = await AuthorizationService.checkAndGetInvoiceAuthorizations(
+ tenant, userToken, { ID: invoiceID }, authAction, entityData);
+ if (!authorizationFilter.authorized) {
+ throw new AppAuthError({
+ errorCode: HTTPAuthError.FORBIDDEN,
+ user: userToken,
+ action: authAction, entity: Entity.INVOICE,
+ module: MODULE_NAME, method: 'checkAndGetInvoiceAuthorization',
+ value: invoiceID
+ });
+ }
+
+ const projectedFields: string[] = [ ...authorizationFilter.projectFields, ...additionalProjectedFields ];
+
+ // Get Invoice
+ const invoice = await BillingStorage.getInvoice(tenant, invoiceID,
+ {
+ ...additionalFilters,
+ ...authorizationFilter.filters
+ },
+ applyProjectFields ? projectedFields : null
+ );
+ UtilsService.assertObjectExists(action, invoice, `Invoice ID '${invoiceID}' does not exist`,
+ MODULE_NAME, 'checkAndGetInvoiceAuthorization', userToken);
+ // Assign projected fields
+ if (projectedFields) {
+ invoice.projectFields = projectedFields;
+ }
+ // Assign Metadata
+ if (authorizationFilter.metadata) {
+ invoice.metadata = authorizationFilter.metadata;
+ }
+ // Add Actions
+ await AuthorizationService.addInvoiceAuthorizations(tenant, userToken, invoice, authorizationFilter);
+ const authorized = AuthorizationService.canPerformAction(invoice, authAction);
+ if (!authorized) {
+ throw new AppAuthError({
+ errorCode: HTTPAuthError.FORBIDDEN,
+ user: userToken,
+ action: authAction, entity: Entity.INVOICE,
+ module: MODULE_NAME, method: 'checkAndGetInvoiceAuthorization',
+ value: invoiceID
+ });
+ }
+ return invoice;
+ }
+
public static async checkAndGetTagAuthorization(tenant: Tenant, userToken:UserToken, tagID: string, authAction: Action,
action: ServerAction, entityData?: EntityData, additionalFilters: Record<string, any> = {}, applyProjectFields = false): Promise<Tag> {
return UtilsService.checkAndGetTagByXXXAuthorization(tenant, userToken, tagID, TagStorage.getTag.bind(this),

MODIFY
src/storage/mongodb/BillingStorage.ts
src/storage/mongodb/BillingStorage.ts
@@ -13,9 +13,9 @@ import Utils from '../../utils/Utils';
const MODULE_NAME = 'BillingStorage';
export default class BillingStorage {
- public static async getInvoice(tenant: Tenant, id: string = Constants.UNKNOWN_OBJECT_ID, projectFields?: string[]): Promise<BillingInvoice> {
+ public static async getInvoice(tenant: Tenant, id: string = Constants.UNKNOWN_OBJECT_ID, params: { userIDs?: string[] } = {}, projectFields?: string[]): Promise<BillingInvoice> {
const invoicesMDB = await BillingStorage.getInvoices(tenant, {
- invoiceIDs: [id]
+ invoiceIDs: [id], userIDs: params.userIDs
}, Constants.DB_PARAMS_SINGLE_RECORD, projectFields);
return invoicesMDB.count === 1 ? invoicesMDB.result[0] : null;
}

Invoice migration
- Use auth definition and remove admin
- Add params in invoice storage get
- Add authorizations functions check and refactoring