Use of declarative auth checks in Company
MODIFY
src/authorization/Authorizations.ts
src/authorization/Authorizations.ts
@@ -541,12 +541,12 @@ export default class Authorizations {
return Authorizations.canPerformAction(loggedUser, Entity.SITE_AREA, Action.DELETE);
}
- public static async canListCompanies(loggedUser: UserToken): Promise<AuthorizationResult> {
- return Authorizations.can(loggedUser, Entity.COMPANIES, Action.LIST);
+ public static async canListCompanies(loggedUser: UserToken, authContext?: AuthorizationContext): Promise<AuthorizationResult> {
+ return Authorizations.can(loggedUser, Entity.COMPANIES, Action.LIST, authContext);
}
- public static async canReadCompany(loggedUser: UserToken): Promise<AuthorizationResult> {
- return Authorizations.can(loggedUser, Entity.COMPANY, Action.READ);
+ public static async canReadCompany(loggedUser: UserToken, authContext?: AuthorizationContext): Promise<AuthorizationResult> {
+ return Authorizations.can(loggedUser, Entity.COMPANY, Action.READ, authContext);
}
public static async canCreateCompany(loggedUser: UserToken): Promise<boolean> {

MODIFY
src/authorization/AuthorizationsDefinition.ts
src/authorization/AuthorizationsDefinition.ts
-import { Action, AuthorizationDefinition, AuthorizationResult, Entity } from '../types/Authorization';
+import { AccessControl, IDictionary, IFunctionCondition } from 'role-acl';
+import { Action, AuthorizationContext, AuthorizationDefinition, AuthorizationResult, Entity } from '../types/Authorization';
-import { AccessControl } from 'role-acl';
import BackendError from '../exception/BackendError';
import Constants from '../utils/Constants';
@@ -28,7 +28,7 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
{ resource: Entity.USER, action: [Action.CREATE, Action.READ, Action.UPDATE, Action.SYNCHRONIZE_BILLING_USER], attributes: ['*'] },
{
resource: Entity.USER, action: Action.DELETE, attributes: ['*'],
- condition: { Fn: 'NOT_EQUALS', args: { 'user': '$.owner' } }
+ condition: { Fn: 'NOT_EQUALS', args: { 'user': '$.owner' } },
},
{ resource: Entity.COMPANIES, action: Action.LIST, attributes: [
'id', 'name', 'address', 'logo', 'issuer', 'distanceMeters', 'createdOn', 'lastChangedOn',
@@ -119,13 +119,26 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
{ resource: Entity.SETTING, action: Action.READ, attributes: ['*'] },
{ resource: Entity.ASSETS, action: Action.LIST, attributes: ['*'] },
{ resource: Entity.ASSET, action: Action.READ, attributes: ['*'] },
- { resource: Entity.COMPANIES, action: Action.LIST, attributes: [
- 'id', 'name', 'address', 'logo', 'issuer', 'distanceMeters', 'createdOn', 'lastChangedOn'] },
{ resource: Entity.CAR_CATALOGS, action: Action.LIST, attributes: ['*'] },
{ resource: Entity.CAR_CATALOG, action: Action.READ, attributes: ['*'] },
{ resource: Entity.CAR, action: [Action.CREATE, Action.READ, Action.UPDATE, Action.DELETE], attributes: ['*'] },
{ resource: Entity.CARS, action: Action.LIST, attributes: ['*'] },
- { resource: Entity.COMPANY, action: Action.READ, attributes: ['id', 'name', 'issuer', 'logo', 'address'] },
+ {
+ resource: Entity.COMPANIES, action: Action.LIST,
+ condition: {
+ Fn: 'custom:dynamicAuthorizationFilters',
+ args: { filters: ['AssignedSitesCompanies'] }
+ },
+ attributes: ['id', 'name', 'address', 'logo', 'issuer', 'distanceMeters', 'createdOn', 'lastChangedOn']
+ },
+ {
+ resource: Entity.COMPANY, action: Action.READ,
+ condition: {
+ Fn: 'custom:dynamicAuthorizationFilters',
+ args: { filters: ['AssignedSitesCompanies'] }
+ },
+ attributes: ['id', 'name', 'issuer', 'logo', 'address']
+ },
// -----------------------------------------------------------------------------------------------
// TODO - put it pack as soon as BILLING has been validated of SLF
// -----------------------------------------------------------------------------------------------
@@ -320,6 +333,18 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
},
};
+
+const AUTHORIZATION_CONDITIONS: IDictionary<IFunctionCondition> = {
+ dynamicAuthorizationFilters: (context: Record<string, any>, args: AuthorizationContext): boolean => {
+ // Pass the dynamic filters to the context
+ // Used by the caller to execute dynamic filters
+ if (context) {
+ context.filters = args.filters;
+ }
+ return true;
+ }
+};
+
const MODULE_NAME = 'AuthorizationsDefinition';
export default class AuthorizationsDefinition {
@@ -328,7 +353,7 @@ export default class AuthorizationsDefinition {
private constructor() {
try {
- this.accessControl = new AccessControl(AUTHORIZATION_DEFINITION);
+ this.accessControl = new AccessControl(AUTHORIZATION_DEFINITION, AUTHORIZATION_CONDITIONS);
} catch (error) {
throw new BackendError({
source: Constants.CENTRAL_SERVER,

ADD
src/authorization/DynamicAuthorizationFilter.ts
+import { AuthorizationFilter } from '../types/Authorization';
+import Tenant from '../types/Tenant';
+import UserToken from '../types/UserToken';
+
+export default interface DynamicAuthorizationFilter {
+ processFilter(tenant: Tenant, userToken: UserToken, authorizationFilters: AuthorizationFilter,
+ extraFilters: Record<string, any>): Promise<void>;
+}

ADD
src/authorization/DynamicAuthorizationFilterFactory.ts
+import AssignedSitesCompaniesDynamicAuthorizationFilter from './dynamic-filters/AssignedSitesCompaniesDynamicAuthorizationFilter';
+import DynamicAuthorizationFilter from './DynamicAuthorizationFilter';
+import { DynamicAuthorizationFilters } from '../types/Authorization';
+
+export default class DynamicAuthorizationFilterFactory {
+ public static getDynamicFilter(filter: DynamicAuthorizationFilters): DynamicAuthorizationFilter {
+ switch (filter) {
+ case DynamicAuthorizationFilters.ASSIGNED_SITES_COMPANIES:
+ return new AssignedSitesCompaniesDynamicAuthorizationFilter();
+ }
+ }
+}

ADD
src/authorization/dynamic-filters/AssignedSitesCompaniesDynamicAuthorizationFilter.ts
+import { AuthorizationFilter } from '../../types/Authorization';
+import AuthorizationService from '../../server/rest/v1/service/AuthorizationService';
+import DynamicAuthorizationFilter from '../DynamicAuthorizationFilter';
+import Tenant from '../../types/Tenant';
+import TenantComponents from '../../types/TenantComponents';
+import UserToken from '../../types/UserToken';
+import Utils from '../../utils/Utils';
+
+export default class AssignedSitesCompaniesDynamicAuthorizationFilter implements DynamicAuthorizationFilter {
+ public async processFilter(tenant: Tenant, userToken: UserToken,
+ authorizationFilters: AuthorizationFilter, extraFilters: Record<string, any>): Promise<void> {
+ if (Utils.isTenantComponentActive(tenant, TenantComponents.ORGANIZATION)) {
+ // Get Company IDs from Site Admin flag
+ const companyIDs = await AuthorizationService.getAssignedSitesCompanyIDs(tenant.id, userToken);
+ if (!Utils.isEmptyArray(companyIDs)) {
+ // Force the filter
+ authorizationFilters.filters.companyIDs = companyIDs;
+ // Check if filter is provided
+ if (Utils.objectHasProperty(extraFilters, 'CompanyID') &&
+ !Utils.isNullOrUndefined(extraFilters['CompanyID'])) {
+ const filteredCompanyIDs: string[] = extraFilters['CompanyID'].split('|');
+ // Override
+ authorizationFilters.filters.companyIDs = filteredCompanyIDs.filter(
+ (companyID) => authorizationFilters.filters.companyIDs.includes(companyID));
+ }
+ }
+ if (!Utils.isEmptyArray(authorizationFilters.filters.companyIDs)) {
+ authorizationFilters.authorized = true;
+ }
+ } else {
+ authorizationFilters.authorized = true;
+ }
+ }
+}

MODIFY
src/server/rest/v1/service/AuthorizationService.ts
src/server/rest/v1/service/AuthorizationService.ts
-import { Action, AuthorizationActions, AuthorizationFilter, Entity } from '../../../../types/Authorization';
+import { Action, AuthorizationActions, AuthorizationContext, AuthorizationFilter, Entity } from '../../../../types/Authorization';
import { CompanyDataResult, SiteAreaDataResult, SiteDataResult } from '../../../../types/DataResult';
import { HttpAssignAssetsToSiteAreaRequest, HttpAssignChargingStationToSiteAreaRequest, HttpSiteAreaRequest, HttpSiteAreasRequest } from '../../../../types/requests/HttpSiteAreaRequest';
import { HttpCompaniesRequest, HttpCompanyRequest } from '../../../../types/requests/HttpCompanyRequest';
@@ -12,6 +12,7 @@ import Authorizations from '../../../../authorization/Authorizations';
import ChargingStationStorage from '../../../../storage/mongodb/ChargingStationStorage';
import Company from '../../../../types/Company';
import Constants from '../../../../utils/Constants';
+import DynamicAuthorizationFilterFactory from '../../../../authorization/DynamicAuthorizationFilterFactory';
import { HTTPAuthError } from '../../../../types/HTTPError';
import { HttpAssetsRequest } from '../../../../types/requests/HttpAssetRequest';
import HttpByIDRequest from '../../../../types/requests/HttpByIDRequest';
@@ -26,6 +27,7 @@ import TenantComponents from '../../../../types/TenantComponents';
import UserStorage from '../../../../storage/mongodb/UserStorage';
import UserToken from '../../../../types/UserToken';
import Utils from '../../../../utils/Utils';
+import UtilsService from './UtilsService';
import _ from 'lodash';
const MODULE_NAME = 'AuthorizationService';
@@ -55,11 +57,9 @@ export default class AuthorizationService {
],
authorized: userToken.role === UserRole.ADMIN,
};
- // Check Projection
- if (!Utils.isEmptyArray(filteredRequest.ProjectFields)) {
- authorizationFilters.projectFields = authorizationFilters.projectFields.filter(
- (projectField) => filteredRequest.ProjectFields.includes(projectField));
- }
+ // Filter projected fields
+ authorizationFilters.projectFields = AuthorizationService.filterProjectFields(
+ authorizationFilters.projectFields, filteredRequest.ProjectFields);
// Not an Admin user?
if (userToken.role !== UserRole.ADMIN) {
// Check assigned Site
@@ -122,11 +122,9 @@ export default class AuthorizationService {
authorizationFilters.projectFields.push(
'createdBy.name', 'createdBy.firstName', 'lastChangedBy.name', 'lastChangedBy.firstName');
}
- // Check Projection
- if (!Utils.isEmptyArray(filteredRequest.ProjectFields)) {
- authorizationFilters.projectFields = authorizationFilters.projectFields.filter(
- (projectField) => filteredRequest.ProjectFields.includes(projectField));
- }
+ // Filter projected fields
+ authorizationFilters.projectFields = AuthorizationService.filterProjectFields(
+ authorizationFilters.projectFields, filteredRequest.ProjectFields);
// Not an Admin?
if (userToken.role !== UserRole.ADMIN) {
// Check assigned Sites
@@ -145,11 +143,9 @@ export default class AuthorizationService {
],
authorized: userToken.role === UserRole.ADMIN,
};
- // Check projection
- if (!Utils.isEmptyArray(filteredRequest.ProjectFields)) {
- authorizationFilters.projectFields = authorizationFilters.projectFields.filter(
- (projectField) => filteredRequest.ProjectFields.includes(projectField));
- }
+ // Filter projected fields
+ authorizationFilters.projectFields = AuthorizationService.filterProjectFields(
+ authorizationFilters.projectFields, filteredRequest.ProjectFields);
// Handle Sites
await AuthorizationService.checkAssignedSiteAdmins(
tenant, userToken, filteredRequest, authorizationFilters);
@@ -165,11 +161,9 @@ export default class AuthorizationService {
],
authorized: userToken.role === UserRole.ADMIN,
};
- // Check projection
- if (!Utils.isEmptyArray(filteredRequest.ProjectFields)) {
- authorizationFilters.projectFields = authorizationFilters.projectFields.filter(
- (projectField) => filteredRequest.ProjectFields.includes(projectField));
- }
+ // Filter projected fields
+ authorizationFilters.projectFields = AuthorizationService.filterProjectFields(
+ authorizationFilters.projectFields, filteredRequest.ProjectFields);
// Handle Sites
await AuthorizationService.checkAssignedSiteAdmins(
tenant, userToken, null, authorizationFilters);
@@ -322,11 +316,9 @@ export default class AuthorizationService {
],
authorized: userToken.role === UserRole.ADMIN,
};
- // Check projection
- if (!Utils.isEmptyArray(filteredRequest.ProjectFields)) {
- authorizationFilters.projectFields = authorizationFilters.projectFields.filter(
- (projectField) => filteredRequest.ProjectFields.includes(projectField));
- }
+ // Filter projected fields
+ authorizationFilters.projectFields = AuthorizationService.filterProjectFields(
+ authorizationFilters.projectFields, filteredRequest.ProjectFields);
// Get authorization filters from users
const usersAuthorizationFilters = await AuthorizationService.checkAndGetUsersAuthorizationFilters(
tenant, userToken, filteredRequest);
@@ -361,11 +353,9 @@ export default class AuthorizationService {
],
authorized: userToken.role === UserRole.ADMIN || userToken.role === UserRole.SUPER_ADMIN,
};
- // Check projection
- if (!Utils.isEmptyArray(filteredRequest.ProjectFields)) {
- authorizationFilters.projectFields = authorizationFilters.projectFields.filter(
- (projectField) => filteredRequest.ProjectFields.includes(projectField));
- }
+ // Filter projected fields
+ authorizationFilters.projectFields = AuthorizationService.filterProjectFields(
+ authorizationFilters.projectFields, filteredRequest.ProjectFields);
// Handle Sites
await AuthorizationService.checkAssignedSiteAdmins(
tenant, userToken, filteredRequest, authorizationFilters);
@@ -382,11 +372,9 @@ export default class AuthorizationService {
],
authorized: userToken.role === UserRole.ADMIN,
};
- // Check projection
- if (!Utils.isEmptyArray(filteredRequest.ProjectFields)) {
- authorizationFilters.projectFields = authorizationFilters.projectFields.filter(
- (projectField) => filteredRequest.ProjectFields.includes(projectField));
- }
+ // Filter projected fields
+ authorizationFilters.projectFields = AuthorizationService.filterProjectFields(
+ authorizationFilters.projectFields, filteredRequest.ProjectFields);
// Handle Sites
await AuthorizationService.checkAssignedSites(
tenant, userToken, filteredRequest, authorizationFilters);
@@ -403,11 +391,9 @@ export default class AuthorizationService {
],
authorized: userToken.role === UserRole.ADMIN || userToken.role === UserRole.SUPER_ADMIN,
};
- // Check projection
- if (!Utils.isEmptyArray(filteredRequest.ProjectFields)) {
- authorizationFilters.projectFields = authorizationFilters.projectFields.filter(
- (projectField) => filteredRequest.ProjectFields.includes(projectField));
- }
+ // Filter projected fields
+ authorizationFilters.projectFields = AuthorizationService.filterProjectFields(
+ authorizationFilters.projectFields, filteredRequest.ProjectFields);
// Handle Sites
await AuthorizationService.checkAssignedSiteAdmins(
tenant, userToken, null, authorizationFilters);
@@ -428,11 +414,9 @@ export default class AuthorizationService {
authorizationFilters.projectFields.push('userID', 'user.id', 'user.name', 'user.firstName', 'user.email',
'createdBy.name', 'createdBy.firstName', 'lastChangedBy.name', 'lastChangedBy.firstName');
}
- // Check projection
- if (!Utils.isEmptyArray(filteredRequest.ProjectFields)) {
- authorizationFilters.projectFields = authorizationFilters.projectFields.filter(
- (projectField) => filteredRequest.ProjectFields.includes(projectField));
- }
+ // Filter projected fields
+ authorizationFilters.projectFields = AuthorizationService.filterProjectFields(
+ authorizationFilters.projectFields, filteredRequest.ProjectFields);
// Handle Sites
await AuthorizationService.checkAssignedSiteAdmins(
tenant, userToken, null, authorizationFilters);
@@ -446,11 +430,9 @@ export default class AuthorizationService {
projectFields: ['id', 'userID', 'issuer', 'active', 'description', 'default', 'deleted', 'user.id', 'user.name', 'user.firstName', 'user.email'],
authorized: userToken.role === UserRole.ADMIN || userToken.role === UserRole.SUPER_ADMIN,
};
- // Check projection
- if (!Utils.isEmptyArray(filteredRequest.ProjectFields)) {
- authorizationFilters.projectFields = authorizationFilters.projectFields.filter(
- (projectField) => filteredRequest.ProjectFields.includes(projectField));
- }
+ // Filter projected fields
+ authorizationFilters.projectFields = AuthorizationService.filterProjectFields(
+ authorizationFilters.projectFields, filteredRequest.ProjectFields);
// Handle Sites
await AuthorizationService.checkAssignedSiteAdmins(
tenant, userToken, null, authorizationFilters);
@@ -489,11 +471,13 @@ export default class AuthorizationService {
const authorizationFilters: AuthorizationFilter = {
filters: {},
projectFields: [],
- authorized: userToken.role === UserRole.ADMIN,
+ authorized: false,
};
// Check static auth
- const authResult = await Authorizations.canListCompanies(userToken);
- if (!authResult.authorized) {
+ const authorizationContext: AuthorizationContext = {};
+ const authResult = await Authorizations.canListCompanies(userToken, authorizationContext);
+ authorizationFilters.authorized = authResult.authorized;
+ if (!authorizationFilters.authorized) {
throw new AppAuthError({
errorCode: HTTPAuthError.FORBIDDEN,
user: userToken,
@@ -501,36 +485,12 @@ export default class AuthorizationService {
module: MODULE_NAME, method: 'checkAndGetCompaniesAuthorizationFilters'
});
}
- authorizationFilters.projectFields = authResult.fields;
- // Check Projection
- if (!Utils.isEmptyArray(filteredRequest.ProjectFields)) {
- authorizationFilters.projectFields = authorizationFilters.projectFields.filter(
- (projectField) => filteredRequest.ProjectFields.includes(projectField));
- }
- // Not an Admin?
- if (userToken.role !== UserRole.ADMIN) {
- if (Utils.isTenantComponentActive(tenant, TenantComponents.ORGANIZATION)) {
- // Get Company IDs from Site Admin flag
- const companyIDs = await AuthorizationService.getAssignedSitesCompanyIDs(tenant.id, userToken);
- if (!Utils.isEmptyArray(companyIDs)) {
- // Force the filter
- authorizationFilters.filters.companyIDs = companyIDs;
- // Check if filter is provided
- if (Utils.objectHasProperty(filteredRequest, 'CompanyID') &&
- !Utils.isNullOrUndefined(filteredRequest['CompanyID'])) {
- const filteredCompanyIDs: string[] = filteredRequest['CompanyID'].split('|');
- // Override
- authorizationFilters.filters.companyIDs = filteredCompanyIDs.filter(
- (companyID) => authorizationFilters.filters.companyIDs.includes(companyID));
- }
- }
- if (!Utils.isEmptyArray(authorizationFilters.filters.companyIDs)) {
- authorizationFilters.authorized = true;
- }
- } else {
- authorizationFilters.authorized = true;
- }
- }
+ // Process dynamic filters
+ await AuthorizationService.processDynamicFilters(tenant, userToken, Action.LIST, Entity.COMPANIES,
+ authorizationFilters, authorizationContext, filteredRequest);
+ // Filter projected fields
+ authorizationFilters.projectFields = AuthorizationService.filterProjectFields(
+ authResult.fields, filteredRequest.ProjectFields);
return authorizationFilters;
}
@@ -539,11 +499,13 @@ export default class AuthorizationService {
const authorizationFilters: AuthorizationFilter = {
filters: {},
projectFields: [],
- authorized: userToken.role === UserRole.ADMIN,
+ authorized: false,
};
// Check static auth
- const authResult = await Authorizations.canReadCompany(userToken);
- if (!authResult.authorized) {
+ const authorizationContext: AuthorizationContext = {};
+ const authResult = await Authorizations.canReadCompany(userToken, authorizationContext);
+ authorizationFilters.authorized = authResult.authorized;
+ if (!authorizationFilters.authorized) {
throw new AppAuthError({
errorCode: HTTPAuthError.FORBIDDEN,
user: userToken,
@@ -551,20 +513,12 @@ export default class AuthorizationService {
module: MODULE_NAME, method: 'checkAndGetCompanyAuthorizationFilters',
});
}
- authorizationFilters.projectFields = authResult.fields;
- // Check projection
- if (!Utils.isEmptyArray(filteredRequest.ProjectFields)) {
- authorizationFilters.projectFields = authorizationFilters.projectFields.filter(
- (projectField) => filteredRequest.ProjectFields.includes(projectField));
- }
- // Not an Admin?
- if (userToken.role !== UserRole.ADMIN) {
- // Get Company IDs from sssigned Sites
- const companyIDs = await AuthorizationService.getAssignedSitesCompanyIDs(tenant.id, userToken);
- if (!Utils.isEmptyArray(companyIDs) && companyIDs.includes(filteredRequest.ID)) {
- authorizationFilters.authorized = true;
- }
- }
+ // Process dynamic filters
+ await AuthorizationService.processDynamicFilters(tenant, userToken, Action.LIST, Entity.COMPANIES,
+ authorizationFilters, authorizationContext, { CompanyID: filteredRequest.ID });
+ // Filter projected fields
+ authorizationFilters.projectFields = AuthorizationService.filterProjectFields(
+ authResult.fields, filteredRequest.ProjectFields);
return authorizationFilters;
}
@@ -577,11 +531,9 @@ export default class AuthorizationService {
],
authorized: userToken.role === UserRole.ADMIN,
};
- // Check projection
- if (!Utils.isEmptyArray(filteredRequest.ProjectFields)) {
- authorizationFilters.projectFields = authorizationFilters.projectFields.filter(
- (projectField) => filteredRequest.ProjectFields.includes(projectField));
- }
+ // Filter projected fields
+ authorizationFilters.projectFields = AuthorizationService.filterProjectFields(
+ authorizationFilters.projectFields, filteredRequest.ProjectFields);
// Not an Admin?
if (userToken.role !== UserRole.ADMIN) {
const siteAreaIDs = await AuthorizationService.getAssignedSiteAreaIDs(tenant.id, userToken);
@@ -601,11 +553,9 @@ export default class AuthorizationService {
],
authorized: userToken.role === UserRole.ADMIN,
};
- // Check Projection
- if (!Utils.isEmptyArray(filteredRequest.ProjectFields)) {
- authorizationFilters.projectFields = authorizationFilters.projectFields.filter(
- (projectField) => filteredRequest.ProjectFields.includes(projectField));
- }
+ // Filter projected fields
+ authorizationFilters.projectFields = AuthorizationService.filterProjectFields(
+ authorizationFilters.projectFields, filteredRequest.ProjectFields);
// Not an Admin?
if (userToken.role !== UserRole.ADMIN) {
// Get assigned SiteArea IDs
@@ -673,11 +623,9 @@ export default class AuthorizationService {
],
authorized: userToken.role === UserRole.ADMIN,
};
- // Check Projection
- if (!Utils.isEmptyArray(filteredRequest.ProjectFields)) {
- authorizationFilters.projectFields = authorizationFilters.projectFields.filter(
- (projectField) => filteredRequest.ProjectFields.includes(projectField));
- }
+ // Filter projected fields
+ authorizationFilters.projectFields = AuthorizationService.filterProjectFields(
+ authorizationFilters.projectFields, filteredRequest.ProjectFields);
// Not an Admin?
if (userToken.role !== UserRole.ADMIN) {
// Check assigned Sites
@@ -858,9 +806,40 @@ export default class AuthorizationService {
return _.uniq(_.map(siteAreas.result, 'id'));
}
- private static async getSiteAreaSiteID(tenantID: string, siteAreaID: string) {
- // Get the Site IDs of SiteArea
- const siteArea = await SiteAreaStorage.getSiteArea(tenantID, siteAreaID);
- return siteArea.siteID;
+ private static filterProjectFields(authFields: string[], httpProjectField: string): string[] {
+ let fields = authFields;
+ const httpProjectFields = UtilsService.httpFilterProjectToArray(httpProjectField);
+ if (!Utils.isEmptyArray(httpProjectFields)) {
+ fields = authFields.filter(
+ (authField) => httpProjectFields.includes(authField));
+ }
+ return fields;
+ }
+
+ private static async processDynamicFilters(tenant: Tenant, userToken: UserToken, action: Action, entity: Entity,
+ authorizationFilters: AuthorizationFilter, authorizationContext: AuthorizationContext, extraFilters?: any): Promise<void> {
+ if (!Utils.isEmptyArray(authorizationContext.filters)) {
+ for (const filter of authorizationContext.filters) {
+ // Reset to false
+ authorizationFilters.authorized = false;
+ // Get the filter
+ const dynamicFilter = DynamicAuthorizationFilterFactory.getDynamicFilter(filter);
+ if (!dynamicFilter) {
+ // Filter not found -> Not authorized (all auth filter MUST work)
+ throw new AppAuthError({
+ errorCode: HTTPAuthError.FORBIDDEN,
+ user: userToken,
+ action, entity,
+ module: MODULE_NAME, method: 'processDynamicFilters'
+ });
+ }
+ // Process the filter
+ await dynamicFilter.processFilter(tenant, userToken, authorizationFilters, extraFilters);
+ // Check
+ if (!authorizationFilters.authorized) {
+ break;
+ }
+ }
+ }
}
}

MODIFY
src/server/rest/v1/service/ChargingStationService.ts
src/server/rest/v1/service/ChargingStationService.ts
@@ -495,8 +495,9 @@ export default class ChargingStationService {
...projectFields
];
// Check projection
- if (!Utils.isEmptyArray(filteredRequest.ProjectFields)) {
- projectFields = projectFields.filter((projectField) => filteredRequest.ProjectFields.includes(projectField));
+ const httpProjectFields = UtilsService.httpFilterProjectToArray(filteredRequest.ProjectFields);
+ if (!Utils.isEmptyArray(httpProjectFields)) {
+ projectFields = projectFields.filter((projectField) => httpProjectFields.includes(projectField));
}
// Get the profiles
const chargingProfiles = await ChargingStationStorage.getChargingProfiles(req.user.tenantID,
@@ -1091,8 +1092,9 @@ export default class ChargingStationService {
'connectors.connectorId', 'connectors.status', 'connectors.type', 'connectors.power', 'connectors.errorCode',
];
// Check projection
- if (!Utils.isEmptyArray(filteredRequest.ProjectFields)) {
- projectFields = projectFields.filter((projectField) => filteredRequest.ProjectFields.includes(projectField));
+ const projectHttpFields = UtilsService.httpFilterProjectToArray(filteredRequest.ProjectFields);
+ if (!Utils.isEmptyArray(projectHttpFields)) {
+ projectFields = projectFields.filter((projectField) => projectHttpFields.includes(projectField));
}
// Get Charging Stations
const chargingStations = await ChargingStationStorage.getChargingStationsInError(req.user.tenantID,
@@ -1492,7 +1494,7 @@ export default class ChargingStationService {
];
}
// Check projection
- const httpProjectFields = UtilsService.httpFilterProjectToMongoDB(filteredRequest.ProjectFields);
+ const httpProjectFields = UtilsService.httpFilterProjectToArray(filteredRequest.ProjectFields);
if (!Utils.isEmptyArray(httpProjectFields)) {
projectFields = projectFields.filter((projectField) => httpProjectFields.includes(projectField));
}

MODIFY
src/server/rest/v1/service/TenantService.ts
src/server/rest/v1/service/TenantService.ts
@@ -126,7 +126,7 @@ export default class TenantService {
'id', 'name', 'email', 'subdomain', 'components', 'address', 'logo'
];
// Check projection
- const httpProjectFields = UtilsService.httpFilterProjectToMongoDB(filteredRequest.ProjectFields);
+ const httpProjectFields = UtilsService.httpFilterProjectToArray(filteredRequest.ProjectFields);
if (!Utils.isEmptyArray(httpProjectFields)) {
projectFields = projectFields.filter((projectField) => httpProjectFields.includes(projectField));
}
@@ -162,7 +162,7 @@ export default class TenantService {
projectFields.push('components');
}
// Check projection
- const httpProjectFields = UtilsService.httpFilterProjectToMongoDB(filteredRequest.ProjectFields);
+ const httpProjectFields = UtilsService.httpFilterProjectToArray(filteredRequest.ProjectFields);
if (!Utils.isEmptyArray(httpProjectFields)) {
projectFields = projectFields.filter((projectField) => httpProjectFields.includes(projectField));
}

MODIFY
src/server/rest/v1/service/TransactionService.ts
src/server/rest/v1/service/TransactionService.ts
@@ -1062,8 +1062,9 @@ export default class TransactionService {
extrafilters.stop = { $exists: false };
}
// Check projection
- if (!Utils.isEmptyArray(filteredRequest.ProjectFields)) {
- projectFields = projectFields.filter((projectField) => filteredRequest.ProjectFields.includes(projectField));
+ const httpProjectFields = UtilsService.httpFilterProjectToArray(filteredRequest.ProjectFields);
+ if (!Utils.isEmptyArray(httpProjectFields)) {
+ projectFields = projectFields.filter((projectField) => httpProjectFields.includes(projectField));
}
// Get the transactions
const transactions = await TransactionStorage.getTransactions(req.user.tenantID,

MODIFY
src/server/rest/v1/service/UtilsService.ts
src/server/rest/v1/service/UtilsService.ts
@@ -112,17 +112,14 @@ export default class UtilsService {
throw new AppAuthError({
errorCode: HTTPAuthError.FORBIDDEN,
user: userToken,
- action: Action.READ, entity: Entity.COMPANY,
+ action: authAction, entity: Entity.COMPANY,
module: MODULE_NAME, method: 'checkAndGetCompanyAuthorization',
value: companyID
});
}
// Get Company
const company = await CompanyStorage.getCompany(tenant.id, companyID,
- {
- ...additionalFilters,
- ...authorizationFilter.filters
- },
+ { ...additionalFilters },
applyProjectFields ? authorizationFilter.projectFields : null
);
UtilsService.assertObjectExists(action, company, `Company ID '${companyID}' does not exist`,
@@ -700,10 +697,8 @@ export default class UtilsService {
}
}
- public static httpFilterProjectToMongoDB(httpProjectFields: string): string[] {
- // Exist?
+ public static httpFilterProjectToArray(httpProjectFields: string): string[] {
if (httpProjectFields) {
- // Convert to array
return httpProjectFields.split('|');
}
}

MODIFY
src/server/rest/v1/service/security/UtilsSecurity.ts
src/server/rest/v1/service/security/UtilsSecurity.ts
@@ -51,12 +51,8 @@ export default class UtilsSecurity {
}
public static filterProject(request: any, filteredRequest: any): void {
- // Count Only?
if (Utils.objectHasProperty(request, 'ProjectFields')) {
- // Clean
- request.ProjectFields = sanitize(request.ProjectFields);
- // Convert to array
- filteredRequest.ProjectFields = request.ProjectFields.split('|');
+ filteredRequest.ProjectFields = sanitize(request.ProjectFields);
}
}
@@ -129,24 +125,5 @@ export default class UtilsSecurity {
}
return [];
}
-
- static filterCreatedAndLastChanged(filteredEntity: any, entity: any, loggedUser: UserToken): void {
- if (Utils.objectHasProperty(entity, 'createdBy') && typeof entity.createdBy === 'object' &&
- Utils.objectHasProperty(entity.createdBy, 'id') && Authorizations.canReadUser(loggedUser, entity.createdBy.id)) {
- // Build user
- filteredEntity.createdBy = Utils.buildUserFullName(entity.createdBy, false);
- }
- if (Utils.objectHasProperty(entity, 'lastChangedBy') && typeof entity.lastChangedBy === 'object' &&
- entity.lastChangedBy.id && Authorizations.canReadUser(loggedUser, entity.lastChangedBy.id)) {
- // Build user
- filteredEntity.lastChangedBy = Utils.buildUserFullName(entity.lastChangedBy, false);
- }
- if (Utils.objectHasProperty(entity, 'lastChangedOn')) {
- filteredEntity.lastChangedOn = entity.lastChangedOn;
- }
- if (Utils.objectHasProperty(entity, 'createdOn')) {
- filteredEntity.createdOn = entity.createdOn;
- }
- }
}

MODIFY
src/types/Authorization.ts
src/types/Authorization.ts
@@ -162,6 +162,7 @@ export interface AuthorizationContext {
companies?: string[];
asset?: string;
assets?: string[];
+ filters?: DynamicAuthorizationFilters[];
}
export interface AuthorizationActions {
@@ -170,3 +171,7 @@ export interface AuthorizationActions {
canUpdate?: boolean;
canDelete?: boolean;
}
+
+export enum DynamicAuthorizationFilters {
+ ASSIGNED_SITES_COMPANIES = 'AssignedSitesCompanies',
+}

MODIFY
src/types/requests/HttpDatabaseRequest.ts
src/types/requests/HttpDatabaseRequest.ts
-export default interface HttpDatabaseRequest {
+export interface HttpDatabaseProjectRequest {
+ ProjectFields?: string;
+}
+export default interface HttpDatabaseRequest extends HttpDatabaseProjectRequest {
Skip?: number;
Limit?: number;
OnlyRecordCount?: boolean;
SortFields: any;
- ProjectFields?: any;
}
-export interface HttpDatabaseProjectRequest {
- ProjectFields?: any;
-}

Use of declarative auth checks in Company