diff --git a/src/client/ocpi/CpoOCPIClient.ts b/src/client/ocpi/CpoOCPIClient.ts index 2d9a020613..23c77dcd6b 100644 --- a/src/client/ocpi/CpoOCPIClient.ts +++ b/src/client/ocpi/CpoOCPIClient.ts @@ -6,7 +6,6 @@ import { OCPIAllowed, OCPIAuthorizationInfo } from '../../types/ocpi/OCPIAuthori import { OCPIAuthMethod, OCPISession, OCPISessionStatus } from '../../types/ocpi/OCPISession'; import { OCPICapability, OCPIEvse, OCPIEvseStatus } from '../../types/ocpi/OCPIEvse'; import { OCPILocation, OCPILocationOptions, OCPILocationReference, OCPILocationType } from '../../types/ocpi/OCPILocation'; -import User, { UserRole, UserStatus } from '../../types/User'; import { AxiosResponse } from 'axios'; import BackendError from '../../exception/BackendError'; @@ -38,6 +37,7 @@ import TagStorage from '../../storage/mongodb/TagStorage'; import Tenant from '../../types/Tenant'; import Transaction from '../../types/Transaction'; import TransactionStorage from '../../storage/mongodb/TransactionStorage'; +import User from '../../types/User'; import UserStorage from '../../storage/mongodb/UserStorage'; import Utils from '../../utils/Utils'; import _ from 'lodash'; @@ -370,7 +370,7 @@ export default class CpoOCPIClient extends OCPIClient { module: MODULE_NAME, method: 'postCdr', }); } - if (!transaction.ocpiData?.session) { + if (!transaction.ocpiData?.session?.id) { throw new BackendError({ ...LoggingHelper.getTransactionProperties(transaction), action: ServerAction.OCPI_CPO_PUSH_CDRS, diff --git a/src/server/ocpi/OCPIFacade.ts b/src/server/ocpi/OCPIFacade.ts index 54fa86c284..fd2fc26376 100644 --- a/src/server/ocpi/OCPIFacade.ts +++ b/src/server/ocpi/OCPIFacade.ts @@ -77,10 +77,10 @@ export default class OCPIFacade { } public static async processEndTransaction(tenant: Tenant, transaction: Transaction, chargingStation: ChargingStation, - siteArea: SiteArea, user: User, action: ServerAction): Promise { + siteArea: SiteArea, user: User, action: ServerAction): Promise { if (!Utils.isTenantComponentActive(tenant, TenantComponents.OCPI) || !chargingStation.issuer || !chargingStation.public || !siteArea.accessControl || user.issuer) { - return; + return false; } // Get OCPI CPO client const ocpiClient = await OCPIFacade.checkAndGetOcpiCpoClient( @@ -100,8 +100,8 @@ export default class OCPIFacade { if (ocpiLock) { try { // Roaming - ocpiCdrSent = true; - await OCPIFacade.processEndTransaction(tenant, transaction, chargingStation, siteArea, transaction.user, action); + ocpiCdrSent = await OCPIFacade.processEndTransaction( + tenant, transaction, chargingStation, siteArea, transaction.user, action); } finally { // Release the lock await LockingManager.release(ocpiLock); diff --git a/src/server/oicp/OICPFacade.ts b/src/server/oicp/OICPFacade.ts index ea080db41d..3204ac2d68 100644 --- a/src/server/oicp/OICPFacade.ts +++ b/src/server/oicp/OICPFacade.ts @@ -83,10 +83,10 @@ export default class OICPFacade { } public static async processEndTransaction(tenant: Tenant, transaction: Transaction, chargingStation: ChargingStation, - siteArea: SiteArea, user: User, action: ServerAction): Promise { + siteArea: SiteArea, user: User, action: ServerAction): Promise { if (!Utils.isTenantComponentActive(tenant, TenantComponents.OICP) || !chargingStation.issuer || !chargingStation.public || !siteArea.accessControl) { - return; + return false; } // Get OICP CPO client const oicpClient = await OICPFacade.checkAndGetOICPCpoClient( @@ -97,7 +97,7 @@ export default class OICPFacade { public static async checkAndSendTransactionCdr(tenant: Tenant, transaction: Transaction, chargingStation: ChargingStation, siteArea: SiteArea, action: ServerAction): Promise { - let OICPCdrSent = false; + let oicpCdrSent = false; // CDR not already pushed if (Utils.isTenantComponentActive(tenant, TenantComponents.OICP) && transaction.oicpData?.session && !transaction.oicpData.cdr?.SessionID) { @@ -106,15 +106,15 @@ export default class OICPFacade { if (OICPLock) { try { // Roaming - OICPCdrSent = true; - await OICPFacade.processEndTransaction(tenant, transaction, chargingStation, siteArea, transaction.user, action); + oicpCdrSent = await OICPFacade.processEndTransaction( + tenant, transaction, chargingStation, siteArea, transaction.user, action); } finally { // Release the lock await LockingManager.release(OICPLock); } } } - return OICPCdrSent; + return oicpCdrSent; } public static async updateConnectorStatus(tenant: Tenant, chargingStation: ChargingStation, connector: Connector): Promise { diff --git a/src/server/rest/v1/service/TransactionService.ts b/src/server/rest/v1/service/TransactionService.ts index a2a5cc8b47..35ad2416ef 100644 --- a/src/server/rest/v1/service/TransactionService.ts +++ b/src/server/rest/v1/service/TransactionService.ts @@ -114,6 +114,7 @@ export default class TransactionService { } if (transaction.refundData && !!transaction.refundData.refundId && transaction.refundData.status !== RefundStatus.CANCELLED) { await Logging.logError({ + ...LoggingHelper.getTransactionProperties(transaction), tenantID: req.tenant.id, user: req.user, actionOnUser: (transaction.user ? transaction.user : null), module: MODULE_NAME, method: 'handleRefundTransactions', @@ -126,6 +127,7 @@ export default class TransactionService { // Check auth if (!await Authorizations.canRefundTransaction(req.user, transaction)) { throw new AppAuthError({ + ...LoggingHelper.getTransactionProperties(transaction), errorCode: HTTPAuthError.FORBIDDEN, user: req.user, action: Action.REFUND_TRANSACTION, entity: Entity.TRANSACTION, @@ -175,6 +177,17 @@ export default class TransactionService { } public static async handlePushTransactionCdr(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise { + // Check if component is active + if (!Utils.isComponentActiveFromToken(req.user, TenantComponents.OCPI) && + !Utils.isComponentActiveFromToken(req.user, TenantComponents.OICP)) { + throw new AppAuthError({ + errorCode: HTTPAuthError.FORBIDDEN, + entity: Entity.TRANSACTION, action: Action.UPDATE, + module: MODULE_NAME, method: 'handlePushTransactionCdr', + inactiveComponent: `${TenantComponents.OCPI}, ${TenantComponents.OICP}` as TenantComponents, + user: req.user + }); + } // Filter const filteredRequest = TransactionValidator.getInstance().validateTransactionCdrPushReq(req.body); // Check Mandatory fields @@ -186,6 +199,7 @@ export default class TransactionService { // Check auth if (!await Authorizations.canUpdateTransaction(req.user, transaction)) { throw new AppAuthError({ + ...LoggingHelper.getTransactionProperties(transaction), errorCode: HTTPAuthError.FORBIDDEN, user: req.user, action: Action.UPDATE, entity: Entity.TRANSACTION, @@ -197,9 +211,38 @@ export default class TransactionService { const chargingStation = await ChargingStationStorage.getChargingStation(req.tenant, transaction.chargeBoxID, { withSiteArea: true }); UtilsService.assertObjectExists(action, chargingStation, `Charging Station ID '${transaction.chargeBoxID}' does not exist`, MODULE_NAME, 'handlePushTransactionCdr', req.user); - // Check Issuer + // Check Charging Station + if (!chargingStation.issuer) { + throw new AppError({ + ...LoggingHelper.getTransactionProperties(transaction), + errorCode: HTTPError.GENERAL_ERROR, + message: `${Utils.buildConnectorInfo(transaction.connectorId, transaction.id)} Charging Station belongs to an external organization`, + module: MODULE_NAME, method: 'handlePushTransactionCdr', + user: req.user, action + }); + } + if (!chargingStation.public) { + throw new AppError({ + ...LoggingHelper.getTransactionProperties(transaction), + errorCode: HTTPError.GENERAL_ERROR, + message: `${Utils.buildConnectorInfo(transaction.connectorId, transaction.id)} Charging Station is not public`, + module: MODULE_NAME, method: 'handlePushTransactionCdr', + user: req.user, action + }); + } + if (chargingStation.siteArea && !chargingStation.siteArea.accessControl) { + throw new AppError({ + ...LoggingHelper.getTransactionProperties(transaction), + errorCode: HTTPError.GENERAL_ERROR, + message: `${Utils.buildConnectorInfo(transaction.connectorId, transaction.id)} Charging Station access control is inactive on Site Area '${chargingStation.siteArea.name}'`, + module: MODULE_NAME, method: 'handlePushTransactionCdr', + user: req.user, action + }); + } + // Check Transaction if (!transaction.issuer) { throw new AppError({ + ...LoggingHelper.getTransactionProperties(transaction), errorCode: HTTPError.TRANSACTION_NOT_FROM_TENANT, message: `${Utils.buildConnectorInfo(transaction.connectorId, transaction.id)} Transaction belongs to an external organization`, module: MODULE_NAME, method: 'handlePushTransactionCdr', @@ -209,6 +252,7 @@ export default class TransactionService { // No Roaming Cdr to push if (!transaction.oicpData?.session && !transaction.ocpiData?.session) { throw new AppError({ + ...LoggingHelper.getTransactionProperties(transaction), errorCode: HTTPError.TRANSACTION_WITH_NO_OCPI_DATA, message: `${Utils.buildConnectorInfo(transaction.connectorId, transaction.id)} No OCPI or OICP Session data`, module: MODULE_NAME, method: 'handlePushTransactionCdr', @@ -220,6 +264,7 @@ export default class TransactionService { // CDR already pushed if (transaction.ocpiData.cdr?.id) { throw new AppError({ + ...LoggingHelper.getTransactionProperties(transaction), errorCode: HTTPError.TRANSACTION_CDR_ALREADY_PUSHED, message: `The CDR of the Transaction ID '${transaction.id}' has already been pushed`, module: MODULE_NAME, method: 'handlePushTransactionCdr', @@ -227,25 +272,34 @@ export default class TransactionService { }); } // OCPI: Post the CDR - const ocpiUpdated = await OCPIFacade.checkAndSendTransactionCdr( + const ocpiCdrSent = await OCPIFacade.checkAndSendTransactionCdr( req.tenant, transaction, chargingStation, chargingStation.siteArea, action); - if (ocpiUpdated) { - // Save - await TransactionStorage.saveTransactionOcpiData(req.tenant, transaction.id, transaction.ocpiData); - await Logging.logInfo({ - tenantID: req.tenant.id, - action, module: MODULE_NAME, method: 'handlePushTransactionCdr', - user: req.user, actionOnUser: (transaction.user ? transaction.user : null), - message: `CDR of Transaction ID '${transaction.id}' has been pushed successfully`, - detailedMessages: { cdr: transaction.ocpiData.cdr } + if (!ocpiCdrSent) { + throw new AppError({ + ...LoggingHelper.getTransactionProperties(transaction), + errorCode: HTTPError.GENERAL_ERROR, + message: `The CDR of the Transaction ID '${transaction.id}' has not been sent`, + module: MODULE_NAME, method: 'handlePushTransactionCdr', + user: req.user, action }); } + // Save + await TransactionStorage.saveTransactionOcpiData(req.tenant, transaction.id, transaction.ocpiData); + await Logging.logInfo({ + ...LoggingHelper.getTransactionProperties(transaction), + tenantID: req.tenant.id, + action, module: MODULE_NAME, method: 'handlePushTransactionCdr', + user: req.user, actionOnUser: (transaction.user ? transaction.user : null), + message: `CDR of Transaction ID '${transaction.id}' has been pushed successfully`, + detailedMessages: { cdr: transaction.ocpiData.cdr } + }); } // Check OICP if (transaction.oicpData?.session) { // CDR already pushed if (transaction.oicpData.cdr?.SessionID) { throw new AppError({ + ...LoggingHelper.getTransactionProperties(transaction), errorCode: HTTPError.TRANSACTION_CDR_ALREADY_PUSHED, message: `The CDR of the transaction ID '${transaction.id}' has already been pushed`, module: MODULE_NAME, method: 'handlePushTransactionCdr', @@ -253,20 +307,27 @@ export default class TransactionService { }); } // OICP: Post the CDR - const oicpUpdated = await OICPFacade.checkAndSendTransactionCdr( + const oicpCdrSent = await OICPFacade.checkAndSendTransactionCdr( req.tenant, transaction, chargingStation, chargingStation.siteArea, action); - if (oicpUpdated) { - // Save - await TransactionStorage.saveTransactionOicpData(req.tenant, transaction.id, transaction.oicpData); - await Logging.logInfo({ - tenantID: req.tenant.id, - action, - user: req.user, actionOnUser: (transaction.user ? transaction.user : null), + if (!oicpCdrSent) { + throw new AppError({ + ...LoggingHelper.getTransactionProperties(transaction), + errorCode: HTTPError.GENERAL_ERROR, + message: `The CDR of the Transaction ID '${transaction.id}' has not been sent`, module: MODULE_NAME, method: 'handlePushTransactionCdr', - message: `CDR of Transaction ID '${transaction.id}' has been pushed successfully`, - detailedMessages: { cdr: transaction.ocpiData.cdr } + user: req.user, action }); } + // Save + await TransactionStorage.saveTransactionOicpData(req.tenant, transaction.id, transaction.oicpData); + await Logging.logInfo({ + ...LoggingHelper.getTransactionProperties(transaction), + tenantID: req.tenant.id, + user: req.user, actionOnUser: (transaction.user ?? null), + action, module: MODULE_NAME, method: 'handlePushTransactionCdr', + message: `CDR of Transaction ID '${transaction.id}' has been pushed successfully`, + detailedMessages: { cdr: transaction.ocpiData.cdr } + }); } res.json(Constants.REST_RESPONSE_SUCCESS); next(); @@ -418,6 +479,7 @@ export default class TransactionService { // Check Transaction if (!await Authorizations.canReadTransaction(req.user, transaction)) { throw new AppAuthError({ + ...LoggingHelper.getTransactionProperties(transaction), errorCode: HTTPAuthError.FORBIDDEN, user: req.user, action: Action.READ, entity: Entity.TRANSACTION, @@ -441,6 +503,7 @@ export default class TransactionService { if (filteredRequest.StartDateTime && filteredRequest.EndDateTime && moment(filteredRequest.StartDateTime).isAfter(moment(filteredRequest.EndDateTime))) { throw new AppError({ + ...LoggingHelper.getTransactionProperties(transaction), errorCode: HTTPError.GENERAL_ERROR, message: `The requested start date '${new Date(filteredRequest.StartDateTime).toISOString()}' is after the requested end date '${new Date(filteredRequest.StartDateTime).toISOString()}' `, module: MODULE_NAME, method: 'handleGetConsumptionFromTransaction', @@ -503,6 +566,7 @@ export default class TransactionService { // Check Transaction if (!await Authorizations.canReadTransaction(req.user, transaction)) { throw new AppAuthError({ + ...LoggingHelper.getTransactionProperties(transaction), errorCode: HTTPAuthError.FORBIDDEN, user: req.user, action: Action.READ, entity: Entity.TRANSACTION, @@ -711,6 +775,7 @@ export default class TransactionService { MODULE_NAME, 'handleExportTransactionOcpiCdr', req.user); if (!transaction?.ocpiData) { throw new AppError({ + ...LoggingHelper.getTransactionProperties(transaction), errorCode: HTTPError.GENERAL_ERROR, message: `Transaction ID '${transaction.id}' does not contain roaming data`, module: MODULE_NAME, method: 'handleExportTransactionOcpiCdr', @@ -868,28 +933,36 @@ export default class TransactionService { message: `Transaction ID '${transactionID}' does not exist`, detailedMessages: { transaction } }); - // Already Refunded - } else if (refundConnector && !refundConnector.canBeDeleted(transaction)) { + continue; + } + // Already Refunded + if (refundConnector && !refundConnector.canBeDeleted(transaction)) { result.inError++; await Logging.logError({ + ...LoggingHelper.getTransactionProperties(transaction), tenantID: loggedUser.tenantID, user: loggedUser, action, module: MODULE_NAME, method: 'handleDeleteTransactions', message: `Transaction ID '${transactionID}' has been refunded and cannot be deleted`, detailedMessages: { transaction } }); - // Billed - } else if (billingImpl && transaction.billingData?.stop?.status === BillingStatus.BILLED) { + continue; + } + // Billed + if (billingImpl && transaction.billingData?.stop?.status === BillingStatus.BILLED) { result.inError++; await Logging.logError({ + ...LoggingHelper.getTransactionProperties(transaction), tenantID: loggedUser.tenantID, user: loggedUser, action, module: MODULE_NAME, method: 'handleDeleteTransactions', message: `Transaction ID '${transactionID}' has been billed and cannot be deleted`, detailedMessages: { transaction } }); - // Transaction in progress - } else if (!transaction.stop) { + continue; + } + // Transaction in progress + if (!transaction.stop) { if (!transaction.chargeBox) { transactionsIDsToDelete.push(transactionID); } else { @@ -903,13 +976,12 @@ export default class TransactionService { // To Delete transactionsIDsToDelete.push(transactionID); } - } else { - transactionsIDsToDelete.push(transactionID); + continue; } + transactionsIDsToDelete.push(transactionID); } // Delete All Transactions result.inSuccess = await TransactionStorage.deleteTransactions(tenant, transactionsIDsToDelete); - // Log await Logging.logActionsResponse(loggedUser.tenantID, ServerAction.TRANSACTIONS_DELETE, MODULE_NAME, 'deleteTransactions', result, @@ -1043,6 +1115,7 @@ export default class TransactionService { await ChargingStationStorage.saveChargingStationConnectors(req.tenant, chargingStation.id, chargingStation.connectors); } await Logging.logInfo({ + ...LoggingHelper.getTransactionProperties(transaction), tenantID: req.tenant.id, user: req.user, actionOnUser: transaction.userID, action, module: MODULE_NAME, method: 'transactionSoftStop', @@ -1052,7 +1125,7 @@ export default class TransactionService { // Transaction is still ongoing if (!chargingStation.inactive && connector.currentTransactionID === transaction.id) { throw new AppError({ - ...LoggingHelper.getChargingStationProperties(chargingStation), + ...LoggingHelper.getTransactionProperties(transaction), errorCode: HTTPError.GENERAL_ERROR, message: `${Utils.buildConnectorInfo(transaction.connectorId, transaction.id)} Cannot soft stop an ongoing Transaction`, module: MODULE_NAME, method: 'transactionSoftStop', @@ -1064,7 +1137,7 @@ export default class TransactionService { req.tenant, transaction, chargingStation, chargingStation.siteArea); if (!success) { throw new AppError({ - ...LoggingHelper.getChargingStationProperties(chargingStation), + ...LoggingHelper.getTransactionProperties(transaction), errorCode: HTTPError.GENERAL_ERROR, message: `${Utils.buildConnectorInfo(transaction.connectorId, transaction.id)} Transaction cannot be stopped`, module: MODULE_NAME, method: 'transactionSoftStop', @@ -1072,7 +1145,7 @@ export default class TransactionService { }); } await Logging.logInfo({ - ...LoggingHelper.getChargingStationProperties(chargingStation), + ...LoggingHelper.getTransactionProperties(transaction), tenantID: req.tenant.id, user: req.user, actionOnUser: transaction.userID, module: MODULE_NAME, method: 'transactionSoftStop', @@ -1093,6 +1166,7 @@ export default class TransactionService { // Check auth if (!await Authorizations.canUpdateTransaction(user, transaction)) { throw new AppAuthError({ + ...LoggingHelper.getTransactionProperties(transaction), errorCode: HTTPAuthError.FORBIDDEN, user, action: Action.UPDATE, entity: Entity.TRANSACTION, module: MODULE_NAME, method: 'checkAndGetTransactionChargingStationConnector', diff --git a/src/server/rest/v1/service/UtilsService.ts b/src/server/rest/v1/service/UtilsService.ts index fad73a95fd..91680bde4d 100644 --- a/src/server/rest/v1/service/UtilsService.ts +++ b/src/server/rest/v1/service/UtilsService.ts @@ -906,7 +906,6 @@ export default class UtilsService { action: Action, entity: Entity, module: string, method: string): void { // Check from token const active = Utils.isComponentActiveFromToken(userToken, component); - // Throw if (!active) { throw new AppAuthError({ errorCode: HTTPAuthError.FORBIDDEN,