Migrated Log endpoint to the new Authorization framework
MODIFY
src/assets/server/rest/v1/docs/e-mobility-oas.json
src/assets/server/rest/v1/docs/e-mobility-oas.json
{
"$ref": "#/components/parameters/LogAction"
},
+ {
+ "$ref": "#/components/parameters/ChargingStationID"
+ },
+ {
+ "$ref": "#/components/parameters/SiteID"
+ },
{
"$ref": "#/components/parameters/UserID"
},
"type": "string"
}
},
+ "SiteID": {
+ "in": "query",
+ "name": "SiteID",
+ "description": "Site ID",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
"UserID": {
"in": "query",
"name": "UserID",
"name": "Source",
"description": "Log source",
"schema": {
- "type": "string"
+ "type": "string",
+ "enum": [
+ "RestServer",
+ "SoapServer",
+ "JsonServer",
+ "OcpiServer",
+ "OicpServer",
+ "ODataServer",
+ "BatchServer"
+ ]
}
},
"LogHost": {

MODIFY
src/assets/server/rest/v1/schemas/logging/loggings-get.json
src/assets/server/rest/v1/schemas/logging/loggings-get.json
"UserID": {
"$ref": "common#/definitions/ids"
},
+ "SiteID": {
+ "$ref": "common#/definitions/ids"
+ },
"ChargingStationID": {
"$ref": "common#/definitions/string-ids"
},

MODIFY
src/authorization/AuthorizationsDefinition.ts
src/authorization/AuthorizationsDefinition.ts
@@ -29,8 +29,20 @@ export const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
'notificationsActive', 'notifications', 'phone', 'mobile', 'iNumber', 'costCenter', 'address'
]
},
- { resource: Entity.LOGGINGS, action: Action.LIST },
- { resource: Entity.LOGGING, action: Action.READ },
+ {
+ resource: Entity.LOGGINGS, action: Action.LIST,
+ attributes: [
+ 'id', 'level', 'timestamp', 'type', 'source', 'host', 'action', 'message', 'chargingStationID', 'siteID',
+ 'user.name', 'user.firstName', 'actionOnUser.name', 'actionOnUser.firstName', 'hasDetailedMessages', 'method', 'module',
+ ]
+ },
+ {
+ resource: Entity.LOGGING, action: Action.READ,
+ attributes: [
+ 'id', 'level', 'timestamp', 'type', 'source', 'host', 'action', 'message', 'chargingStationID', 'siteID',
+ 'user.name', 'user.firstName', 'actionOnUser.name', 'actionOnUser.firstName', 'hasDetailedMessages', 'detailedMessages'
+ ]
+ },
{ resource: Entity.TENANTS, action: Action.LIST },
{ resource: Entity.TENANT, action: [Action.CREATE, Action.READ, Action.UPDATE, Action.DELETE] },
{
@@ -211,8 +223,20 @@ export const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
]
},
{ resource: Entity.REPORT, action: [Action.READ] },
- { resource: Entity.LOGGINGS, action: Action.LIST },
- { resource: Entity.LOGGING, action: Action.READ },
+ {
+ resource: Entity.LOGGINGS, action: Action.LIST,
+ attributes: [
+ 'id', 'level', 'timestamp', 'type', 'source', 'host', 'action', 'message', 'chargingStationID', 'siteID',
+ 'user.name', 'user.firstName', 'actionOnUser.name', 'actionOnUser.firstName', 'hasDetailedMessages', 'method', 'module',
+ ]
+ },
+ {
+ resource: Entity.LOGGING, action: Action.READ,
+ attributes: [
+ 'id', 'level', 'timestamp', 'type', 'source', 'host', 'action', 'message', 'chargingStationID', 'siteID',
+ 'user.name', 'user.firstName', 'actionOnUser.name', 'actionOnUser.firstName', 'hasDetailedMessages', 'detailedMessages'
+ ]
+ },
{ resource: Entity.PRICING, action: [Action.READ, Action.UPDATE] },
{ resource: Entity.BILLING, action: [Action.CHECK_CONNECTION, Action.CLEAR_BILLING_TEST_DATA] },
{ resource: Entity.TAXES, action: [Action.LIST] },
@@ -992,11 +1016,33 @@ export const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
},
},
{ resource: Entity.REPORT, action: [Action.READ] },
- { resource: Entity.LOGGINGS, action: Action.LIST },
{
- resource: Entity.LOGGING,
- action: Action.READ,
- args: { 'sites': '$.site' }
+ resource: Entity.LOGGINGS, action: Action.LIST,
+ condition: {
+ Fn: 'custom:dynamicAuthorizations',
+ args: {
+ asserts: [],
+ filters: ['SitesAdmin']
+ }
+ },
+ attributes: [
+ 'id', 'level', 'timestamp', 'type', 'source', 'host', 'action', 'message', 'chargingStationID', 'siteID',
+ 'user.name', 'user.firstName', 'actionOnUser.name', 'actionOnUser.firstName', 'hasDetailedMessages', 'method', 'module',
+ ]
+ },
+ {
+ resource: Entity.LOGGING, action: Action.READ,
+ condition: {
+ Fn: 'custom:dynamicAuthorizations',
+ args: {
+ asserts: [],
+ filters: ['SitesAdmin']
+ }
+ },
+ attributes: [
+ 'id', 'level', 'timestamp', 'type', 'source', 'host', 'action', 'message', 'chargingStationID', 'siteID',
+ 'user.name', 'user.firstName', 'actionOnUser.name', 'actionOnUser.firstName', 'hasDetailedMessages', 'detailedMessages'
+ ]
},
{ resource: Entity.TOKENS, action: Action.LIST },
{

MODIFY
src/server/rest/v1/service/AuthorizationService.ts
src/server/rest/v1/service/AuthorizationService.ts
import { Action, AuthorizationActions, AuthorizationContext, AuthorizationFilter, Entity, SiteAreaAuthorizationActions, TagAuthorizationActions } from '../../../../types/Authorization';
import { Car, CarCatalog } from '../../../../types/Car';
-import { CarCatalogDataResult, CarDataResult, CompanyDataResult, SiteAreaDataResult, SiteDataResult, TagDataResult, UserDataResult } from '../../../../types/DataResult';
+import { CarCatalogDataResult, CarDataResult, CompanyDataResult, LogDataResult, SiteAreaDataResult, SiteDataResult, TagDataResult, UserDataResult } from '../../../../types/DataResult';
import { HttpCarCatalogRequest, HttpCarCatalogsRequest, HttpCarRequest, HttpCarsRequest } from '../../../../types/requests/HttpCarRequest';
import { HttpChargingStationRequest, HttpChargingStationsRequest } from '../../../../types/requests/HttpChargingStationRequest';
import { HttpCompaniesRequest, HttpCompanyRequest } from '../../../../types/requests/HttpCompanyRequest';
@@ -20,6 +20,8 @@ import DynamicAuthorizationFactory from '../../../../authorization/DynamicAuthor
import { EntityDataType } from '../../../../types/GlobalType';
import { HTTPAuthError } from '../../../../types/HTTPError';
import { HttpAssetsRequest } from '../../../../types/requests/HttpAssetRequest';
+import { HttpLogRequest } from '../../../../types/requests/HttpLoggingRequest';
+import { Log } from '../../../../types/Log';
import { ServerAction } from '../../../../types/Server';
import Site from '../../../../types/Site';
import SiteArea from '../../../../types/SiteArea';
@@ -69,6 +71,12 @@ export default class AuthorizationService {
tenant, Entity.SITE, userToken, filteredRequest, filteredRequest.ID ? { SiteID: filteredRequest.ID } : {}, authAction, entityData);
}
+ public static async checkAndGetLoggingAuthorizations(tenant: Tenant, userToken: UserToken,
+ filteredRequest: Partial<HttpLogRequest>, authAction: Action, entityData?: EntityDataType): Promise<AuthorizationFilter> {
+ return AuthorizationService.checkAndGetEntityAuthorizations(
+ tenant, Entity.LOGGING, userToken, filteredRequest, filteredRequest.ID ? { LogID: filteredRequest.ID } : {}, authAction, entityData);
+ }
+
public static async addSitesAuthorizations(tenant: Tenant, userToken: UserToken, sites: SiteDataResult, authorizationFilter: AuthorizationFilter): Promise<void> {
// Add canCreate flag to root
sites.canCreate = await AuthorizationService.canPerformAuthorizationAction(tenant, userToken, Entity.SITE, Action.CREATE,
@@ -83,27 +91,40 @@ export default class AuthorizationService {
// Enrich
if (!site.issuer) {
site.canRead = true;
- site.canUpdate = false;
- site.canDelete = false;
- site.canAssignUsers = false;
- site.canUnassignUsers = false;
} else {
site.canRead = true; // Always true as it should be filtered upfront
site.canDelete = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.SITE, Action.DELETE, authorizationFilter, { SiteID: site.id }, site);
+ !site.canDelete && delete site.canDelete; // Optimize data over the net
site.canUpdate = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.SITE, Action.UPDATE, authorizationFilter, { SiteID: site.id }, site);
+ !site.canUpdate && delete site.canUpdate; // Optimize data over the net
site.canExportOCPPParams = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.SITE_AREA, Action.EXPORT_OCPP_PARAMS, authorizationFilter, { SiteID: site.id }, site);
+ !site.canExportOCPPParams && delete site.canExportOCPPParams; // Optimize data over the net
site.canGenerateQrCode = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.SITE_AREA, Action.GENERATE_QR, authorizationFilter, { SiteID: site.id }, site);
+ !site.canGenerateQrCode && delete site.canGenerateQrCode; // Optimize data over the net
site.canAssignUsers = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.USERS_SITES, Action.ASSIGN, authorizationFilter, { SiteID: site.id }, site);
+ !site.canAssignUsers && delete site.canAssignUsers; // Optimize data over the net
site.canUnassignUsers = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.USERS_SITES, Action.UNASSIGN, authorizationFilter, { SiteID: site.id }, site);
+ !site.canUnassignUsers && delete site.canUnassignUsers; // Optimize data over the net
}
}
+ public static addLogsAuthorizations(tenant: Tenant, userToken: UserToken, logs: LogDataResult, authorizationFilter: AuthorizationFilter): void {
+ // Enrich
+ for (const log of logs.result) {
+ AuthorizationService.addLogAuthorizations(tenant, userToken, log, authorizationFilter);
+ }
+ }
+
+ public static addLogAuthorizations(tenant: Tenant, userToken: UserToken, log: Log, authorizationFilter: AuthorizationFilter): void {
+ log.canRead = true; // Always true as it should be filtered upfront
+ }
+
public static async checkAndGetSitesAuthorizations(tenant: Tenant, userToken: UserToken,
filteredRequest: Partial<HttpSiteUsersRequest>): Promise<AuthorizationFilter> {
const authorizationFilters: AuthorizationFilter = {
@@ -118,6 +139,20 @@ export default class AuthorizationService {
return authorizationFilters;
}
+ public static async checkAndGetLoggingsAuthorizations(tenant: Tenant, userToken: UserToken,
+ filteredRequest: Partial<HttpSiteUsersRequest>): Promise<AuthorizationFilter> {
+ const authorizationFilters: AuthorizationFilter = {
+ filters: {},
+ dataSources: new Map(),
+ projectFields: [],
+ authorized: false,
+ };
+ // Check static & dynamic authorization
+ await this.canPerformAuthorizationAction(tenant, userToken, Entity.LOGGINGS, Action.LIST,
+ authorizationFilters, filteredRequest);
+ return authorizationFilters;
+ }
+
public static async checkAndGetSiteUsersAuthorizations(tenant: Tenant, userToken: UserToken,
filteredRequest: Partial<HttpSiteUsersRequest>): Promise<AuthorizationFilter> {
const authorizationFilters: AuthorizationFilter = {
@@ -206,14 +241,14 @@ export default class AuthorizationService {
// Enrich
if (!user.issuer) {
user.canRead = true;
- user.canUpdate = false;
- user.canDelete = false;
} else {
user.canRead = true; // Always true as it should be filtered upfront
user.canUpdate = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.USER, Action.UPDATE, authorizationFilter, { UserID: user.id }, user);
+ !user.canUpdate && delete user.canUpdate; // Optimize data over the net
user.canDelete = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.USER, Action.DELETE, authorizationFilter, { UserID: user.id }, user);
+ !user.canDelete && delete user.canDelete; // Optimize data over the net
}
}
@@ -274,11 +309,17 @@ export default class AuthorizationService {
public static async addTagsAuthorizations(tenant: Tenant, userToken: UserToken, tags: TagDataResult, authorizationFilter: AuthorizationFilter): Promise<void> {
// Add canCreate flag to root
tags.canCreate = await AuthorizationService.canPerformAuthorizationAction(tenant, userToken, Entity.TAG, Action.CREATE, authorizationFilter);
+ !tags.canCreate && delete tags.canCreate; // Optimize data over the net
tags.canAssign = await AuthorizationService.canPerformAuthorizationAction(tenant, userToken, Entity.TAG, Action.ASSIGN, authorizationFilter);
+ !tags.canAssign && delete tags.canAssign; // Optimize data over the net
tags.canDelete = await AuthorizationService.canPerformAuthorizationAction(tenant, userToken, Entity.TAG, Action.DELETE, authorizationFilter);
+ !tags.canDelete && delete tags.canDelete; // Optimize data over the net
tags.canImport = await AuthorizationService.canPerformAuthorizationAction(tenant, userToken, Entity.TAGS, Action.IMPORT, authorizationFilter);
+ !tags.canImport && delete tags.canImport; // Optimize data over the net
tags.canExport = await AuthorizationService.canPerformAuthorizationAction(tenant, userToken, Entity.TAGS, Action.EXPORT, authorizationFilter);
+ !tags.canExport && delete tags.canExport; // Optimize data over the net
tags.canUnassign = await AuthorizationService.canPerformAuthorizationAction(tenant, userToken, Entity.TAGS, Action.UNASSIGN, authorizationFilter);
+ !tags.canUnassign && delete tags.canUnassign; // Optimize data over the net
tags.metadata = authorizationFilter.metadata;
// Enrich
for (const tag of tags.result) {
@@ -290,20 +331,23 @@ export default class AuthorizationService {
// Enrich
if (!tag.issuer) {
tag.canRead = true;
- tag.canUpdate = false;
- tag.canDelete = false;
} else {
tag.canRead = true; // Always true as it should be filtered upfront
tag.canDelete = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.TAG, Action.DELETE, authorizationFilter, { TagID: tag.id }, tag);
+ !tag.canDelete && delete tag.canDelete; // Optimize data over the net
tag.canUpdate = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.TAG, Action.UPDATE, authorizationFilter, { TagID: tag.id }, tag);
+ !tag.canUpdate && delete tag.canUpdate; // Optimize data over the net
tag.canUpdateByVisualID = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.TAG, Action.UPDATE_BY_VISUAL_ID, authorizationFilter, { TagID: tag.id }, tag);
+ !tag.canUpdateByVisualID && delete tag.canUpdateByVisualID; // Optimize data over the net
tag.canUnassign = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.TAG, Action.UNASSIGN, authorizationFilter, { TagID: tag.id }, tag);
+ !tag.canUnassign && delete tag.canUnassign; // Optimize data over the net
tag.canAssign = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.TAG, Action.ASSIGN, authorizationFilter, { TagID: tag.id }, tag);
+ !tag.canAssign && delete tag.canAssign; // Optimize data over the net
}
}
@@ -335,14 +379,14 @@ export default class AuthorizationService {
// Enrich
if (!company.issuer) {
company.canRead = true;
- company.canUpdate = false;
- company.canDelete = false;
} else {
company.canRead = true; // Always true as it should be filtered upfront
company.canDelete = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.COMPANY, Action.DELETE, authorizationFilter, { CompanyID: company.id }, company);
+ !company.canDelete && delete company.canDelete; // Optimize data over the net
company.canUpdate = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.COMPANY, Action.UPDATE, authorizationFilter, { CompanyID: company.id }, company);
+ !company.canUpdate && delete company.canUpdate; // Optimize data over the net
}
}
@@ -386,31 +430,33 @@ export default class AuthorizationService {
// Enrich
if (!siteArea.issuer) {
siteArea.canRead = true;
- siteArea.canUpdate = false;
- siteArea.canDelete = false;
- siteArea.canAssignAssets = false;
- siteArea.canUnassignAssets = false;
- siteArea.canAssignChargingStations = false;
- siteArea.canUnassignChargingStations = false;
} else {
// Downcast & enhance filters with values needed in dynamic filters
siteArea.canRead = true; // Always true as it should be filtered upfront
siteArea.canUpdate = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.SITE_AREA, Action.DELETE, authorizationFilter, { SiteAreaID: siteArea.id, SiteID: siteArea.siteID }, siteArea);
+ !siteArea.canUpdate && delete siteArea.canUpdate; // Optimize data over the net
siteArea.canDelete = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.SITE_AREA, Action.UPDATE, authorizationFilter, { SiteAreaID: siteArea.id, SiteID: siteArea.siteID }, siteArea);
+ !siteArea.canDelete && delete siteArea.canDelete; // Optimize data over the net
siteArea.canAssignAssets = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.SITE_AREA, Action.ASSIGN_ASSETS_TO_SITE_AREA, authorizationFilter, { SiteAreaID: siteArea.id, SiteID: siteArea.siteID }, siteArea);
+ !siteArea.canAssignAssets && delete siteArea.canAssignAssets; // Optimize data over the net
siteArea.canUnassignAssets = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.SITE_AREA, Action.UNASSIGN_ASSETS_TO_SITE_AREA, authorizationFilter, { SiteAreaID: siteArea.id, SiteID: siteArea.siteID }, siteArea);
+ !siteArea.canUnassignAssets && delete siteArea.canUnassignAssets; // Optimize data over the net
siteArea.canAssignChargingStations = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.SITE_AREA, Action.ASSIGN_CHARGING_STATIONS_TO_SITE_AREA, authorizationFilter, { SiteAreaID: siteArea.id, SiteID: siteArea.siteID }, siteArea);
+ !siteArea.canAssignChargingStations && delete siteArea.canAssignChargingStations; // Optimize data over the net
siteArea.canUnassignChargingStations = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.SITE_AREA, Action.UNASSIGN_CHARGING_STATIONS_TO_SITE_AREA, authorizationFilter, { SiteAreaID: siteArea.id, SiteID: siteArea.siteID }, siteArea);
+ !siteArea.canUnassignChargingStations && delete siteArea.canUnassignChargingStations; // Optimize data over the net
siteArea.canExportOCPPParams = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.SITE_AREA, Action.EXPORT_OCPP_PARAMS, authorizationFilter, { SiteAreaID: siteArea.id, SiteID: siteArea.siteID }, siteArea);
+ !siteArea.canExportOCPPParams && delete siteArea.canExportOCPPParams; // Optimize data over the net
siteArea.canGenerateQrCode = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.SITE_AREA, Action.GENERATE_QR, authorizationFilter, { SiteAreaID: siteArea.id, SiteID: siteArea.siteID }, siteArea);
+ !siteArea.canGenerateQrCode && delete siteArea.canGenerateQrCode; // Optimize data over the net
}
}
@@ -474,8 +520,10 @@ export default class AuthorizationService {
car.canRead = true; // Always true as it should be filtered upfront
car.canDelete = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.CAR, Action.DELETE, authorizationFilter, { CarID: car.id }, car);
+ !car.canDelete && delete car.canDelete; // Optimize data over the net
car.canUpdate = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.CAR, Action.UPDATE, authorizationFilter, { CarID: car.id }, car);
+ !car.canUpdate && delete car.canUpdate; // Optimize data over the net
}
public static async checkAndGetCarCatalogsAuthorizations(tenant: Tenant, userToken: UserToken,
@@ -513,8 +561,10 @@ export default class AuthorizationService {
carCatalog.canRead = true; // Always true as it should be filtered upfront
carCatalog.canDelete = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.CAR_CATALOG, Action.DELETE, authorizationFilter, { CarCatalogID: carCatalog.id }, carCatalog);
+ !carCatalog.canDelete && delete carCatalog.canDelete; // Optimize data over the net
carCatalog.canUpdate = await AuthorizationService.canPerformAuthorizationAction(
tenant, userToken, Entity.CAR_CATALOG, Action.UPDATE, authorizationFilter, { CarCatalogID: carCatalog.id }, carCatalog);
+ !carCatalog.canUpdate && delete carCatalog.canUpdate; // Optimize data over the net
}

