Frontend Endpoints added for OICP
MODIFY
.gitignore
.gitignore
@@ -81,3 +81,5 @@ build/config.gypi
.DS_Store
Thumbs.db
+src/assets/server/oicp/ssl/cert.pem
+src/assets/server/oicp/ssl/key.pem

MODIFY
src/authorization/Authorizations.ts
src/authorization/Authorizations.ts
@@ -444,10 +444,26 @@ export default class Authorizations {
return Authorizations.canPerformAction(loggedUser, Entity.OCPI_ENDPOINT, Action.GENERATE_LOCAL_TOKEN);
}
+ public static canListOicpEndpoints(loggedUser: UserToken): boolean {
+ return Authorizations.canPerformAction(loggedUser, Entity.OICP_ENDPOINTS, Action.LIST);
+ }
+
+ public static canReadOicpEndpoint(loggedUser: UserToken): boolean {
+ return Authorizations.canPerformAction(loggedUser, Entity.OICP_ENDPOINT, Action.READ);
+ }
+
+ public static canDeleteOicpEndpoint(loggedUser: UserToken): boolean {
+ return Authorizations.canPerformAction(loggedUser, Entity.OICP_ENDPOINT, Action.DELETE);
+ }
+
public static canCreateOicpEndpoint(loggedUser: UserToken): boolean {
return Authorizations.canPerformAction(loggedUser, Entity.OICP_ENDPOINT, Action.CREATE);
}
+ public static canUpdateOicpEndpoint(loggedUser: UserToken): boolean {
+ return Authorizations.canPerformAction(loggedUser, Entity.OICP_ENDPOINT, Action.UPDATE);
+ }
+
public static canPingOicpEndpoint(loggedUser: UserToken): boolean {
return Authorizations.canPerformAction(loggedUser, Entity.OICP_ENDPOINT, Action.PING);
}
@@ -456,6 +472,10 @@ export default class Authorizations {
return Authorizations.canPerformAction(loggedUser, Entity.OICP_ENDPOINT, Action.TRIGGER_JOB);
}
+ public static canRegisterOicpEndpoint(loggedUser: UserToken): boolean {
+ return Authorizations.canPerformAction(loggedUser, Entity.OICP_ENDPOINT, Action.REGISTER);
+ }
+
public static canListChargingProfiles(loggedUser: UserToken): boolean {
return Authorizations.canPerformAction(loggedUser, Entity.CHARGING_PROFILES, Action.LIST);
}

MODIFY
src/authorization/AuthorizationsDefinition.ts
src/authorization/AuthorizationsDefinition.ts
@@ -85,7 +85,8 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
{ resource: Entity.OICP_ENDPOINTS, action: Action.LIST, attributes: ['*'] },
{
resource: Entity.OICP_ENDPOINT,
- action: [Action.CREATE, Action.READ, Action.UPDATE, Action.DELETE, Action.PING, Action.TRIGGER_JOB],
+ action: [Action.CREATE, Action.READ, Action.UPDATE, Action.DELETE, Action.PING,
+ Action.REGISTER, Action.TRIGGER_JOB],
attributes: ['*']
},
{ resource: Entity.CONNECTIONS, action: Action.LIST, attributes: ['*'] },

