Change auth concept for handleGetCar
MODIFY
src/authorization/AuthorizationsDefinition.ts
src/authorization/AuthorizationsDefinition.ts
@@ -127,7 +127,16 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
{ resource: Entity.CONNECTION, action: [Action.CREATE, Action.READ, Action.DELETE], attributes: ['*'] },
{ 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.CAR, action: [Action.CREATE, Action.UPDATE, Action.DELETE], attributes: ['*'] },
+ {
+ resource: Entity.CAR, action: Action.READ, attributes: [
+ 'id', 'type', 'vin', 'licensePlate', 'converter', 'default', 'owner', 'createdOn', 'lastChangedOn',
+ 'carCatalogID', 'carCatalog.vehicleMake', 'carCatalog.vehicleModel', 'carCatalog.vehicleModelVersion', 'carCatalog.image',
+ 'carCatalog.chargeStandardPower', 'carCatalog.chargeStandardPhaseAmp', 'carCatalog.chargeStandardPhase',
+ 'carCatalog.chargeAlternativePower', 'carCatalog.chargeAlternativePhaseAmp', 'carCatalog.chargeAlternativePhase',
+ 'carCatalog.chargeOptionPower', 'carCatalog.chargeOptionPhaseAmp', 'carCatalog.chargeOptionPhase'
+ ]
+ },
{
resource: Entity.CARS, action: Action.LIST, attributes: [
'id', 'type', 'vin', 'licensePlate', 'converter', 'default', 'owner', 'createdOn', 'lastChangedOn',
@@ -170,7 +179,16 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
{ resource: Entity.ASSET, action: Action.READ, attributes: ['*'] },
{ 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.CAR, action: [Action.CREATE, Action.UPDATE, Action.DELETE], attributes: ['*'] },
+ {
+ resource: Entity.CAR, action: Action.READ, attributes: [
+ 'id', 'type', 'vin', 'licensePlate', 'converter', 'default', 'owner', 'createdOn', 'lastChangedOn',
+ 'carCatalogID', 'carCatalog.vehicleMake', 'carCatalog.vehicleModel', 'carCatalog.vehicleModelVersion', 'carCatalog.image',
+ 'carCatalog.chargeStandardPower', 'carCatalog.chargeStandardPhaseAmp', 'carCatalog.chargeStandardPhase',
+ 'carCatalog.chargeAlternativePower', 'carCatalog.chargeAlternativePhaseAmp', 'carCatalog.chargeAlternativePhase',
+ 'carCatalog.chargeOptionPower', 'carCatalog.chargeOptionPhaseAmp', 'carCatalog.chargeOptionPhase'
+ ]
+ },
{
resource: Entity.CARS, action: Action.LIST, attributes: [
'id', 'type', 'vin', 'licensePlate', 'converter', 'default', 'owner', 'createdOn', 'lastChangedOn',
@@ -318,7 +336,15 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
{ resource: Entity.ASSET, action: Action.READ, attributes: ['*'] },
{ resource: Entity.CAR_CATALOGS, action: Action.LIST, attributes: ['*'] },
{ resource: Entity.CAR_CATALOG, action: Action.READ, attributes: ['*'] },
- { resource: Entity.CAR, action: Action.READ, attributes: ['*'] },
+ {
+ resource: Entity.CAR, action: Action.READ, attributes: [
+ 'id', 'type', 'vin', 'licensePlate', 'converter', 'default', 'owner', 'createdOn', 'lastChangedOn',
+ 'carCatalogID', 'carCatalog.vehicleMake', 'carCatalog.vehicleModel', 'carCatalog.vehicleModelVersion', 'carCatalog.image',
+ 'carCatalog.chargeStandardPower', 'carCatalog.chargeStandardPhaseAmp', 'carCatalog.chargeStandardPhase',
+ 'carCatalog.chargeAlternativePower', 'carCatalog.chargeAlternativePhaseAmp', 'carCatalog.chargeAlternativePhase',
+ 'carCatalog.chargeOptionPower', 'carCatalog.chargeOptionPhaseAmp', 'carCatalog.chargeOptionPhase'
+ ]
+ },
{
resource: Entity.CARS, action: Action.LIST, attributes: [
'id', 'type', 'vin', 'licensePlate', 'converter', 'default', 'owner', 'createdOn', 'lastChangedOn',

MODIFY
src/server/rest/v1/service/AuthorizationService.ts
src/server/rest/v1/service/AuthorizationService.ts
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 { HttpCarByIDRequest, HttpCarsRequest } from '../../../../types/requests/HttpCarRequest';
import { HttpCompaniesRequest, HttpCompanyRequest } from '../../../../types/requests/HttpCompanyRequest';
import { HttpSiteAssignUsersRequest, HttpSiteRequest, HttpSiteUsersRequest } from '../../../../types/requests/HttpSiteRequest';
import { HttpTagsRequest, HttpUserAssignSitesRequest, HttpUserRequest, HttpUserSitesRequest, HttpUsersRequest } from '../../../../types/requests/HttpUserRequest';
@@ -16,7 +17,6 @@ import DynamicAuthorizationFactory from '../../../../authorization/DynamicAuthor
import { HTTPAuthError } from '../../../../types/HTTPError';
import { HttpAssetsRequest } from '../../../../types/requests/HttpAssetRequest';
import HttpByIDRequest from '../../../../types/requests/HttpByIDRequest';
-import { HttpCarsRequest } from '../../../../types/requests/HttpCarRequest';
import { HttpChargingStationRequest } from '../../../../types/requests/HttpChargingStationRequest';
import { ServerAction } from '../../../../types/Server';
import Site from '../../../../types/Site';
@@ -669,20 +669,32 @@ export default class AuthorizationService {
return authorizationFilters;
}
- public static async checkAndGetCarsAuthorizationFilters(tenant: Tenant, userToken: UserToken, filteredRequest: HttpCarsRequest) {
+ public static async checkAndGetCarsAuthorizationFilters(tenant: Tenant, userToken: UserToken, filteredRequest: HttpCarsRequest): Promise<AuthorizationFilter> {
const authorizationFilters: AuthorizationFilter = {
filters: {},
dataSources: new Map(),
projectFields: [ ],
authorized: false
};
-
// Check static & dynamic authorization
await this.canPerformAuthorizationAction(
tenant, userToken, Entity.CARS, Action.LIST, authorizationFilters, filteredRequest);
return authorizationFilters;
}
+ public static async checkAndGetCarAuthorizationFilter(tenant: Tenant, userToken: UserToken, filteredRequest: HttpCarByIDRequest,
+ action: Action): Promise<AuthorizationFilter> {
+ const authorizationFilters: AuthorizationFilter = {
+ filters: {},
+ dataSources: new Map(),
+ projectFields: [ ],
+ authorized: false
+ };
+ // Check static & dynamic authorization
+ await this.canPerformAuthorizationAction(tenant, userToken, Entity.CAR, action, authorizationFilters, filteredRequest);
+ return authorizationFilters;
+ }
+
public static async getSiteAdminSiteIDs(tenantID: string, userToken: UserToken): Promise<string[]> {
// Get the Sites where the user is Site Admin
const userSites = await UserStorage.getUserSites(tenantID,

MODIFY
src/server/rest/v1/service/CarService.ts
src/server/rest/v1/service/CarService.ts
@@ -459,37 +459,13 @@ export default class CarService {
// Check if component is active
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.CAR, Action.READ, Entity.CAR,
MODULE_NAME, 'handleGetCar');
- // Check auth
- if (!await Authorizations.canReadCar(req.user)) {
- throw new AppAuthError({
- errorCode: HTTPAuthError.FORBIDDEN,
- user: req.user,
- action: Action.READ, entity: Entity.CAR,
- module: MODULE_NAME, method: 'handleGetCar'
- });
- }
+ // Filter
const filteredRequest = CarSecurity.filterCarRequest(req.query);
- UtilsService.assertIdIsProvided(action, filteredRequest.ID, MODULE_NAME, 'handleGetCar', req.user);
- // Check User
- let userProject: string[] = [];
- if (await Authorizations.canReadUser(req.user)) {
- userProject = [ 'createdBy.name', 'createdBy.firstName', 'lastChangedBy.name', 'lastChangedBy.firstName', 'carUsers.id',
- 'carUsers.user.id', 'carUsers.user.name', 'carUsers.user.firstName', 'carUsers.user.email', 'carUsers.default', 'carUsers.owner'
- ];
- }
- // Get the car
- const car = await CarStorage.getCar(req.user.tenantID, filteredRequest.ID, {
+ // Check and get Car
+ const car = await UtilsService.checkAndGetCarAuthorization(req.tenant, req.user, filteredRequest.ID, Action.READ, action, {
withUsers: true ,
- userIDs: Authorizations.isBasic(req.user) ? [req.user.id] : null
- },
- [
- 'id', 'type', 'vin', 'licensePlate', 'converter', 'default', 'owner', 'createdOn', 'lastChangedOn',
- 'carCatalogID', 'carCatalog.vehicleMake', 'carCatalog.vehicleModel', 'carCatalog.vehicleModelVersion', 'carCatalog.image',
- 'carCatalog.chargeStandardPower', 'carCatalog.chargeStandardPhaseAmp', 'carCatalog.chargeStandardPhase',
- 'carCatalog.chargeAlternativePower', 'carCatalog.chargeAlternativePhaseAmp', 'carCatalog.chargeAlternativePhase',
- 'carCatalog.chargeOptionPower', 'carCatalog.chargeOptionPhaseAmp', 'carCatalog.chargeOptionPhase',
- ...userProject
- ]);
+ userIDs: Authorizations.isBasic(req.user) ? [req.user.id] : null // todo: dynamic filter?
+ }, true);
// Return
res.json(car);
next();

MODIFY
src/server/rest/v1/service/UtilsService.ts
src/server/rest/v1/service/UtilsService.ts
@@ -11,7 +11,7 @@ import Asset from '../../../../types/Asset';
import AssetStorage from '../../../../storage/mongodb/AssetStorage';
import AuthorizationService from './AuthorizationService';
import Authorizations from '../../../../authorization/Authorizations';
-import BackendError from '../../../../exception/BackendError';
+import CarStorage from '../../../../storage/mongodb/CarStorage';
import { ChargingProfile } from '../../../../types/ChargingProfile';
import ChargingStationStorage from '../../../../storage/mongodb/ChargingStationStorage';
import Company from '../../../../types/Company';
@@ -25,7 +25,6 @@ import OCPIEndpoint from '../../../../types/ocpi/OCPIEndpoint';
import OICPEndpoint from '../../../../types/oicp/OICPEndpoint';
import PDFDocument from 'pdfkit';
import { ServerAction } from '../../../../types/Server';
-import { Setting } from '../../../../types/Setting';
import Site from '../../../../types/Site';
import SiteArea from '../../../../types/SiteArea';
import SiteAreaStorage from '../../../../storage/mongodb/SiteAreaStorage';
@@ -486,6 +485,46 @@ export default class UtilsService {
return siteArea;
}
+ public static async checkAndGetCarAuthorization(tenant: Tenant, userToken: UserToken, carID: string, authAction: Action,
+ action: ServerAction, additionalFilters: Record<string, any>, applyProjectFields = false): Promise<Car> {
+ // Check mandatory fields
+ UtilsService.assertIdIsProvided(action, carID, MODULE_NAME, 'handleGetCar', userToken);
+ // Get dynamic auth
+ const authorizationFilter = await AuthorizationService.checkAndGetCarAuthorizationFilter(
+ tenant, userToken, { ID: carID }, Action.READ);
+ if (!authorizationFilter.authorized) {
+ throw new AppAuthError({
+ errorCode: HTTPAuthError.FORBIDDEN,
+ user: userToken,
+ action: Action.READ, entity: Entity.CAR,
+ module: MODULE_NAME, method: 'checkAndGetCarAuthorization',
+ value: carID
+ });
+ }
+ // Check User
+ let userProject: string[] = [];
+ if (await Authorizations.canReadUser(userToken)) {
+ userProject = [ 'createdBy.name', 'createdBy.firstName', 'lastChangedBy.name', 'lastChangedBy.firstName', 'carUsers.id',
+ 'carUsers.user.id', 'carUsers.user.name', 'carUsers.user.firstName', 'carUsers.user.email', 'carUsers.default', 'carUsers.owner'
+ ];
+ // todo: is it ok like this?
+ authorizationFilter.projectFields = authorizationFilter.projectFields.concat(userProject);
+ }
+ // Get Car
+ const car = await CarStorage.getCar(userToken.tenantID, carID,
+ {
+ ...additionalFilters,
+ ...authorizationFilter.filters
+ },
+ applyProjectFields ? authorizationFilter.projectFields : null
+ );
+ // Check it exists
+ UtilsService.assertObjectExists(action, car, `Car ID '${carID}' does not exist`,
+ MODULE_NAME, 'checkAndGetCarAuthorization', userToken);
+ // Return
+ return car;
+ }
+
public static sendEmptyDataResult(res: Response, next: NextFunction): void {
res.json(Constants.DB_EMPTY_DATA_RESULT);
next();

Change auth concept for handleGetCar