MODIFY
src/server/rest/v1/service/LoggingService.ts
src/server/rest/v1/service/LoggingService.ts
-import { Action, Entity } from '../../../../types/Authorization';
+import { DataResult, LogDataResult } from '../../../../types/DataResult';
import { NextFunction, Request, Response } from 'express';
-import AppAuthError from '../../../../exception/AppAuthError';
-import Authorizations from '../../../../authorization/Authorizations';
-import ChargingStationStorage from '../../../../storage/mongodb/ChargingStationStorage';
+import { Action } from '../../../../types/Authorization';
+import AuthorizationService from './AuthorizationService';
import Constants from '../../../../utils/Constants';
-import { DataResult } from '../../../../types/DataResult';
-import { HTTPAuthError } from '../../../../types/HTTPError';
import { Log } from '../../../../types/Log';
import LoggingStorage from '../../../../storage/mongodb/LoggingStorage';
import LoggingValidator from '../validator/LoggingValidator';
import { ServerAction } from '../../../../types/Server';
-import { TenantComponents } from '../../../../types/Tenant';
import Utils from '../../../../utils/Utils';
import UtilsService from './UtilsService';
import moment from 'moment';
@@ -34,21 +30,10 @@ export default class LoggingService {
public static async handleGetLog(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Filter
const filteredRequest = LoggingValidator.getInstance().validateLoggingGetReq(req.query);
- // Check auth
- if (!await Authorizations.canReadLog(req.user)) {
- throw new AppAuthError({
- errorCode: HTTPAuthError.FORBIDDEN,
- user: req.user,
- action: Action.READ, entity: Entity.LOGGING,
- module: MODULE_NAME, method: 'handleGetLog'
- });
- }
- // Get Log
- const logging = await LoggingStorage.getLog(req.tenant, filteredRequest.ID, [
- 'id', 'level', 'timestamp', 'type', 'source', 'host', 'action', 'message', 'chargingStationID',
- 'user.name', 'user.firstName', 'actionOnUser.name', 'actionOnUser.firstName', 'hasDetailedMessages', 'detailedMessages'
- ]);
- res.json(logging);
+ // Check and Get Log
+ const log = await UtilsService.checkAndGetLogAuthorization(
+ req.tenant, req.user, filteredRequest.ID, Action.READ, action, null, null, true);
+ res.json(log);
next();
}
@@ -63,6 +48,7 @@ export default class LoggingService {
'host',
'source',
'action',
+ 'siteID',
'chargingStationID',
'module',
'method',
@@ -78,6 +64,7 @@ export default class LoggingService {
log.host,
log.source,
log.action,
+ log.siteID,
log.chargingStationID,
log.module,
log.method,
@@ -89,60 +76,41 @@ export default class LoggingService {
}
private static async getLogs(req: Request): Promise<DataResult<Log>> {
- // Check auth
- if (!await Authorizations.canListLoggings(req.user)) {
- throw new AppAuthError({
- errorCode: HTTPAuthError.FORBIDDEN,
- user: req.user,
- action: Action.LIST, entity: Entity.LOGGINGS,
- module: MODULE_NAME, method: 'getLogs'
- });
- }
// Filter
const filteredRequest = LoggingValidator.getInstance().validateLoggingsGetReq(req.query);
- // Add filter for Site Admins
- if (Utils.isComponentActiveFromToken(req.user, TenantComponents.ORGANIZATION) && Authorizations.isSiteAdmin(req.user)) {
- // Optimization: Retrieve Charging Stations to get the logs only for the Site Admin user
- const chargingStations = await ChargingStationStorage.getChargingStations(req.tenant,
- { siteIDs: req.user.sitesAdmin, withSiteArea: true }, Constants.DB_PARAMS_MAX_LIMIT);
- // Check if Charging Station is already filtered
- if (chargingStations.count === 0) {
- filteredRequest.Source = '';
- } else if (filteredRequest.Source && filteredRequest.Source.length > 0) {
- // Filter only Site Admin Chargers
- const sources = [];
- for (const chargingStation of chargingStations.result) {
- if (filteredRequest.Source.includes(chargingStation.id)) {
- sources.push(chargingStation.id);
- }
- }
- filteredRequest.Source = sources.join('|');
- } else {
- // Add all Site Admin Chargers in filter
- filteredRequest.Source = chargingStations.result.map((chargingStation) => chargingStation.id).join('|');
- }
+ // Check dynamic auth
+ const authorizationSitesFilter = await AuthorizationService.checkAndGetLoggingsAuthorizations(
+ req.tenant, req.user, filteredRequest);
+ if (!authorizationSitesFilter.authorized) {
+ return Constants.DB_EMPTY_DATA_RESULT;
}
// Get logs
- const loggings = await LoggingStorage.getLogs(req.tenant, {
+ const logs = await LoggingStorage.getLogs(req.tenant, {
search: filteredRequest.Search,
startDateTime: filteredRequest.StartDateTime,
endDateTime: filteredRequest.EndDateTime,
userIDs: filteredRequest.UserID ? filteredRequest.UserID.split('|') : null,
+ siteIDs: filteredRequest.SiteID ? filteredRequest.SiteID.split('|') : null,
chargingStationIDs: filteredRequest.ChargingStationID ? filteredRequest.ChargingStationID.split('|') : null,
hosts: filteredRequest.Host ? filteredRequest.Host.split('|') : null,
levels: filteredRequest.Level ? filteredRequest.Level.split('|') : null,
type: filteredRequest.Type,
sources: filteredRequest.Source ? filteredRequest.Source.split('|') : null,
actions: filteredRequest.Action ? filteredRequest.Action.split('|') : null,
+ ...authorizationSitesFilter.filters
}, {
limit: filteredRequest.Limit,
skip: filteredRequest.Skip,
sort: UtilsService.httpSortFieldsToMongoDB(filteredRequest.SortFields),
onlyRecordCount: filteredRequest.OnlyRecordCount
- }, [
- 'id', 'level', 'timestamp', 'type', 'source', 'host', 'action', 'message', 'chargingStationID',
- 'user.name', 'user.firstName', 'actionOnUser.name', 'actionOnUser.firstName', 'hasDetailedMessages', 'method', 'module',
- ]);
- return loggings;
+ },
+ authorizationSitesFilter.projectFields);
+ // Assign projected fields
+ if (authorizationSitesFilter.projectFields) {
+ logs.projectFields = authorizationSitesFilter.projectFields;
+ }
+ // Add Auth flags
+ AuthorizationService.addLogsAuthorizations(req.tenant, req.user, logs as LogDataResult, authorizationSitesFilter);
+ return logs;
}
}

MODIFY
src/server/rest/v1/service/UserService.ts
src/server/rest/v1/service/UserService.ts
@@ -733,9 +733,12 @@ export default class UserService {
if (!authorizationUsersFilters.authorized) {
return Constants.DB_EMPTY_DATA_RESULT;
}
- // Get Tag IDs from Visual IDs
+ // Optimization: Get Tag IDs from Visual IDs
if (filteredRequest.VisualTagID) {
- const tagIDs = await TagStorage.getTags(req.tenant, { visualIDs: filteredRequest.VisualTagID.split('|') }, Constants.DB_PARAMS_MAX_LIMIT, ['userID']);
+ const tagIDs = await TagStorage.getTags(req.tenant, {
+ visualIDs: filteredRequest.VisualTagID.split('|')
+ },
+ Constants.DB_PARAMS_MAX_LIMIT, ['userID']);
if (!Utils.isEmptyArray(tagIDs.result)) {
const userIDs = _.uniq(tagIDs.result.map((tag) => tag.userID));
filteredRequest.UserID = userIDs.join('|');

MODIFY
src/server/rest/v1/service/UtilsService.ts
src/server/rest/v1/service/UtilsService.ts
@@ -21,7 +21,9 @@ import Constants from '../../../../utils/Constants';
import Cypher from '../../../../utils/Cypher';
import { DataResult } from '../../../../types/DataResult';
import { EntityDataType } from '../../../../types/GlobalType';
+import { Log } from '../../../../types/Log';
import Logging from '../../../../utils/Logging';
+import LoggingStorage from '../../../../storage/mongodb/LoggingStorage';
import OCPIEndpoint from '../../../../types/ocpi/OCPIEndpoint';
import OICPEndpoint from '../../../../types/oicp/OICPEndpoint';
import PDFDocument from 'pdfkit';
@@ -287,6 +289,56 @@ export default class UtilsService {
return site;
}
+ public static async checkAndGetLogAuthorization(tenant: Tenant, userToken: UserToken, logID: string, authAction: Action,
+ action: ServerAction, entityData?: EntityDataType, additionalFilters: Record<string, any> = {}, applyProjectFields = false): Promise<Log> {
+ // Check mandatory fields
+ UtilsService.assertIdIsProvided(action, logID, MODULE_NAME, 'checkAndGetLogAuthorization', userToken);
+ // Get dynamic auth
+ const authorizationFilter = await AuthorizationService.checkAndGetLoggingAuthorizations(
+ tenant, userToken, { ID: logID }, authAction, entityData);
+ if (!authorizationFilter.authorized) {
+ throw new AppAuthError({
+ errorCode: HTTPAuthError.FORBIDDEN,
+ user: userToken,
+ action: authAction, entity: Entity.LOGGING,
+ module: MODULE_NAME, method: 'checkAndGetLogAuthorization',
+ value: logID
+ });
+ }
+ // Get Log
+ const log = await LoggingStorage.getLog(tenant, logID,
+ {
+ ...additionalFilters,
+ ...authorizationFilter.filters,
+ },
+ applyProjectFields ? authorizationFilter.projectFields : null
+ );
+ UtilsService.assertObjectExists(action, log, `Log ID '${logID}' does not exist`,
+ MODULE_NAME, 'checkAndGetLogAuthorization', userToken);
+ // Assign projected fields
+ if (authorizationFilter.projectFields) {
+ log.projectFields = authorizationFilter.projectFields;
+ }
+ // Assign Metadata
+ if (authorizationFilter.metadata) {
+ log.metadata = authorizationFilter.metadata;
+ }
+ // Add actions
+ AuthorizationService.addLogAuthorizations(tenant, userToken, log, authorizationFilter);
+ // Check
+ const authorized = AuthorizationService.canPerformAction(log, authAction);
+ if (!authorized) {
+ throw new AppAuthError({
+ errorCode: HTTPAuthError.FORBIDDEN,
+ user: userToken,
+ action: authAction, entity: Entity.LOGGING,
+ module: MODULE_NAME, method: 'checkAndGetLogAuthorization',
+ value: logID,
+ });
+ }
+ return log;
+ }
+
public static async checkUserSitesAuthorization(tenant: Tenant, userToken: UserToken, user: User, siteIDs: string[],
action: ServerAction, additionalFilters: Record<string, any> = {}, applyProjectFields = false): Promise<Site[]> {
// Check mandatory fields

MODIFY
src/storage/mongodb/LoggingStorage.ts
src/storage/mongodb/LoggingStorage.ts
@@ -55,16 +55,18 @@ export default class LoggingStorage {
}
}
- public static async getLog(tenant: Tenant, id: string = Constants.UNKNOWN_OBJECT_ID, projectFields: string[]): Promise<Log> {
+ public static async getLog(tenant: Tenant, id: string = Constants.UNKNOWN_OBJECT_ID,
+ params: { siteIDs?: string[]; } = {}, projectFields: string[]): Promise<Log> {
const logsMDB = await LoggingStorage.getLogs(tenant, {
- logIDs: [id]
+ logIDs: [id],
+ siteIDs: params.siteIDs,
}, Constants.DB_PARAMS_SINGLE_RECORD, projectFields);
return logsMDB.count === 1 ? logsMDB.result[0] : null;
}
public static async getLogs(tenant: Tenant, params: {
startDateTime?: Date; endDateTime?: Date; levels?: string[]; sources?: string[]; type?: string; actions?: string[];
- hosts?: string[]; userIDs?: string[]; chargingStationIDs?: string[]; search?: string; logIDs?: string[];
+ hosts?: string[]; userIDs?: string[]; siteIDs?: string[]; chargingStationIDs?: string[]; search?: string; logIDs?: string[];
} = {}, dbParams: DbParams, projectFields: string[]): Promise<DataResult<Log>> {
// Check Tenant
DatabaseUtils.checkTenantObject(tenant);
@@ -118,6 +120,12 @@ export default class LoggingStorage {
if (params.hosts && params.hosts.length > 0) {
filters.host = { $in: params.hosts };
}
+ // Site
+ if (!Utils.isEmptyArray(params.siteIDs)) {
+ filters.siteID = {
+ $in: params.siteIDs.map((siteID) => DatabaseUtils.convertToObjectID(siteID))
+ };
+ }
// User
if (!Utils.isEmptyArray(params.userIDs)) {
filters.$or = [

MODIFY
src/storage/mongodb/SiteStorage.ts
src/storage/mongodb/SiteStorage.ts
-import Site, { SiteUser } from '../../types/Site';
-import User, { UserSite } from '../../types/User';
import global, { DatabaseCount, FilterParams, Image } from '../../types/GlobalType';
import ChargingStationStorage from './ChargingStationStorage';
@@ -10,8 +8,10 @@ import DatabaseUtils from './DatabaseUtils';
import DbParams from '../../types/database/DbParams';
import Logging from '../../utils/Logging';
import { ObjectId } from 'mongodb';
+import Site from '../../types/Site';
import SiteAreaStorage from './SiteAreaStorage';
import Tenant from '../../types/Tenant';
+import { UserSite } from '../../types/User';
import Utils from '../../utils/Utils';
const MODULE_NAME = 'SiteStorage';

MODIFY
src/types/DataResult.ts
src/types/DataResult.ts
@@ -3,6 +3,7 @@ import Transaction, { TransactionStats } from './Transaction';
import { AuthorizationDefinitionFieldMetadata } from './Authorization';
import Company from './Company';
+import { Log } from './Log';
import Site from './Site';
import SiteArea from './SiteArea';
import Tag from './Tag';
@@ -28,6 +29,8 @@ export interface SiteDataResult extends DataResult<Site>{
canUnassignUsers: boolean;
}
+export type LogDataResult = DataResult<Log>;
+
export interface SiteAreaDataResult extends DataResult<SiteArea> {
canCreate: boolean;
}

MODIFY
src/types/Log.ts
src/types/Log.ts
import { ServerAction, ServerType } from './Server';
+import { AuthorizationActions } from './Authorization';
import User from './User';
import UserToken from './UserToken';
-export interface Log {
+export interface Log extends AuthorizationActions {
tenantID: string;
id?: string;
level?: LogLevel;

MODIFY
src/types/requests/HttpLoggingRequest.ts
src/types/requests/HttpLoggingRequest.ts
@@ -17,5 +17,6 @@ export interface HttpLogsRequest extends HttpDatabaseRequest {
Type: LogType;
Action: string;
UserID: string;
+ SiteID: string;
ChargingStationID: string;
}

MODIFY
src/utils/Logging.ts
src/utils/Logging.ts
@@ -2,6 +2,7 @@ import { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
import { Log, LogLevel, LogType } from '../types/Log';
import { NextFunction, Request, Response } from 'express';
import PerformanceRecord, { PerformanceRecordGroup, PerformanceTracingData } from '../types/Performance';
+import { ServerAction, ServerType } from '../types/Server';
import global, { ActionsResponse } from '../types/GlobalType';
import AppAuthError from '../exception/AppAuthError';
@@ -17,7 +18,6 @@ import { OCPIResult } from '../types/ocpi/OCPIResult';
import { OCPPStatus } from '../types/ocpp/OCPPClient';
import { OICPResult } from '../types/oicp/OICPResult';
import PerformanceStorage from '../storage/mongodb/PerformanceStorage';
-import { ServerAction } from '../types/Server';
import Tenant from '../types/Tenant';
import TenantStorage from '../storage/mongodb/TenantStorage';
import User from '../types/User';
@@ -949,8 +949,8 @@ export default class Logging {
if (!log.tenantID || log.tenantID === '') {
log.tenantID = Constants.DEFAULT_TENANT;
}
- // Force the source
- log.source = global.serverType;
+ // Source
+ log.source = global.serverType ?? ServerType.CENTRAL_SERVER;
// Log in Cloud Foundry
if (Configuration.isCloudFoundry()) {
// Bind to express app

Migrated Log endpoint to the new Authorization framework