MODIFY
src/client/oicp/OICPClient.ts
src/client/oicp/OICPClient.ts
import { AxiosInstance, AxiosRequestConfig } from 'axios';
+import { ReasonPhrases, StatusCodes } from 'http-status-codes';
import AxiosFactory from '../../utils/AxiosFactory';
import BackendError from '../../exception/BackendError';
+import Configuration from '../../utils/Configuration';
+import { HTTPError } from '../../types/HTTPError';
import Logging from '../../utils/Logging';
import OICPEndpoint from '../../types/oicp/OICPEndpoint';
+import OICPEndpointStorage from '../../storage/mongodb/OICPEndpointStorage';
import { OICPOperatorID } from '../../types/oicp/OICPEvse';
+import { OICPRegistrationStatus } from '../../types/oicp/OICPRegistrationStatus';
import { OICPResult } from '../../types/oicp/OICPResult';
import { OICPRole } from '../../types/oicp/OICPRole';
+import OICPServiceConfiguration from '../../types/configuration/OICPServiceConfiguration';
import { OicpSetting } from '../../types/Setting';
import { ServerAction } from '../../types/Server';
import Tenant from '../../types/Tenant';
+import fs from 'fs';
import https from 'https';
const MODULE_NAME = 'OICPClient';
@@ -20,6 +27,7 @@ export default abstract class OICPClient {
protected tenant: Tenant;
protected role: string;
protected settings: OicpSetting;
+ private oicpConfig: OICPServiceConfiguration;
protected constructor(tenant: Tenant, settings: OicpSetting, oicpEndpoint: OICPEndpoint, role: string) {
if (role !== OICPRole.CPO && role !== OICPRole.EMSP) {
@@ -32,6 +40,7 @@ export default abstract class OICPClient {
this.settings = settings;
this.oicpEndpoint = oicpEndpoint;
this.role = role.toLowerCase();
+ this.oicpConfig = Configuration.getOICPServiceConfig();
this.axiosInstance = AxiosFactory.getAxiosInstance(tenant.id, { axiosConfig: this.getAxiosConfig(ServerAction.OICP_CREATE_AXIOS_INSTANCE) });
}
@@ -74,6 +83,40 @@ export default abstract class OICPClient {
return operatorID;
}
+ async unregister(): Promise<any> {
+ const unregisterResult: any = {};
+ try {
+ // Save endpoint
+ this.oicpEndpoint.status = OICPRegistrationStatus.UNREGISTERED;
+ await OICPEndpointStorage.saveOicpEndpoint(this.tenant.id, this.oicpEndpoint);
+ // Send success
+ unregisterResult.statusCode = StatusCodes.OK;
+ unregisterResult.statusText = ReasonPhrases.OK;
+ } catch (error) {
+ unregisterResult.message = error.message;
+ unregisterResult.statusCode = (error.response) ? error.response.status : HTTPError.GENERAL_ERROR;
+ }
+ // Return result
+ return unregisterResult;
+ }
+
+ async register(): Promise<any> {
+ const registerResult: any = {};
+ try {
+ // Save endpoint
+ this.oicpEndpoint.status = OICPRegistrationStatus.REGISTERED;
+ await OICPEndpointStorage.saveOicpEndpoint(this.tenant.id, this.oicpEndpoint);
+ // Send success
+ registerResult.statusCode = StatusCodes.OK;
+ registerResult.statusText = ReasonPhrases.OK;
+ } catch (error) {
+ registerResult.message = error.message;
+ registerResult.statusCode = (error.response) ? error.response.status : HTTPError.GENERAL_ERROR;
+ }
+ // Return result
+ return registerResult;
+ }
+
protected getEndpointUrl(service: string, action: ServerAction): string {
if (this.oicpEndpoint.availableEndpoints) {
const baseURL = this.oicpEndpoint.baseUrl;
@@ -87,45 +130,47 @@ export default abstract class OICPClient {
});
}
- private getAxiosConfig(action: ServerAction): AxiosRequestConfig {
- const axiosConfig: AxiosRequestConfig = {} as AxiosRequestConfig;
- axiosConfig.httpsAgent = this.getHttpsAgent(action);
- axiosConfig.headers = {
- 'Content-Type': 'application/json'
- };
- return axiosConfig;
- }
-
- private getPrivateKey(action: ServerAction): string {
+ private getPrivateKey(action: ServerAction): Buffer {
if (!this.settings[this.role]) {
throw new BackendError({
action, message: `OICP Settings are missing for role ${this.role}`,
module: MODULE_NAME, method: 'getPrivateKey',
});
}
- if (!this.settings[this.role].privateKey) {
+ const key = fs.readFileSync(this.oicpConfig['ssl-key']);
+ if (!key) {
throw new BackendError({
action, message: `OICP private Key setting is missing for role ${this.role}`,
module: MODULE_NAME, method: 'getPrivateKey',
});
}
- return this.settings[this.role].privateKey;
+ return key;
}
- private getClientCertificate(action: ServerAction): string {
+ private getClientCertificate(action: ServerAction): Buffer {
if (!this.settings[this.role]) {
throw new BackendError({
action, message: `OICP Settings are missing for role ${this.role}`,
module: MODULE_NAME, method: 'getClientCertificate',
});
}
- if (!this.settings[this.role].clientCertificate) {
+ const cert = fs.readFileSync(this.oicpConfig['ssl-cert']);
+ if (!cert) {
throw new BackendError({
action, message: `OICP client certificate setting is missing for role ${this.role}`,
module: MODULE_NAME, method: 'getClientCertificate',
});
}
- return this.settings[this.role].clientCertificate;
+ return cert;
+ }
+
+ private getAxiosConfig(action: ServerAction): AxiosRequestConfig {
+ const axiosConfig: AxiosRequestConfig = {} as AxiosRequestConfig;
+ axiosConfig.httpsAgent = this.getHttpsAgent(action);
+ axiosConfig.headers = {
+ 'Content-Type': 'application/json'
+ };
+ return axiosConfig;
}
private getHttpsAgent(action: ServerAction): https.Agent {
@@ -133,7 +178,7 @@ export default abstract class OICPClient {
const privateKey = this.getPrivateKey(action);
const httpsAgent = new https.Agent({
- rejectUnauthorized: false, // (NOTE: this will disable client verification)
+ rejectUnauthorized: false,
cert: publicCert,
key: privateKey,
passphrase: ''

MODIFY
src/server/rest/CentralRestServerService.ts
src/server/rest/CentralRestServerService.ts
@@ -197,6 +197,8 @@ class RequestMapper {
[ServerAction.BILLING_DOWNLOAD_INVOICE]: BillingService.handleDownloadInvoice.bind(this),
[ServerAction.OCPI_ENDPOINTS]: OCPIEndpointService.handleGetOcpiEndpoints.bind(this),
[ServerAction.OCPI_ENDPOINT]: OCPIEndpointService.handleGetOcpiEndpoint.bind(this),
+ [ServerAction.OICP_ENDPOINTS]: OICPEndpointService.handleGetOicpEndpoints.bind(this),
+ [ServerAction.OICP_ENDPOINT]: OICPEndpointService.handleGetOicpEndpoint.bind(this),
[ServerAction.INTEGRATION_CONNECTIONS]: ConnectionService.handleGetConnections.bind(this),
[ServerAction.INTEGRATION_CONNECTION]: ConnectionService.handleGetConnection.bind(this),
[ServerAction.PING]: (action: ServerAction, req: Request, res: Response, next: NextFunction) => {
@@ -227,6 +229,9 @@ class RequestMapper {
[ServerAction.OCPI_ENDPOINT_UPDATE]: OCPIEndpointService.handleUpdateOcpiEndpoint.bind(this),
[ServerAction.OCPI_ENDPOINT_REGISTER]: OCPIEndpointService.handleRegisterOcpiEndpoint.bind(this),
[ServerAction.OCPI_ENDPOINT_UNREGISTER]: OCPIEndpointService.handleUnregisterOcpiEndpoint.bind(this),
+ [ServerAction.OICP_ENDPOINT_UPDATE]: OICPEndpointService.handleUpdateOicpEndpoint.bind(this),
+ [ServerAction.OICP_ENDPOINT_REGISTER]: OICPEndpointService.handleRegisterOicpEndpoint.bind(this),
+ [ServerAction.OICP_ENDPOINT_UNREGISTER]: OICPEndpointService.handleUnregisterOicpEndpoint.bind(this),
[ServerAction.SYNCHRONIZE_CAR_CATALOGS]: CarService.handleSynchronizeCarCatalogs.bind(this),
[ServerAction.CAR_UPDATE]: CarService.handleUpdateCar.bind(this),
[ServerAction.TAG_UPDATE]: TagService.handleUpdateTag.bind(this),
@@ -252,6 +257,7 @@ class RequestMapper {
[ServerAction.INTEGRATION_CONNECTION_DELETE]: ConnectionService.handleDeleteConnection.bind(this),
[ServerAction.SETTING_DELETE]: SettingService.handleDeleteSetting.bind(this),
[ServerAction.OCPI_ENDPOINT_DELETE]: OCPIEndpointService.handleDeleteOcpiEndpoint.bind(this),
+ [ServerAction.OICP_ENDPOINT_DELETE]: OICPEndpointService.handleDeleteOicpEndpoint.bind(this),
[ServerAction.CAR_DELETE]: CarService.handleDeleteCar.bind(this),
[ServerAction.TAG_DELETE]: TagService.handleDeleteTag.bind(this),
});

MODIFY
src/server/rest/v1/service/security/SettingSecurity.ts
src/server/rest/v1/service/security/SettingSecurity.ts
-import { AnalyticsSettingsType, AssetConnectionSetting, AssetConnectionType, AssetSettingsType, BillingSettingsType, ConcurRefundSetting, OcpiBusinessDetails, OcpiSetting, PricingSettingsType, RefundSettingsType, RoamingSettingsType, SettingDB, SettingDBContent, SettingLink, SimplePricingSetting, SmartChargingSettingsType } from '../../../../../types/Setting';
+import { AnalyticsSettingsType, AssetConnectionSetting, AssetConnectionType, AssetSettingsType, BillingSettingsType, ConcurRefundSetting, OcpiBusinessDetails, OcpiSetting, OicpBusinessDetails, OicpSetting, PricingSettingsType, RefundSettingsType, RoamingSettingsType, SettingDB, SettingDBContent, SettingLink, SimplePricingSetting, SmartChargingSettingsType } from '../../../../../types/Setting';
import { HttpSettingRequest, HttpSettingsRequest } from '../../../../../types/requests/HttpSettingRequest';
import Utils from '../../../../../utils/Utils';
@@ -99,6 +99,40 @@ export default class SettingSecurity {
settings.content.ocpi.currency = request.content.ocpi.currency;
}
break;
+ case RoamingSettingsType.HUBJECT:
+ settings.content.oicp = {} as OicpSetting;
+ if (request.content.oicp.businessDetails) {
+ settings.content.oicp.businessDetails = {
+ name: sanitize(request.content.oicp.businessDetails.name),
+ website: sanitize(request.content.oicp.businessDetails.website)
+ } as OicpBusinessDetails;
+ if (request.content.oicp.businessDetails.logo) {
+ settings.content.oicp.businessDetails.logo = {
+ url: sanitize(request.content.oicp.businessDetails.logo.url),
+ thumbnail: sanitize(request.content.oicp.businessDetails.logo.thumbnail),
+ category: sanitize(request.content.oicp.businessDetails.logo.category),
+ type: sanitize(request.content.oicp.businessDetails.logo.type),
+ width: sanitize(request.content.oicp.businessDetails.logo.width),
+ height: sanitize(request.content.oicp.businessDetails.logo.height),
+ };
+ }
+ }
+ if (request.content.oicp.cpo) {
+ settings.content.oicp.cpo = {
+ countryCode: sanitize(request.content.oicp.cpo.countryCode),
+ partyID: sanitize(request.content.oicp.cpo.partyID)
+ };
+ }
+ if (request.content.oicp.emsp) {
+ settings.content.oicp.emsp = {
+ countryCode: sanitize(request.content.oicp.emsp.countryCode),
+ partyID: sanitize(request.content.oicp.emsp.partyID)
+ };
+ }
+ if (request.content.oicp.currency) {
+ settings.content.oicp.currency = request.content.oicp.currency;
+ }
+ break;
case RefundSettingsType.CONCUR:
if (!Utils.isEmptyJSon(request.content.concur)) {
settings.content.concur = {

MODIFY
src/server/restOICP/v2.3.0/service/OICPEndpointService.ts
src/server/restOICP/v2.3.0/service/OICPEndpointService.ts
@@ -13,6 +13,8 @@ import OICPEndpointSecurity from './security/OICPEndpointSecurity';
import OICPEndpointStorage from '../../../../storage/mongodb/OICPEndpointStorage';
import { OICPRegistrationStatus } from '../../../../types/oicp/OICPRegistrationStatus';
import { ServerAction } from '../../../../types/Server';
+import { StatusCodes } from 'http-status-codes';
+import TenantComponents from '../../../../types/TenantComponents';
import TenantStorage from '../../../../storage/mongodb/TenantStorage';
import Utils from '../../../../utils/Utils';
import UtilsService from '../../../rest/v1/service/UtilsService';
@@ -20,6 +22,42 @@ import UtilsService from '../../../rest/v1/service/UtilsService';
const MODULE_NAME = 'OICPEndpointService';
export default class OICPEndpointService {
+ static async handleDeleteOicpEndpoint(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
+ // Check if component is active
+ UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.OICP,
+ Action.DELETE, Entity.OICP_ENDPOINT, MODULE_NAME, 'handleDeleteOicpEndpoint');
+ // Filter
+ const filteredRequest = OICPEndpointSecurity.filterOicpEndpointDeleteRequest(req.query);
+ UtilsService.assertIdIsProvided(action, filteredRequest.ID, MODULE_NAME, 'handleDeleteOicpEndpoint', req.user);
+ // Check auth
+ if (!Authorizations.canDeleteOicpEndpoint(req.user)) {
+ throw new AppAuthError({
+ errorCode: HTTPAuthError.ERROR,
+ user: req.user,
+ action: Action.DELETE, entity: Entity.OICP_ENDPOINT,
+ module: MODULE_NAME, method: 'handleDeleteOicpEndpoint',
+ value: filteredRequest.ID
+ });
+ }
+ // Get
+ const oicpEndpoint = await OICPEndpointStorage.getOicpEndpoint(req.user.tenantID, filteredRequest.ID);
+ UtilsService.assertObjectExists(action, oicpEndpoint, `OICPEndpoint with ID '${String(filteredRequest.ID)}' does not exist`,
+ MODULE_NAME, 'handleDeleteOicpEndpoint', req.user);
+ // Delete
+ await OICPEndpointStorage.deleteOicpEndpoint(req.user.tenantID, oicpEndpoint.id);
+ // Log
+ Logging.logSecurityInfo({
+ tenantID: req.user.tenantID,
+ user: req.user, module: MODULE_NAME, method: 'handleDeleteOicpEndpoint',
+ message: `Oicp Endpoint '${oicpEndpoint.name}' has been deleted successfully`,
+ action: action,
+ detailedMessages: { oicpEndpoint }
+ });
+ // Ok
+ res.json(Constants.REST_RESPONSE_SUCCESS);
+ next();
+ }
+
static async handleCreateOicpEndpoint(action: ServerAction, req: Request, res: Response, next: NextFunction) {
// Check auth
if (!Authorizations.canCreateOicpEndpoint(req.user)) {
@@ -40,7 +78,18 @@ export default class OICPEndpointService {
...filteredRequest,
createdBy: { id: req.user.id },
createdOn: new Date(),
- status: OICPRegistrationStatus.NEW
+ status: OICPRegistrationStatus.NEW,
+ version: '2.3.0',
+ availableEndpoints: {
+ evses: '/api/oicp/evsepush/v23/operators/{operatorID}/data-records',
+ statuses: '/api/oicp/evsepush/v21/operators/{operatorID}/status-records',
+ authorizeStart: '/api/oicp/charging/v21/operators/{operatorID}/authorize/start',
+ authorizeStop: '/api/oicp/charging/v21/operators/{operatorID}/authorize/stop',
+ pricing: '/api/oicp/dynamicpricing/v10/operators/{operatorID}/evse-pricing',
+ cdrs: '/api/oicp/cdrmgmt/v22/operators/{operatorID}/charge-detail-record',
+ pricingProducts: '/api/oicp/dynamicpricing/v10/operators/{operatorID}/pricing-products',
+ notifications: '/api/oicp/notificationmgmt/v11/charging-notifications'
+ }
} as OICPEndpoint;
const endpointID = await OICPEndpointStorage.saveOicpEndpoint(req.user.tenantID, oicpEndpoint);
// Log
@@ -56,6 +105,115 @@ export default class OICPEndpointService {
next();
}
+ static async handleUpdateOicpEndpoint(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
+ // Check if component is active
+ UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.OICP,
+ Action.UPDATE, Entity.OICP_ENDPOINT, MODULE_NAME, 'handleUpdateOicpEndpoint');
+ // Filter
+ const filteredRequest = OICPEndpointSecurity.filterOicpEndpointUpdateRequest(req.body);
+ // Check Mandatory fields
+ UtilsService.checkIfOICPEndpointValid(filteredRequest, req);
+ // Check auth
+ if (!Authorizations.canUpdateOicpEndpoint(req.user)) {
+ throw new AppAuthError({
+ errorCode: HTTPAuthError.ERROR,
+ user: req.user,
+ action: Action.UPDATE, entity: Entity.OICP_ENDPOINT,
+ module: MODULE_NAME, method: 'handleUpdateOicpEndpoint',
+ value: filteredRequest.id
+ });
+ }
+ // Get OicpEndpoint
+ const oicpEndpoint = await OICPEndpointStorage.getOicpEndpoint(req.user.tenantID, filteredRequest.id);
+ UtilsService.assertObjectExists(action, oicpEndpoint, `OICPEndpoint with ID '${String(filteredRequest.id)}' does not exist`,
+ MODULE_NAME, 'handleUpdateOicpEndpoint', req.user);
+ // Update timestamp
+ oicpEndpoint.lastChangedBy = { 'id': req.user.id };
+ oicpEndpoint.lastChangedOn = new Date();
+ // Update OicpEndpoint
+ await OICPEndpointStorage.saveOicpEndpoint(req.user.tenantID, { ...oicpEndpoint, ...filteredRequest });
+ // Log
+ Logging.logSecurityInfo({
+ tenantID: req.user.tenantID,
+ user: req.user, module: MODULE_NAME, method: 'handleUpdateOicpEndpoint',
+ message: `Oicp Endpoint '${oicpEndpoint.name}' has been updated successfully`,
+ action: action,
+ detailedMessages: { endpoint: oicpEndpoint }
+ });
+ // Ok
+ res.json(Constants.REST_RESPONSE_SUCCESS);
+ next();
+ }
+
+ static async handleGetOicpEndpoint(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
+ // Check if component is active
+ UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.OICP,
+ Action.READ, Entity.OICP_ENDPOINT, MODULE_NAME, 'handleGetOicpEndpoint');
+ // Filter
+ const endpointID = OICPEndpointSecurity.filterOicpEndpointRequestByID(req.query);
+ UtilsService.assertIdIsProvided(action, endpointID, MODULE_NAME, 'handleGetOicpEndpoint', req.user);
+ // Check auth
+ if (!Authorizations.canReadOicpEndpoint(req.user)) {
+ throw new AppAuthError({
+ errorCode: HTTPAuthError.ERROR,
+ user: req.user,
+ action: Action.READ, entity: Entity.OICP_ENDPOINT,
+ module: MODULE_NAME, method: 'handleGetOicpEndpoint',
+ value: endpointID
+ });
+ }
+ // Get it
+ const oicpEndpoint = await OICPEndpointStorage.getOicpEndpoint(req.user.tenantID, endpointID,
+ [
+ 'id', 'name', 'role', 'baseUrl', 'countryCode', 'partyId', 'version', 'status', 'patchJobStatus', 'localToken', 'token',
+ 'patchJobResult.successNbr', 'patchJobResult.failureNbr', 'patchJobResult.totalNbr'
+ ]);
+ UtilsService.assertObjectExists(action, oicpEndpoint, `OICPEndpoint with ID '${String(endpointID)}' does not exist`,
+ MODULE_NAME, 'handleGetOicpEndpoint', req.user);
+ res.json(oicpEndpoint);
+ next();
+ }
+
+ static async handleGetOicpEndpoints(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
+ // Check if component is active
+ UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.OICP,
+ Action.LIST, Entity.OICP_ENDPOINT, MODULE_NAME, 'handleGetOicpEndpoints');
+ // Check auth
+ if (!Authorizations.canListOicpEndpoints(req.user)) {
+ throw new AppAuthError({
+ errorCode: HTTPAuthError.ERROR,
+ user: req.user,
+ action: Action.LIST, entity: Entity.OICP_ENDPOINTS,
+ module: MODULE_NAME, method: 'handleGetOicpEndpoints'
+ });
+ }
+ // Check User
+ let userProject: string[] = [];
+ if (Authorizations.canListUsers(req.user)) {
+ userProject = [ 'createdBy.name', 'createdBy.firstName', 'lastChangedBy.name', 'lastChangedBy.firstName' ];
+ }
+ // Filter
+ const filteredRequest = OICPEndpointSecurity.filterOicpEndpointsRequest(req.query);
+ // Get all oicpendpoints
+ const oicpEndpoints = await OICPEndpointStorage.getOicpEndpoints(req.user.tenantID,
+ {
+ 'search': filteredRequest.Search
+ }, {
+ limit: filteredRequest.Limit,
+ skip: filteredRequest.Skip,
+ sort: filteredRequest.Sort,
+ onlyRecordCount: filteredRequest.OnlyRecordCount
+ },
+ [
+ 'id', 'name', 'role', 'baseUrl', 'countryCode', 'partyId', 'version', 'status', 'lastChangedOn', 'lastPatchJobOn',
+ 'backgroundPatchJob', 'localToken', 'token',
+ 'lastPatchJobResult.successNbr', 'lastPatchJobResult.failureNbr', 'lastPatchJobResult.totalNbr',
+ ...userProject
+ ]);
+ res.json(oicpEndpoints);
+ next();
+ }
+
static async handleSendEVSEStatusesOicpEndpoint(action: ServerAction, req: Request, res: Response, next: NextFunction) {
// Check auth
if (!Authorizations.canTriggerJobOicpEndpoint(req.user)) {
@@ -190,4 +348,104 @@ export default class OICPEndpointService {
}
next();
}
+
+ static async handleUnregisterOicpEndpoint(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
+ // Check if component is active
+ UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.OICP,
+ Action.READ, Entity.OICP_ENDPOINT, MODULE_NAME, 'handleUnregisterOicpEndpoint');
+ // Check auth
+ if (!Authorizations.canRegisterOicpEndpoint(req.user)) {
+ throw new AppAuthError({
+ errorCode: HTTPAuthError.ERROR,
+ user: req.user,
+ action: Action.REGISTER, entity: Entity.OICP_ENDPOINT,
+ module: MODULE_NAME, method: 'handleUnregisterOicpEndpoint'
+ });
+ }
+ // Filter
+ const filteredRequest = OICPEndpointSecurity.filterOicpEndpointRegisterRequest(req.body);
+ UtilsService.assertIdIsProvided(action, filteredRequest.id, MODULE_NAME, 'handleUnregisterOicpEndpoint', req.user);
+ // Get OicpEndpoint
+ const oicpEndpoint = await OICPEndpointStorage.getOicpEndpoint(req.user.tenantID, filteredRequest.id);
+ UtilsService.assertObjectExists(action, oicpEndpoint, `OICPEndpoint with ID '${String(filteredRequest.id)}' does not exist`,
+ MODULE_NAME, 'handleUnregisterOicpEndpoint', req.user);
+ const tenant = await TenantStorage.getTenant(req.user.tenantID);
+ // Build OICP Client
+ const oicpClient = await OICPClientFactory.getOicpClient(tenant, oicpEndpoint);
+ // Try to register
+ const result = await oicpClient.unregister();
+ // Check ping result
+ if (result.statusCode === StatusCodes.OK) {
+ // Log
+ Logging.logSecurityInfo({
+ tenantID: req.user.tenantID,
+ user: req.user, module: MODULE_NAME, method: 'handleUnregisterOicpEndpoint',
+ message: `Oicp Endpoint '${oicpEndpoint.name}' can be reached successfully`,
+ action: action,
+ detailedMessages: { result }
+ });
+ res.json(Object.assign(result, Constants.REST_RESPONSE_SUCCESS));
+ } else {
+ // Log
+ Logging.logSecurityError({
+ tenantID: req.user.tenantID,
+ user: req.user, module: MODULE_NAME, method: 'handleUnregisterOicpEndpoint',
+ message: `Oicp Endpoint '${oicpEndpoint.name}' cannot be reached`,
+ action: action,
+ detailedMessages: { result }
+ });
+ res.json(result);
+ }
+ next();
+ }
+
+ static async handleRegisterOicpEndpoint(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
+ // Check if component is active
+ UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.OICP,
+ Action.UPDATE, Entity.OICP_ENDPOINT, MODULE_NAME, 'handleRegisterOicpEndpoint');
+ // Check auth
+ if (!Authorizations.canRegisterOicpEndpoint(req.user)) {
+ throw new AppAuthError({
+ errorCode: HTTPAuthError.ERROR,
+ user: req.user,
+ action: Action.REGISTER, entity: Entity.OICP_ENDPOINT,
+ module: MODULE_NAME, method: 'handleRegisterOicpEndpoint'
+ });
+ }
+ // Filter
+ const filteredRequest = OICPEndpointSecurity.filterOicpEndpointRegisterRequest(req.body);
+ UtilsService.assertIdIsProvided(action, filteredRequest.id, MODULE_NAME, 'handleRegisterOicpEndpoint', req.user);
+ // Get OicpEndpoint
+ const oicpEndpoint = await OICPEndpointStorage.getOicpEndpoint(req.user.tenantID, filteredRequest.id);
+ UtilsService.assertObjectExists(action, oicpEndpoint, `OICPEndpoint with ID '${filteredRequest.id}' does not exist`,
+ MODULE_NAME, 'handleRegisterOicpEndpoint', req.user);
+ const tenant = await TenantStorage.getTenant(req.user.tenantID);
+ // Build OICP Client
+ const oicpClient = await OICPClientFactory.getOicpClient(tenant, oicpEndpoint);
+ // Try to register
+ const result = await oicpClient.register();
+ // Check ping result
+ if (result.statusCode === StatusCodes.OK) {
+ // Log
+ Logging.logSecurityInfo({
+ tenantID: req.user.tenantID,
+ user: req.user, module: MODULE_NAME, method: 'handleRegisterOicpEndpoint',
+ message: `Oicp Endpoint '${oicpEndpoint.name}' can be reached successfully`,
+ action: action,
+ detailedMessages: { result }
+ });
+ res.json(Object.assign(result, Constants.REST_RESPONSE_SUCCESS));
+ } else {
+ // Log
+ Logging.logSecurityError({
+ tenantID: req.user.tenantID,
+ user: req.user, module: MODULE_NAME, method: 'handleRegisterOicpEndpoint',
+ message: `Oicp Endpoint '${oicpEndpoint.name}' cannot be reached`,
+ action: action,
+ detailedMessages: { result }
+ });
+ res.json(result);
+ }
+ next();
+ }
}

MODIFY
src/server/restOICP/v2.3.0/service/security/OICPEndpointSecurity.ts
src/server/restOICP/v2.3.0/service/security/OICPEndpointSecurity.ts
+import { HttpOICPEndpointsRequest } from '../../../../../types/requests/HttpOICPEndpointRequest';
import OICPEndpoint from '../../../../../types/oicp/OICPEndpoint';
+import UtilsSecurity from '../../../../rest/v1/service/security/UtilsSecurity';
import sanitize from 'mongo-sanitize';
export default class OICPEndpointSecurity {
+ static filterOicpEndpointDeleteRequest(request: any): HttpOICPEndpointsRequest {
+ const filteredRequest: HttpOICPEndpointsRequest = {} as HttpOICPEndpointsRequest;
+ // Set
+ filteredRequest.ID = sanitize(request.ID);
+ return filteredRequest;
+ }
+
+ static filterOicpEndpointRequestByID(request: any): string {
+ return sanitize(request.ID);
+ }
+
+ static filterOicpEndpointUpdateRequest(request: any): Partial<OICPEndpoint> {
+ const filteredRequest = OICPEndpointSecurity._filterOicpEndpointRequest(request);
+ filteredRequest.id = sanitize(request.id);
+ return filteredRequest;
+ }
+
static filterOicpEndpointCreateRequest(request: any): Partial<OICPEndpoint> {
return OICPEndpointSecurity._filterOicpEndpointRequest(request);
}
+ public static filterOicpEndpointsRequest(request: any): HttpOICPEndpointsRequest {
+ const filteredRequest: HttpOICPEndpointsRequest = {} as HttpOICPEndpointsRequest;
+ filteredRequest.Search = sanitize(request.Search);
+ UtilsSecurity.filterSkipAndLimit(request, filteredRequest);
+ UtilsSecurity.filterSort(request, filteredRequest);
+ return filteredRequest;
+ }
+
static filterOicpEndpointPingRequest(request: any): Partial<OICPEndpoint> {
const filteredRequest = OICPEndpointSecurity._filterOicpEndpointRequest(request);
filteredRequest.id = sanitize(request.id);
@@ -30,6 +57,12 @@ export default class OICPEndpointSecurity {
return filteredRequest;
}
+ static filterOicpEndpointRegisterRequest(request: any): Partial<OICPEndpoint> {
+ const filteredRequest = OICPEndpointSecurity._filterOicpEndpointRequest(request);
+ filteredRequest.id = sanitize(request.id);
+ return filteredRequest;
+ }
+
// eslint-disable-next-line no-unused-vars
static _filterOicpEndpointRequest(request: any): Partial<OICPEndpoint> {
const filteredRequest: Partial<OICPEndpoint> = {};

MODIFY
src/storage/mongodb/OICPEndpointStorage.ts
src/storage/mongodb/OICPEndpointStorage.ts
@@ -13,13 +13,9 @@ import Utils from '../../utils/Utils';
const MODULE_NAME = 'OICPEndpointStorage';
export default class OICPEndpointStorage {
- static async getOicpEndpoint(tenantID: string, id: string): Promise<OICPEndpoint> {
- // Debug
- const uniqueTimerID = Logging.traceStart(tenantID, MODULE_NAME, 'getOicpEndpoint');
- const endpointsMDB = await OICPEndpointStorage.getOicpEndpoints(tenantID, { id: id }, Constants.DB_PARAMS_SINGLE_RECORD);
-
- // Debug
- Logging.traceEnd(tenantID, MODULE_NAME, 'getOicpEndpoint', uniqueTimerID, { id });
+ static async getOicpEndpoint(tenantID: string, id: string, projectFields?: string[]): Promise<OICPEndpoint> {
+ const endpointsMDB = await OICPEndpointStorage.getOicpEndpoints(
+ tenantID, { oicpEndpointIDs: [id] }, Constants.DB_PARAMS_SINGLE_RECORD, projectFields);
return endpointsMDB.count === 1 ? endpointsMDB.result[0] : null;
}
@@ -58,7 +54,8 @@ export default class OICPEndpointStorage {
businessDetails: oicpEndpointToSave.businessDetails,
availableEndpoints: oicpEndpointToSave.availableEndpoints,
lastPatchJobOn: oicpEndpointToSave.lastPatchJobOn,
- lastPatchJobResult: oicpEndpointToSave.lastPatchJobResult
+ lastPatchJobResult: oicpEndpointToSave.lastPatchJobResult,
+ version: oicpEndpointToSave.version
};
// Add Last Changed/Created props
DatabaseUtils.addLastChangedCreatedProps(oicpEndpointMDB, oicpEndpointToSave);
@@ -74,7 +71,9 @@ export default class OICPEndpointStorage {
}
// Delegate
- static async getOicpEndpoints(tenantID: string, params: { search?: string; role?: string; id?: string; }, dbParams: DbParams): Promise<DataResult<OICPEndpoint>> {
+ static async getOicpEndpoints(tenantID: string,
+ params: { search?: string; role?: string; oicpEndpointIDs?: string[]; localToken?: string },
+ dbParams: DbParams, projectFields?: string[]): Promise<DataResult<OICPEndpoint>> {
// Debug
const uniqueTimerID = Logging.traceStart(tenantID, MODULE_NAME, 'getOicpEndpoints');
// Check Tenant
@@ -89,15 +88,20 @@ export default class OICPEndpointStorage {
const aggregation: any[] = [];
// Set the filters
const filters: FilterParams = {};
- // Source?
- if (params.id) {
- filters._id = Utils.convertToObjectID(params.id);
- } else if (params.search) {
- // Build filter
+ // Search
+ if (params.search) {
filters.$or = [
{ 'name': { $regex: params.search, $options: 'i' } }
];
}
+ if (params.oicpEndpointIDs) {
+ filters._id = {
+ $in: params.oicpEndpointIDs.map((oicpEndpointID) => Utils.convertToObjectID(oicpEndpointID))
+ };
+ }
+ if (params.localToken) {
+ filters.localToken = params.localToken;
+ }
if (params.role) {
filters.role = params.role;
}
@@ -117,6 +121,7 @@ export default class OICPEndpointStorage {
.toArray();
// Check if only the total count is requested
if (dbParams.onlyRecordCount) {
+ Logging.traceEnd(tenantID, MODULE_NAME, 'getOicpEndpoints', uniqueTimerID, oicpEndpointsCountMDB);
return {
count: (oicpEndpointsCountMDB.length > 0 ? oicpEndpointsCountMDB[0].count : 0),
result: []
@@ -124,7 +129,6 @@ export default class OICPEndpointStorage {
}
// Remove the limit
aggregation.pop();
-
// Add Created By / Last Changed By
DatabaseUtils.pushCreatedLastChangedInAggregation(tenantID, aggregation);
// Handle the ID
@@ -144,13 +148,16 @@ export default class OICPEndpointStorage {
aggregation.push({
$limit: dbParams.limit
});
+ // Project
+ DatabaseUtils.projectFields(aggregation, projectFields);
// Read DB
const oicpEndpointsMDB = await global.database.getCollection<any>(tenantID, 'oicpendpoints')
- .aggregate(aggregation, { collation: { locale: Constants.DEFAULT_LOCALE, strength: 2 } })
+ .aggregate(aggregation, {
+ allowDiskUse: true
+ })
.toArray();
-
// Debug
- Logging.traceEnd(tenantID, MODULE_NAME, 'getOicpEndpoints', uniqueTimerID, { params });
+ Logging.traceEnd(tenantID, MODULE_NAME, 'getOicpEndpoints', uniqueTimerID, oicpEndpointsMDB);
// Ok
return {
count: (oicpEndpointsCountMDB.length > 0 ? oicpEndpointsCountMDB[0].count : 0),

MODIFY
src/types/Server.ts
src/types/Server.ts
@@ -155,6 +155,8 @@ export enum ServerAction {
OICP_ENPOINT_PING = 'OicpEndpointPing',
OICP_ENPOINT_TRIGGER_JOBS = 'OicpEndpointTriggerJobs',
OICP_ENDPOINT = 'OicpEndpoint',
+ OICP_ENDPOINTS = 'OicpEndpoints',
+ OICP_ENDPOINT_START = 'OicpEndpointStart',
OICP_PUSH_EVSE_DATA = 'OicpPushEvseData',
OICP_PUSH_EVSE_STATUSES = 'OicpPushEvseStatuses',
OICP_UPDATE_EVSE_STATUS = 'OicpUpdateEvseStatus',
@@ -173,7 +175,10 @@ export enum ServerAction {
OICP_ENPOINT_SEND_EVSES = 'OicpEndpointSendEVSEs',
OICP_PUSH_SESSIONS = 'OicpPushSessions',
OICP_CREATE_AXIOS_INSTANCE = 'OicpCreateAxiosInstance',
-
+ OICP_ENDPOINT_UPDATE = 'OicpEndpointUpdate',
+ OICP_ENDPOINT_REGISTER = 'OicpEndpointRegister',
+ OICP_ENDPOINT_UNREGISTER = 'OicpEndpointUnregister',
+ OICP_ENDPOINT_DELETE = 'OicpEndpointDelete',
AUTHORIZE = 'Authorize',

MODIFY
src/types/oicp/OICPEndpoint.ts
src/types/oicp/OICPEndpoint.ts
@@ -11,7 +11,8 @@ export default interface OICPEndpoint extends CreatedUpdatedProps {
backgroundPatchJob: boolean;
status?: string;
businessDetails?: OicpBusinessDetails;
- availableEndpoints?: OICPEndpoint[];
+ availableEndpoints?: any;
+ version: string;
lastPatchJobOn?: Date;
lastPatchJobResult?: {
successNbr: number;
@@ -20,5 +21,6 @@ export default interface OICPEndpoint extends CreatedUpdatedProps {
chargeBoxIDsInFailure?: string[];
chargeBoxIDsInSuccess?: string[];
};
+
}

ADD
src/types/requests/HttpOICPEndpointRequest.ts
+import HttpDatabaseRequest from './HttpDatabaseRequest';
+
+export interface HttpOICPEndpointsRequest extends HttpDatabaseRequest{
+ ID?: string;
+ Search?: string;
+}

Frontend Endpoints added for OICP