change tagID to VisualTagID
MODIFY
src/assets/server/rest/v1/schemas/chargingstation/chargingstation-action.json
src/assets/server/rest/v1/schemas/chargingstation/chargingstation-action.json
"tagID": {
"$ref": "tag.json#/definitions/id"
},
+ "visualTagID": {
+ "type": "string",
+ "sanitize": "mongo"
+ },
"location": {
"type": "string",
"sanitize": "mongo"

MODIFY
src/authorization/AuthorizationsDefinition.ts
src/authorization/AuthorizationsDefinition.ts
@@ -87,7 +87,7 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
{ resource: Entity.TAGS, action: [Action.IMPORT, Action.EXPORT], attributes: ['*'] },
{
resource: Entity.TAG, action: Action.READ, attributes: [
- 'id', 'userID', 'issuer', 'active', 'description', 'visualID', 'default', 'user.id', 'user.name', 'user.firstName', 'user.email'
+ 'id', 'userID', 'issuer', 'active', 'description', 'visualID', 'default', 'user.id', 'user.name', 'user.firstName', 'user.email', 'user.issuer'
]
},
{ resource: Entity.TAG, action: [Action.CREATE, Action.UPDATE, Action.DELETE], attributes: ['*'] },
@@ -433,7 +433,7 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
},
{
resource: Entity.TAG, action: Action.READ, attributes: [
- 'id', 'userID', 'issuer', 'active', 'description', 'visualID', 'default', 'user.id', 'user.name', 'user.firstName', 'user.email'
+ 'userID', 'issuer', 'active', 'description', 'visualID', 'default', 'user.id', 'user.name', 'user.firstName', 'user.email', 'user.issuer'
],
condition: {
Fn: 'custom:dynamicAuthorizationFilters',
@@ -647,14 +647,16 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
'user.id', 'user.name', 'user.firstName', 'user.email', 'user.role', 'siteAdmin', 'siteOwner', 'siteID'
]
},
- { resource: Entity.USERS_SITES, action: Action.UNASSIGN,
+ {
+ resource: Entity.USERS_SITES, action: Action.UNASSIGN,
condition: {
Fn: 'custom:dynamicAuthorizationFilters',
args: { filters: ['SitesAdmin'] }
},
attributes: ['*']
},
- { resource: Entity.SITE, action: [Action.UPDATE],
+ {
+ resource: Entity.SITE, action: [Action.UPDATE],
condition: {
Fn: 'custom:dynamicAuthorizationFilters',
args: { filters: ['SitesAdmin'] }

MODIFY
src/server/rest/v1/service/ChargingStationService.ts
src/server/rest/v1/service/ChargingStationService.ts
@@ -33,8 +33,11 @@ import OICPUtils from '../../../oicp/OICPUtils';
import { ServerAction } from '../../../../types/Server';
import SiteArea from '../../../../types/SiteArea';
import SiteAreaStorage from '../../../../storage/mongodb/SiteAreaStorage';
+import SiteStorage from '../../../../storage/mongodb/SiteStorage';
import SmartChargingFactory from '../../../../integration/smart-charging/SmartChargingFactory';
import { StatusCodes } from 'http-status-codes';
+import Tag from '../../../../types/Tag';
+import TagStorage from '../../../../storage/mongodb/TagStorage';
import Tenant from '../../../../types/Tenant';
import TenantComponents from '../../../../types/TenantComponents';
import TransactionStorage from '../../../../storage/mongodb/TransactionStorage';
@@ -1190,7 +1193,7 @@ export default class ChargingStationService {
// Remote Start Transaction
} else if (command === Command.REMOTE_START_TRANSACTION) {
// Check Tag ID
- if (!filteredRequest.args || !filteredRequest.args.tagID) {
+ if (!filteredRequest.args || !filteredRequest.args.userID || !filteredRequest.args.visualTagID) {
throw new AppError({
source: Constants.CENTRAL_SERVER,
errorCode: HTTPError.USER_NO_BADGE_ERROR,
@@ -1201,9 +1204,12 @@ export default class ChargingStationService {
action: action,
});
}
- // Check if user is authorized
- const user = await Authorizations.isAuthorizedToStartTransaction(req.tenant, chargingStation, filteredRequest.args.tagID,
- ServerAction.CHARGING_STATION_REMOTE_START_TRANSACTION, Action.REMOTE_START_TRANSACTION);
+ // Check and Get User
+ const user = await UtilsService.checkAndGetUserAuthorization(
+ req.tenant, req.user, filteredRequest.args.userID, Action.READ, action, {});
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+ UtilsService.assertObjectExists(action, user, `User ID '${filteredRequest.args.userID}' does not exist`,
+ MODULE_NAME, 'handleAction', req.user);
if (!user.issuer) {
throw new AppError({
source: Constants.CENTRAL_SERVER,
@@ -1214,9 +1220,70 @@ export default class ChargingStationService {
action: action
});
}
+ const tag = await TagStorage.getTagByVisualID(req.tenant.id, filteredRequest.args.visualTagID, { userIDs: [user.id] });
+ if (!tag) {
+ throw new AppError({
+ source: Constants.CENTRAL_SERVER,
+ errorCode: HTTPError.USER_NO_BADGE_ERROR,
+ message: 'The user does not have any badge',
+ module: MODULE_NAME,
+ method: 'handleAction',
+ user: req.user,
+ action: action,
+ });
+ }
+ // Inactive Tag
+ if (!tag.active) {
+ throw new BackendError({
+ source: chargingStation.id,
+ action: action,
+ message: `Tag ID '${tag.id}' is not active`,
+ module: MODULE_NAME, method: 'handleAction',
+ user: tag.user,
+ detailedMessages: { tag }
+ });
+ }
+ // Org component enabled?
+ if (Utils.isTenantComponentActive(req.tenant, TenantComponents.ORGANIZATION)) {
+ let foundSiteArea = true;
+ // Site Area -----------------------------------------------
+ if (!chargingStation.siteAreaID) {
+ foundSiteArea = false;
+ } else if (!chargingStation.siteArea) {
+ chargingStation.siteArea = await SiteAreaStorage.getSiteArea(
+ req.tenant.id, chargingStation.siteAreaID, { withSite: true });
+ if (!chargingStation.siteArea) {
+ foundSiteArea = false;
+ }
+ }
+ // Site is mandatory
+ if (!foundSiteArea) {
+ // Reject Site Not Found
+ throw new BackendError({
+ source: chargingStation.id,
+ action: action,
+ module: MODULE_NAME, method: 'handleAction',
+ message: `Charging Station '${chargingStation.id}' is not assigned to a Site Area!`,
+ detailedMessages: { chargingStation }
+ });
+ }
+ // Site -----------------------------------------------------
+ chargingStation.siteArea.site = chargingStation.siteArea.site ??
+ (chargingStation.siteArea.siteID ? await SiteStorage.getSite(req.tenant.id, chargingStation.siteArea.siteID) : null);
+ if (!chargingStation.siteArea.site) {
+ // Reject Site Not Found
+ throw new BackendError({
+ source: chargingStation.id,
+ action: action,
+ module: MODULE_NAME, method: 'handleAction',
+ message: `Site Area '${chargingStation.siteArea.name}' is not assigned to a Site!`,
+ detailedMessages: { chargingStation }
+ });
+ }
+ }
// Ok: Execute it
result = await ChargingStationService.handleChargingStationCommand(
- req.tenant, req.user, chargingStation, action, command, filteredRequest.args);
+ req.tenant, req.user, chargingStation, action, command, { tagID: tag.id, connectorId: filteredRequest.args.connectorId });
// Save Car ID
if (Utils.isComponentActiveFromToken(req.user, TenantComponents.CAR)) {
if (result?.status === OCPPRemoteStartStopStatus.ACCEPTED) {

MODIFY
src/server/rest/v1/service/TransactionService.ts
src/server/rest/v1/service/TransactionService.ts
@@ -1089,6 +1089,7 @@ export default class TransactionService {
issuer: Utils.objectHasProperty(filteredRequest, 'Issuer') ? filteredRequest.Issuer : null,
userIDs: filteredRequest.UserID ? filteredRequest.UserID.split('|') : null,
tagIDs: filteredRequest.TagID ? filteredRequest.TagID.split('|') : null,
+ visualTagIDs: filteredRequest.VisualTagID ? filteredRequest.VisualTagID.split('|') : null,
ownerID: Authorizations.isBasic(req.user) ? req.user.id : null,
siteAreaIDs: filteredRequest.SiteAreaID ? filteredRequest.SiteAreaID.split('|') : null,
siteIDs: filteredRequest.SiteID ? Authorizations.getAuthorizedSiteAdminIDs(req.user, filteredRequest.SiteID.split('|')) : null,

MODIFY
src/server/rest/v1/service/UserService.ts
src/server/rest/v1/service/UserService.ts
@@ -90,12 +90,16 @@ export default class UserService {
// Get the default Tag
let tag = await TagStorage.getDefaultUserTag(req.user.tenantID, userID, {
issuer: true
- }, ['id', 'description', 'active']);
+ }, ['id']);
if (!tag) {
// Get the first active Tag
tag = await TagStorage.getFirstActiveUserTag(req.user.tenantID, userID, {
issuer: true
- }, ['id', 'description', 'active']);
+ }, ['id']);
+ }
+ if (tag) {
+ tag = await UtilsService.checkAndGetTagAuthorization(req.tenant, req.user, tag.id, Action.READ, action,
+ { withUser: false }, true);
}
// Handle Car
let car: Car;
@@ -1198,6 +1202,7 @@ export default class UserService {
siteIDs: (filteredRequest.SiteID ? filteredRequest.SiteID.split('|') : null),
userIDs: (filteredRequest.UserID ? filteredRequest.UserID.split('|') : null),
tagIDs: (filteredRequest.TagID ? filteredRequest.TagID.split('|') : null),
+ visualTagIDs: (filteredRequest.VisualTagID ? filteredRequest.VisualTagID.split('|') : null),
roles: (filteredRequest.Role ? filteredRequest.Role.split('|') : null),
statuses: (filteredRequest.Status ? filteredRequest.Status.split('|') : null),
excludeSiteID: filteredRequest.ExcludeSiteID,

MODIFY
src/server/rest/v1/service/security/UserSecurity.ts
src/server/rest/v1/service/security/UserSecurity.ts
@@ -69,6 +69,9 @@ export default class UserSecurity {
if (Utils.objectHasProperty(request, 'TagID')) {
filteredRequest.TagID = sanitize(request.TagID);
}
+ if (Utils.objectHasProperty(request, 'VisualTagID')) {
+ filteredRequest.VisualTagID = sanitize(request.VisualTagID);
+ }
if (Utils.objectHasProperty(request, 'ExcludeUserIDs')) {
filteredRequest.ExcludeUserIDs = sanitize(request.ExcludeUserIDs);
}

MODIFY
src/storage/mongodb/TagStorage.ts
src/storage/mongodb/TagStorage.ts
@@ -271,20 +271,22 @@ export default class TagStorage {
}
public static async getTagByVisualID(tenantID: string, visualID: string,
- params: { withUser?: boolean; withNbrTransactions?: boolean } = {}, projectFields?: string[]): Promise<Tag> {
+ params: { withUser?: boolean; withNbrTransactions?: boolean; userIDs?: string[] } = {}, projectFields?: string[]): Promise<Tag> {
const tagMDB = await TagStorage.getTagByVisualIDs(tenantID, [visualID], {
withUser: params.withUser,
withNbrTransactions: params.withNbrTransactions,
+ userIDs: params.userIDs
}, Constants.DB_PARAMS_SINGLE_RECORD, projectFields);
return tagMDB.count === 1 ? tagMDB.result[0] : null;
}
public static async getTagByVisualIDs(tenantID: string, visualIDs: string[],
- params: { withUser?: boolean; withNbrTransactions?: boolean } = {}, dbParams: DbParams, projectFields?: string[]): Promise<DataResult<Tag>> {
+ params: { withUser?: boolean; withNbrTransactions?: boolean; userIDs?: string[] } = {}, dbParams: DbParams, projectFields?: string[]): Promise<DataResult<Tag>> {
return await TagStorage.getTags(tenantID, {
visualIDs: visualIDs,
withUser: params.withUser,
withNbrTransactions: params.withNbrTransactions,
+ userIDs: params.userIDs
}, dbParams, projectFields);
}

MODIFY
src/storage/mongodb/TransactionStorage.ts
src/storage/mongodb/TransactionStorage.ts
@@ -270,7 +270,7 @@ export default class TransactionStorage {
params: {
transactionIDs?: number[]; issuer?: boolean; search?: string; ownerID?: string; userIDs?: string[]; siteAdminIDs?: string[];
chargeBoxIDs?: string[]; siteAreaIDs?: string[]; siteIDs?: string[]; connectorIDs?: number[]; startDateTime?: Date;
- endDateTime?: Date; stop?: any; minimalPrice?: boolean; reportIDs?: string[]; tagIDs?: string[]; inactivityStatus?: string[];
+ endDateTime?: Date; stop?: any; minimalPrice?: boolean; reportIDs?: string[]; tagIDs?: string[]; visualTagIDs?: string[]; inactivityStatus?: string[];
ocpiSessionID?: string; ocpiAuthorizationID?: string; ocpiSessionDateFrom?: Date; ocpiSessionDateTo?: Date; ocpiCdrDateFrom?: Date; ocpiCdrDateTo?: Date;
ocpiSessionChecked?: boolean; ocpiCdrChecked?: boolean; oicpSessionID?: string;
statistics?: 'refund' | 'history'; refundStatus?: string[]; withTag?: boolean; hasUserID?: boolean;
@@ -445,6 +445,14 @@ export default class TransactionStorage {
}
// Create Aggregation
const aggregation = [];
+ if (!Utils.isEmptyArray(params.visualTagIDs)) {
+ DatabaseUtils.pushTagLookupInAggregation({
+ tenantID, aggregation, localField: 'tagID', foreignField: '_id', asField: 'tag'
+ });
+ aggregation.push({
+ $match: { 'tag.visualID': { $in: params.visualTagIDs } }
+ });
+ }
// Filters
if (ownerMatch.$or && ownerMatch.$or.length > 0) {
aggregation.push({

MODIFY
src/storage/mongodb/UserStorage.ts
src/storage/mongodb/UserStorage.ts
@@ -541,7 +541,7 @@ export default class UserStorage {
userIDs?: string[]; email?: string; issuer?: boolean; passwordResetHash?: string; roles?: string[];
statuses?: string[]; withImage?: boolean; billingUserID?: string;
notSynchronizedBillingData?: boolean; withTestBillingData?: boolean;
- notifications?: any; noLoginSince?: Date; tagIDs?: string[];
+ notifications?: any; noLoginSince?: Date; tagIDs?: string[]; visualTagIDs?: string[]
},
dbParams: DbParams, projectFields?: string[]): Promise<DataResult<User>> {
// Debug
@@ -658,14 +658,21 @@ export default class UserStorage {
});
}
// Add Tags
- if (params.tagIDs) {
+ if (!Utils.isEmptyArray(params.tagIDs) || !Utils.isEmptyArray(params.visualTagIDs)) {
DatabaseUtils.pushTagLookupInAggregation({
tenantID, aggregation, localField: '_id', foreignField: 'userID', asField: 'tag'
});
+ if (!Utils.isEmptyArray(params.tagIDs)) {
aggregation.push({
$match: { 'tag.id': { $in: params.tagIDs } }
});
}
+ if (!Utils.isEmptyArray(params.visualTagIDs)) {
+ aggregation.push({
+ $match: { 'tag.visualID': { $in: params.visualTagIDs } }
+ });
+ }
+ }
// Add Site
if (params.siteIDs || params.excludeSiteID) {
DatabaseUtils.pushSiteUserLookupInAggregation({

MODIFY
src/types/requests/HttpTransactionRequest.ts
src/types/requests/HttpTransactionRequest.ts
@@ -30,6 +30,7 @@ export interface HttpTransactionsRequest extends HttpDatabaseRequest {
SiteID?: string;
UserID?: string;
TagID?: string;
+ VisualTagID?: string;
StartDateTime?: Date;
EndDateTime?: Date;
Search?: string;

MODIFY
src/types/requests/HttpUserRequest.ts
src/types/requests/HttpUserRequest.ts
@@ -38,6 +38,7 @@ export interface HttpUsersRequest extends HttpDatabaseRequest {
Status: string;
ErrorType?: string;
TagID?: string;
+ VisualTagID?: string;
ExcludeSiteID: string;
ExcludeUserIDs: string;
IncludeCarUserIDs: string;

change tagID to VisualTagID