Add fields in companies
MODIFY
src/authorization/Authorizations.ts
src/authorization/Authorizations.ts
-import { Action, AuthorizationContext, Entity } from '../types/Authorization';
+import { Action, AuthorizationContext, AuthorizationResult, Entity } from '../types/Authorization';
import ChargingStation, { Connector } from '../types/ChargingStation';
import User, { UserRole, UserStatus } from '../types/User';
@@ -159,27 +159,29 @@ export default class Authorizations {
if (pricing && pricing.type === PricingSettingsType.SIMPLE) {
currency = pricing.simple.currency;
}
+ const rolesACL = Authorizations.getAuthGroupsFromUser(user.role, siteAdminIDs.length, siteOwnerIDs.length);
return {
- 'id': user.id,
- 'role': user.role,
- 'name': user.name,
- 'mobile': user.mobile,
- 'email': user.email,
- 'tagIDs': tags ? tags.filter((tag) => tag.active).map((tag) => tag.id) : [],
- 'firstName': user.firstName,
- 'locale': user.locale,
- 'language': Utils.getLanguageFromLocale(user.locale),
- 'currency': currency,
- 'tenantID': tenantID,
- 'tenantName': tenantName,
- 'tenantSubdomain': tenantSubdomain,
- 'userHashID': SessionHashService.buildUserHashID(user),
- 'tenantHashID': tenantHashID,
- 'scopes': Authorizations.getUserScopes(tenantID, user, siteAdminIDs.length, siteOwnerIDs.length),
- 'sitesAdmin': siteAdminIDs,
- 'sitesOwner': siteOwnerIDs,
- 'sites': siteIDs,
- 'activeComponents': activeComponents
+ id: user.id,
+ role: user.role,
+ rolesACL,
+ name: user.name,
+ mobile: user.mobile,
+ email: user.email,
+ tagIDs: tags ? tags.filter((tag) => tag.active).map((tag) => tag.id) : [],
+ firstName: user.firstName,
+ locale: user.locale,
+ language: Utils.getLanguageFromLocale(user.locale),
+ currency: currency,
+ tenantID: tenantID,
+ tenantName: tenantName,
+ tenantSubdomain: tenantSubdomain,
+ userHashID: SessionHashService.buildUserHashID(user),
+ tenantHashID: tenantHashID,
+ scopes: AuthorizationsDefinition.getInstance().getScopes(rolesACL),
+ sitesAdmin: siteAdminIDs,
+ sitesOwner: siteOwnerIDs,
+ sites: siteIDs,
+ activeComponents: activeComponents
};
}
@@ -542,12 +544,12 @@ export default class Authorizations {
return Authorizations.canPerformAction(loggedUser, Entity.SITE_AREA, Action.DELETE);
}
- public static canListCompanies(loggedUser: UserToken): boolean {
- return Authorizations.canPerformAction(loggedUser, Entity.COMPANIES, Action.LIST);
+ public static canListCompanies(loggedUser: UserToken): AuthorizationResult {
+ return Authorizations.can(loggedUser, Entity.COMPANIES, Action.LIST);
}
- public static canReadCompany(loggedUser: UserToken): boolean {
- return Authorizations.canPerformAction(loggedUser, Entity.COMPANY, Action.READ);
+ public static canReadCompany(loggedUser: UserToken): AuthorizationResult {
+ return Authorizations.can(loggedUser, Entity.COMPANY, Action.READ);
}
public static canCreateCompany(loggedUser: UserToken): boolean {
@@ -966,7 +968,8 @@ export default class Authorizations {
});
}
// Transaction can be nullified to assess the authorization at a higher level than connectors, default connector ID value to 1 then
- const transactionConnector: Connector = transaction?.connectorId ? Utils.getConnectorFromID(chargingStation, transaction.connectorId) : Utils.getConnectorFromID(chargingStation, 1);
+ const transactionConnector: Connector = transaction?.connectorId ?
+ Utils.getConnectorFromID(chargingStation, transaction.connectorId) : Utils.getConnectorFromID(chargingStation, 1);
// Keep the Auth ID
user.authorizationID = await ocpiClient.authorizeToken(tag.ocpiToken, chargingStation, transactionConnector);
}
@@ -974,13 +977,6 @@ export default class Authorizations {
return user;
}
- private static getUserScopes(tenantID: string, user: User, sitesAdminCount: number, sitesOwnerCount: number): ReadonlyArray<string> {
- // Get the group from User's role
- const groups = Authorizations.getAuthGroupsFromUser(user.role, sitesAdminCount, sitesOwnerCount);
- // Return the scopes
- return AuthorizationsDefinition.getInstance().getScopes(groups);
- }
-
private static getConfiguration() {
if (!Authorizations.configuration) {
Authorizations.configuration = Configuration.getAuthorizationConfig();
@@ -988,42 +984,36 @@ export default class Authorizations {
return Authorizations.configuration;
}
- private static getAuthGroupsFromUser(userRole: string, sitesAdminCount: number, sitesOwnerCount: number): ReadonlyArray<string> {
- const groups: Array<string> = [];
+ private static getAuthGroupsFromUser(userRole: string, sitesAdminCount: number, sitesOwnerCount: number): string[] {
+ const roles: Array<string> = [];
switch (userRole) {
case UserRole.ADMIN:
- groups.push('admin');
+ roles.push('admin');
break;
case UserRole.SUPER_ADMIN:
- groups.push('superAdmin');
+ roles.push('superAdmin');
break;
case UserRole.BASIC:
- groups.push('basic');
- // Check Site Admin
+ roles.push('basic');
if (sitesAdminCount > 0) {
- groups.push('siteAdmin');
+ roles.push('siteAdmin');
}
break;
case UserRole.DEMO:
- groups.push('demo');
+ roles.push('demo');
break;
}
-
if (sitesOwnerCount > 0) {
- groups.push('siteOwner');
+ roles.push('siteOwner');
}
- return groups;
+ return roles;
}
private static canPerformAction(loggedUser: UserToken, entity: Entity, action: Action, context?: AuthorizationContext): boolean {
- // Get the groups
- const groups = Authorizations.getAuthGroupsFromUser(loggedUser.role,
- loggedUser.sitesAdmin ? loggedUser.sitesAdmin.length : 0,
- loggedUser.sitesOwner ? loggedUser.sitesOwner.length : 0);
// Check
- const authorized = AuthorizationsDefinition.getInstance().can(groups, entity, action, context);
+ const authorized = AuthorizationsDefinition.getInstance().can(loggedUser.rolesACL, entity, action, context);
if (!authorized && Authorizations.getConfiguration().debug) {
- Logging.logSecurityInfo({
+ void Logging.logSecurityInfo({
tenantID: loggedUser.tenantID, user: loggedUser,
action: ServerAction.AUTHORIZATIONS,
module: MODULE_NAME, method: 'canPerformAction',
@@ -1032,4 +1022,18 @@ export default class Authorizations {
}
return authorized;
}
+
+ private static can(loggedUser: UserToken, entity: Entity, action: Action, context?: AuthorizationContext): AuthorizationResult {
+ // Check
+ const result = AuthorizationsDefinition.getInstance().canPerformAction(loggedUser.rolesACL, entity, action, context);
+ if (!result.authorized && Authorizations.getConfiguration().debug) {
+ void Logging.logSecurityInfo({
+ tenantID: loggedUser.tenantID, user: loggedUser,
+ action: ServerAction.AUTHORIZATIONS,
+ module: MODULE_NAME, method: 'canPerformAction',
+ message: `Role ${loggedUser.role} Cannot ${action} on ${entity} with context ${JSON.stringify(context)}`,
+ });
+ }
+ return result;
+ }
}

MODIFY
src/authorization/AuthorizationsDefinition.ts
src/authorization/AuthorizationsDefinition.ts
-import { Action, AuthorizationDefinition, Entity } from '../types/Authorization';
+import { Action, AuthorizationDefinition, AuthorizationResult, Entity } from '../types/Authorization';
import AccessControl from 'role-acl';
import BackendError from '../exception/BackendError';
@@ -30,12 +30,15 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
resource: Entity.USER, action: Action.DELETE, attributes: ['*'],
condition: { Fn: 'NOT_EQUALS', args: { 'user': '$.owner' } }
},
- { resource: Entity.COMPANIES, action: Action.LIST, attributes: ['*'] },
+ { resource: Entity.COMPANIES, action: Action.LIST, attributes: [
+ 'id', 'name', 'address', 'logo', 'issuer', 'distanceMeters', 'createdOn', 'lastChangedOn',
+ 'createdBy.name', 'createdBy.firstName', 'lastChangedBy.name', 'lastChangedBy.firstName' ] },
{ resource: Entity.TAGS, action: [Action.LIST, Action.IMPORT], attributes: ['*'] },
{ resource: Entity.TAG, action: [Action.CREATE, Action.UPDATE, Action.DELETE, Action.READ], attributes: ['*'] },
{ resource: Entity.CHARGING_PROFILES, action: Action.LIST, attributes: ['*'] },
{ resource: Entity.CHARGING_PROFILE, action: [Action.READ], attributes: ['*'] },
- { resource: Entity.COMPANY, action: [Action.CREATE, Action.READ, Action.UPDATE, Action.DELETE], attributes: ['*'] },
+ { resource: Entity.COMPANY, action: Action.READ, attributes: ['id', 'name', 'issuer', 'logo', 'address'] },
+ { resource: Entity.COMPANY, action: [Action.CREATE, Action.UPDATE, Action.DELETE], attributes: ['*'] },
{ resource: Entity.SITES, action: Action.LIST, attributes: ['*'] },
{ resource: Entity.SITE, action: [Action.CREATE, Action.READ, Action.UPDATE, Action.DELETE], attributes: ['*'] },
{ resource: Entity.SITE_AREAS, action: Action.LIST, attributes: ['*'] },
@@ -116,14 +119,13 @@ 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: ['*'] },
+ { 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: ['*']
- },
+ { resource: Entity.COMPANY, action: Action.READ, attributes: ['id', 'name', 'issuer', 'logo', 'address'] },
// -----------------------------------------------------------------------------------------------
// TODO - put it pack as soon as BILLING has been validated of SLF
// -----------------------------------------------------------------------------------------------
@@ -230,8 +232,10 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
{ resource: Entity.CAR_CATALOG, action: Action.READ, attributes: ['*'] },
{ resource: Entity.CAR, action: Action.READ, attributes: ['*'] },
{ resource: Entity.CARS, action: Action.LIST, attributes: ['*'] },
- { resource: Entity.COMPANIES, action: Action.LIST, attributes: ['*'] },
- { resource: Entity.COMPANY, action: Action.READ, attributes: ['*'] },
+ { resource: Entity.COMPANIES, action: Action.LIST, attributes: [
+ 'id', 'name', 'address', 'logo', 'issuer', 'distanceMeters', 'createdOn', 'lastChangedOn'] },
+ { resource: Entity.COMPANY, action: Action.READ, attributes: [
+ 'id', 'name', 'issuer', 'logo', 'address'] },
{ resource: Entity.SITES, action: Action.LIST, attributes: ['*'] },
{ resource: Entity.SITE, action: Action.READ, attributes: ['*'] },
{ resource: Entity.SITE_AREAS, action: Action.LIST, attributes: ['*'] },
@@ -347,11 +351,11 @@ export default class AuthorizationsDefinition {
return AuthorizationsDefinition._instance;
}
- public getScopes(groups: ReadonlyArray<string>): ReadonlyArray<string> {
+ public getScopes(roles: ReadonlyArray<string>): ReadonlyArray<string> {
const scopes = [];
try {
- for (const resource of this.accessControl.allowedResources({ role: groups }) as string[]) {
- for (const action of this.accessControl.allowedActions({ role: groups, resource: resource }) as string[]) {
+ for (const resource of this.accessControl.allowedResources({ role: roles }) as string[]) {
+ for (const action of this.accessControl.allowedActions({ role: roles, resource }) as string[]) {
scopes.push(`${resource}:${action}`);
}
}
@@ -381,4 +385,22 @@ export default class AuthorizationsDefinition {
});
}
}
+
+ public canPerformAction(role: ReadonlyArray<string>, resource: string, action: string, context?): AuthorizationResult {
+ try {
+ const permission = this.accessControl.can(role).execute(action).with(context).on(resource);
+ return {
+ authorized: permission.granted,
+ fields: permission.attributes,
+ };
+ } catch (error) {
+ throw new BackendError({
+ source: Constants.CENTRAL_SERVER,
+ module: MODULE_NAME,
+ method: 'canPerformAction',
+ message: 'Unable to check authorization',
+ detailedMessages: { error: error.message, stack: error.stack }
+ });
+ }
+ }
}

MODIFY
src/server/rest/v1/service/AuthorizationService.ts
src/server/rest/v1/service/AuthorizationService.ts
@@ -405,7 +405,7 @@ export default class AuthorizationService {
company.canUpdate = true;
company.canDelete = true;
} else {
- company.canRead = Authorizations.canReadCompany(userToken) && assignedCompanies.includes(company.id);
+ company.canRead = Authorizations.canReadCompany(userToken).authorized && assignedCompanies.includes(company.id);
company.canUpdate = Authorizations.canUpdateCompany(userToken) && assignedCompanies.includes(company.id);
company.canDelete = Authorizations.canDeleteCompany(userToken) && assignedCompanies.includes(company.id);
}
@@ -415,14 +415,20 @@ export default class AuthorizationService {
public static async checkAndGetCompaniesAuthorizationFilters(tenant: Tenant, userToken: UserToken, filteredRequest: HttpCompaniesRequest): Promise<AuthorizationFilter> {
const authorizationFilters: AuthorizationFilter = {
filters: {},
- projectFields: ['id', 'name', 'address', 'logo', 'issuer', 'distanceMeters', 'createdOn', 'lastChangedOn'],
+ projectFields: [],
authorized: userToken.role === UserRole.ADMIN,
};
- // Add user info
- if (Authorizations.canListUsers(userToken)) {
- authorizationFilters.projectFields.push(
- 'createdBy.name', 'createdBy.firstName', 'lastChangedBy.name', 'lastChangedBy.firstName');
+ // Check static auth
+ const authResult = Authorizations.canListCompanies(userToken);
+ if (!authResult.authorized) {
+ throw new AppAuthError({
+ errorCode: HTTPAuthError.FORBIDDEN,
+ user: userToken,
+ action: Action.LIST, entity: Entity.COMPANIES,
+ 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));
@@ -457,11 +463,20 @@ export default class AuthorizationService {
public static async checkAndGetCompanyAuthorizationFilters(tenant: Tenant, userToken: UserToken, filteredRequest: HttpCompanyRequest): Promise<AuthorizationFilter> {
const authorizationFilters: AuthorizationFilter = {
filters: {},
- projectFields: [
- 'id', 'name', 'issuer', 'logo', 'address'
- ],
+ projectFields: [],
authorized: userToken.role === UserRole.ADMIN,
};
+ // Check static auth
+ const authResult = Authorizations.canReadCompany(userToken);
+ if (!authResult.authorized) {
+ throw new AppAuthError({
+ errorCode: HTTPAuthError.FORBIDDEN,
+ user: userToken,
+ action: Action.READ, entity: Entity.COMPANY,
+ 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));

MODIFY
src/server/rest/v1/service/CompanyService.ts
src/server/rest/v1/service/CompanyService.ts
@@ -75,21 +75,13 @@ export default class CompanyService {
// Check if component is active
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.ORGANIZATION,
Action.READ, Entity.COMPANY, MODULE_NAME, 'handleGetCompany');
- // Check static auth
- if (!Authorizations.canReadCompany(req.user)) {
- throw new AppAuthError({
- errorCode: HTTPAuthError.FORBIDDEN,
- user: req.user,
- action: Action.READ, entity: Entity.COMPANY,
- module: MODULE_NAME, method: 'handleGetCompany',
- });
- }
// Filter
const filteredRequest = CompanySecurity.filterCompanyRequest(req.query);
// Check mandatory fields
UtilsService.assertIdIsProvided(action, filteredRequest.ID, MODULE_NAME, 'handleGetCompany', req.user);
// Check dynamic auth
- const authorizationCompanyFilters = await AuthorizationService.checkAndGetCompanyAuthorizationFilters(req.tenant, req.user, filteredRequest);
+ const authorizationCompanyFilters =
+ await AuthorizationService.checkAndGetCompanyAuthorizationFilters(req.tenant, req.user, filteredRequest);
// Get company
const company = await CompanyStorage.getCompany(req.user.tenantID, filteredRequest.ID,
{
@@ -137,15 +129,6 @@ export default class CompanyService {
// Check if component is active
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.ORGANIZATION,
Action.LIST, Entity.COMPANIES, MODULE_NAME, 'handleGetCompanies');
- // Check static auth
- if (!Authorizations.canListCompanies(req.user)) {
- throw new AppAuthError({
- errorCode: HTTPAuthError.FORBIDDEN,
- user: req.user,
- action: Action.LIST, entity: Entity.COMPANIES,
- module: MODULE_NAME, method: 'handleGetCompanies'
- });
- }
// Filter
const filteredRequest = CompanySecurity.filterCompaniesRequest(req.query);
// Check dynamic auth

MODIFY
src/server/rest/v1/service/SiteService.ts
src/server/rest/v1/service/SiteService.ts
@@ -600,7 +600,7 @@ export default class SiteService {
// Check data is valid
UtilsService.checkIfSiteValid(filteredRequest, req);
// Check static auth for reading company
- if (!Authorizations.canReadCompany(req.user)) {
+ if (!Authorizations.canReadCompany(req.user).authorized) {
throw new AppAuthError({
errorCode: HTTPAuthError.FORBIDDEN,
user: req.user,

MODIFY
src/types/Authorization.ts
src/types/Authorization.ts
@@ -24,6 +24,12 @@ export interface AuthorizationDefinition {
$extend?: any;
};
}
+
+export interface AuthorizationResult {
+ authorized: boolean;
+ fields: string[];
+}
+
export interface AuthorizationFilter {
filters: Record<string, any>;
projectFields: string[];

MODIFY
src/types/UserToken.ts
src/types/UserToken.ts
@@ -3,6 +3,7 @@ import User from './User';
export default interface UserToken {
id?: string;
role?: string;
+ rolesACL?: string[];
name?: string;
email?: string;
mobile?: string;

Add fields in companies