Site Admin can create users that are assigned to his sites
MODIFY
src/authorization/AuthorizationsDefinition.ts
src/authorization/AuthorizationsDefinition.ts
-import { AccessControl, IDictionary, IFunctionCondition, Permission } from 'role-acl';
+import { AccessControl, IDictionary, IFunctionCondition } from 'role-acl';
import { Action, AuthorizationContext, AuthorizationDefinition, AuthorizationResult, Entity } from '../types/Authorization';
import BackendError from '../exception/BackendError';
import Constants from '../utils/Constants';
-import LockingHelper from '../locking/LockingHelper';
-import LockingManager from '../locking/LockingManager';
import Utils from '../utils/Utils';
-import { authorize } from 'passport';
const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
superAdmin: {
@@ -19,7 +16,6 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
'billingData.customerID', 'billingData.lastChangedOn'
]
},
- { resource: Entity.USER, action: [Action.CREATE, Action.UPDATE] },
{
resource: Entity.USER, action: Action.DELETE,
condition: {
@@ -28,7 +24,7 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
}
},
{
- resource: Entity.USER, action: Action.READ,
+ resource: Entity.USER, action: [Action.READ, Action.CREATE, Action.UPDATE],
attributes: [
'id', 'name', 'firstName', 'email', 'role', 'status', 'issuer', 'locale', 'plateID',
'notificationsActive', 'notifications', 'phone', 'mobile', 'iNumber', 'costCenter', 'address'
@@ -64,10 +60,13 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
},
admin: {
grants: [
+ {
+ resource: Entity.USERS, action: Action.SYNCHRONIZE_BILLING_USERS,
+ },
{
resource: Entity.USERS,
action: [
- Action.LIST, Action.SYNCHRONIZE_BILLING_USERS, Action.EXPORT, Action.IMPORT
+ Action.LIST, Action.EXPORT, Action.IMPORT
],
attributes: [
'id', 'name', 'firstName', 'email', 'role', 'status', 'issuer', 'createdOn', 'createdBy',
@@ -314,23 +313,20 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
basic: {
grants: [
{
- resource: Entity.USER, action: Action.READ,
+ resource: Entity.USER, action: [Action.READ, Action.UPDATE],
condition: {
Fn: 'custom:dynamicAuthorizations',
- args: { filters: ['OwnUser'] }
+ args: {
+ filters: ['OwnUser'],
+ asserts: ['BasicUser']
+ }
},
attributes: [
- 'id', 'name', 'firstName', 'email', 'role', 'status', 'issuer', 'locale', 'plateID',
- 'notificationsActive', 'notifications', 'phone', 'mobile', 'iNumber', 'costCenter', 'address'
+ 'id', 'name', 'firstName', 'email', 'role', 'issuer', 'locale',
+ 'notificationsActive', 'notifications', 'phone', 'mobile',
+ 'iNumber', 'costCenter', 'address'
]
},
- {
- resource: Entity.USER, action: Action.UPDATE,
- condition: {
- Fn: 'custom:dynamicAuthorizations',
- args: { filters: ['OwnUser'] }
- }
- },
{ resource: Entity.SETTING, action: Action.READ },
{
resource: Entity.CAR_CATALOGS, action: Action.LIST,
@@ -624,8 +620,8 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
args: { filters: ['OwnUser'] }
},
attributes: [
- 'id', 'name', 'firstName', 'email', 'role', 'status', 'issuer', 'locale', 'plateID',
- 'notificationsActive', 'notifications', 'phone', 'mobile', 'iNumber', 'costCenter', 'address'
+ 'id', 'name', 'firstName', 'email', 'issuer', 'locale', 'notificationsActive',
+ 'notifications', 'phone', 'mobile', 'iNumber', 'costCenter', 'address'
],
},
{
@@ -769,7 +765,9 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
resource: Entity.USER, action: Action.READ,
condition: {
Fn: 'custom:dynamicAuthorizations',
- args: { filters: ['SitesAdmin', 'LocalIssuer'] }
+ args: {
+ filters: ['SitesAdmin', 'LocalIssuer']
+ }
},
attributes: [
'id', 'name', 'firstName', 'email', 'role', 'status', 'issuer', 'locale', 'plateID',
@@ -781,16 +779,24 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
condition: {
Fn: 'custom:dynamicAuthorizations',
args: {
- asserts: ['BasicUser']
- }
+ asserts: ['BasicUser'],
+ filters: ['SitesAdmin']
}
},
+ attributes: [
+ 'id', 'name', 'firstName', 'email', 'role', 'status', 'issuer', 'locale', 'plateID',
+ 'notificationsActive', 'notifications', 'phone', 'mobile', 'iNumber', 'costCenter', 'address', 'technical'
+ ],
+ },
{ resource: Entity.USER, action: Action.SYNCHRONIZE_BILLING_USER },
{
resource: Entity.USER, action: Action.DELETE,
condition: {
Fn: 'custom:dynamicAuthorizations',
- args: { filters: ['-OwnUser'] }
+ args: {
+ asserts: ['BasicUser'],
+ filters: ['-OwnUser', 'SitesAdmin']
+ }
}
},
{
@@ -958,11 +964,13 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
resource: Entity.USER, action: Action.READ,
condition: {
Fn: 'custom:dynamicAuthorizations',
- args: { filters: ['SitesOwner', 'LocalIssuer'] }
+ args: {
+ filters: ['SitesOwner', 'LocalIssuer']
+ }
},
attributes: [
- 'id', 'name', 'firstName', 'email', 'role', 'status', 'issuer', 'locale', 'plateID',
- 'notificationsActive', 'notifications', 'phone', 'mobile', 'iNumber', 'costCenter', 'address'
+ 'id', 'name', 'firstName', 'email', 'issuer', 'locale', 'notificationsActive', 'notifications',
+ 'phone', 'mobile', 'iNumber', 'costCenter', 'address'
],
},
{

MODIFY
src/server/rest/v1/service/AuthorizationService.ts
src/server/rest/v1/service/AuthorizationService.ts
@@ -82,7 +82,7 @@ export default class AuthorizationService {
public static async addSiteAuthorizations(tenant: Tenant, userToken: UserToken, site: Site, authorizationFilter: AuthorizationFilter): Promise<void> {
// Assign projected fields
if (authorizationFilter.projectFields) {
- site.projectedFields = authorizationFilter.projectFields;
+ site.projectFields = authorizationFilter.projectFields;
}
// Enrich
if (!site.issuer) {
@@ -252,7 +252,7 @@ export default class AuthorizationService {
public static async addUserAuthorizations(tenant: Tenant, userToken: UserToken, user: User, authorizationFilter: AuthorizationFilter): Promise<void> {
// Assign projected fields
if (authorizationFilter.projectFields) {
- user.projectedFields = authorizationFilter.projectFields;
+ user.projectFields = authorizationFilter.projectFields;
}
// Enrich
if (!user.issuer) {
@@ -339,7 +339,7 @@ export default class AuthorizationService {
public static async addTagAuthorizations(tenant: Tenant, userToken: UserToken, tag: Tag, authorizationFilter: AuthorizationFilter): Promise<void> {
// Assign projected fields
if (authorizationFilter.projectFields) {
- tag.projectedFields = authorizationFilter.projectFields;
+ tag.projectFields = authorizationFilter.projectFields;
}
// Enrich
if (!tag.issuer) {
@@ -388,7 +388,7 @@ export default class AuthorizationService {
public static async addCompanyAuthorizations(tenant: Tenant, userToken: UserToken, company: Company, authorizationFilter: AuthorizationFilter): Promise<void> {
// Assign projected fields
if (authorizationFilter.projectFields) {
- company.projectedFields = authorizationFilter.projectFields;
+ company.projectFields = authorizationFilter.projectFields;
}
// Enrich
if (!company.issuer) {
@@ -443,7 +443,7 @@ export default class AuthorizationService {
public static async addSiteAreaAuthorizations(tenant: Tenant, userToken: UserToken, siteArea: SiteArea, authorizationFilter: AuthorizationFilter): Promise<void> {
// Assign projected fields
if (authorizationFilter.projectFields) {
- siteArea.projectedFields = authorizationFilter.projectFields;
+ siteArea.projectFields = authorizationFilter.projectFields;
}
// Enrich
if (!siteArea.issuer) {
@@ -534,7 +534,7 @@ export default class AuthorizationService {
public static async addCarAuthorizations(tenant: Tenant, userToken: UserToken, car: Car, authorizationFilter: AuthorizationFilter): Promise<void> {
// Assign projected fields
if (authorizationFilter.projectFields) {
- car.projectedFields = authorizationFilter.projectFields;
+ car.projectFields = authorizationFilter.projectFields;
}
// Enrich
car.canRead = true; // Always true as it should be filtered upfront
@@ -577,7 +577,7 @@ export default class AuthorizationService {
public static async addCarCatalogAuthorizations(tenant: Tenant, userToken: UserToken, carCatalog: CarCatalog, authorizationFilter: AuthorizationFilter): Promise<void> {
// Assign projected fields
if (authorizationFilter.projectFields) {
- carCatalog.projectedFields = authorizationFilter.projectFields;
+ carCatalog.projectFields = authorizationFilter.projectFields;
}
// Enrich
carCatalog.canRead = true; // Always true as it should be filtered upfront

MODIFY
src/server/rest/v1/service/UserService.ts
src/server/rest/v1/service/UserService.ts
-import { Action, Entity } from '../../../../types/Authorization';
+import { Action, AuthorizationFilter, Entity } from '../../../../types/Authorization';
import { ActionsResponse, ImportStatus } from '../../../../types/GlobalType';
import { AsyncTaskType, AsyncTasks } from '../../../../types/AsyncTask';
import { Car, CarType } from '../../../../types/Car';
@@ -216,28 +216,8 @@ export default class UserService {
passwordBlockedUntil: null
});
}
- // Only Admin can save these data
- if (Authorizations.isAdmin(req.user) || Authorizations.isSuperAdmin(req.user)) {
- // Save User's Status
- if (filteredRequest.status) {
- await UserStorage.saveUserStatus(req.tenant, user.id, filteredRequest.status);
- }
- // Save User's Role
- if (filteredRequest.role) {
- await UserStorage.saveUserRole(req.tenant, user.id, filteredRequest.role);
- }
- // Save Admin Data
- if (user.projectedFields.includes('plateID') || user.projectedFields.includes('technical')) {
- const adminData: { plateID?: string; technical?: boolean; } = {};
- if (user.projectedFields.includes('plateID')) {
- adminData.plateID = filteredRequest.plateID || null;
- }
- if (user.projectedFields.includes('technical')) {
- adminData.technical = filteredRequest.technical;
- }
- await UserStorage.saveUserAdminData(req.tenant, user.id, adminData);
- }
- }
+ // Update User Admin Data
+ await UserService.updateUserAdminData(req.tenant, user, user.projectFields);
// Update Billing
await UserService.updateUserBilling(ServerAction.USER_UPDATE, req.tenant, req.user, user);
// Log
@@ -672,44 +652,10 @@ export default class UserService {
passwordBlockedUntil: null
});
}
- // Only Admin can save these data
- if (Authorizations.isAdmin(req.user) || Authorizations.isSuperAdmin(req.user)) {
- // Save User Status
- if (newUser.status) {
- await UserStorage.saveUserStatus(req.tenant, newUser.id, newUser.status);
- }
- // Save User Role
- if (newUser.role) {
- await UserStorage.saveUserRole(req.tenant, newUser.id, newUser.role);
- }
- // Save Admin Data
- if (newUser.plateID || Utils.objectHasProperty(newUser, 'notificationsActive') || Utils.objectHasProperty(newUser, 'technical')) {
- const adminData: { plateID?: string; notificationsActive?: boolean; notifications?: UserNotifications, technical?: boolean } = {};
- if (newUser.plateID) {
- adminData.plateID = newUser.plateID;
- }
- if (Utils.objectHasProperty(newUser, 'notificationsActive')) {
- adminData.notificationsActive = newUser.notificationsActive;
- if (newUser.notifications) {
- adminData.notifications = newUser.notifications;
- }
- }
- if (authorizationFilter.projectFields.includes('technical')) {
- adminData.technical = newUser.technical;
- }
- // Save User Admin data
- await UserStorage.saveUserAdminData(req.tenant, newUser.id, adminData);
- }
- }
- // Assign user to all sites with auto-assign flag set
- const sites = await SiteStorage.getSites(req.tenant,
- { withAutoUserAssignment: true },
- Constants.DB_PARAMS_MAX_LIMIT
- );
- if (!Utils.isEmptyArray(sites.result)) {
- const siteIDs = sites.result.map((site) => site.id);
- await UserStorage.addSitesToUser(req.tenant, newUser.id, siteIDs);
- }
+ // Update User Admin Data
+ await UserService.updateUserAdminData(req.tenant, newUser, authorizationFilter.projectFields);
+ // Assign Site to new User
+ await UserService.assignCreatedUserToSites(req.tenant, req.user, newUser, authorizationFilter);
// Update Billing
await UserService.updateUserBilling(ServerAction.USER_CREATE, req.tenant, req.user, newUser);
// Log
@@ -883,17 +829,7 @@ export default class UserService {
if (Utils.isComponentActiveFromToken(loggedUser, TenantComponents.BILLING)) {
try {
const billingImpl = await BillingFactory.getBillingImpl(tenant);
- if (!billingImpl) {
- throw new AppError({
- source: Constants.CENTRAL_SERVER,
- action: ServerAction.USER_DELETE,
- errorCode: HTTPError.GENERAL_ERROR,
- message: 'Billing service is not configured',
- module: MODULE_NAME, method: 'checkAndDeleteUserBilling',
- user: loggedUser, actionOnUser: user
- });
- }
- if (user.billingData) {
+ if (billingImpl && user.billingData) {
const userCanBeDeleted = await billingImpl.checkIfUserCanBeDeleted(user);
if (!userCanBeDeleted) {
throw new AppError({
@@ -905,8 +841,8 @@ export default class UserService {
user: loggedUser, actionOnUser: user
});
}
- }
await billingImpl.deleteUser(user);
+ }
} catch (error) {
throw new AppError({
source: Constants.CENTRAL_SERVER,
@@ -995,4 +931,60 @@ export default class UserService {
}
}
}
+
+ private static async assignCreatedUserToSites(tenant: Tenant, loggedUser: UserToken, user: User, authorizationFilter: AuthorizationFilter) {
+ // Assign user to sites
+ if (Utils.isComponentActiveFromToken(loggedUser, TenantComponents.ORGANIZATION)) {
+ let siteIDs = [];
+ if (!Utils.isEmptyArray(authorizationFilter.filters.siteIDs)) {
+ siteIDs = authorizationFilter.filters.siteIDs;
+ } else {
+ // Assign user to all sites with auto-assign flag set
+ const sites = await SiteStorage.getSites(tenant,
+ { withAutoUserAssignment: true },
+ Constants.DB_PARAMS_MAX_LIMIT
+ );
+ siteIDs = sites.result.map((site) => site.id);
+ }
+ await UserStorage.addSitesToUser(tenant, user.id, siteIDs);
+ }
+ }
+
+ private static async updateUserAdminData(tenant: Tenant, user: User, projectFields: string[]) {
+ // Save User Status
+ if (Utils.objectHasProperty(user, 'status') &&
+ projectFields.includes('status')) {
+ await UserStorage.saveUserStatus(tenant, user.id, user.status);
+ }
+ // Save User Role
+ if (Utils.objectHasProperty(user, 'role') &&
+ projectFields.includes('role')) {
+ await UserStorage.saveUserRole(tenant, user.id, user.role);
+ }
+ // Save Admin Data
+ if (projectFields.includes('plateID') ||
+ projectFields.includes('technical') ||
+ projectFields.includes('notificationsActive')) {
+ const adminData: { plateID?: string; notificationsActive?: boolean; notifications?: UserNotifications, technical?: boolean } = {};
+ if (Utils.objectHasProperty(user, 'plateID') &&
+ projectFields.includes('plateID')) {
+ adminData.plateID = user.plateID || null;
+ }
+ if (Utils.objectHasProperty(user, 'technical') &&
+ projectFields.includes('technical')) {
+ adminData.technical = user.technical;
+ }
+ if (Utils.objectHasProperty(user, 'notificationsActive') &&
+ projectFields.includes('notificationsActive')) {
+ adminData.notificationsActive = user.notificationsActive;
+ if (user.notifications) {
+ adminData.notifications = user.notifications;
+ }
+ }
+ // Save
+ if (!Utils.isEmptyArray(Object.keys(adminData))) {
+ await UserStorage.saveUserAdminData(tenant, user.id, adminData);
+ }
+ }
+ }
}

MODIFY
src/server/rest/v1/validator/SchemaValidator.ts
src/server/rest/v1/validator/SchemaValidator.ts
@@ -87,12 +87,12 @@ export default class SchemaValidator {
protected validate(schemaID: string, schema: Schema, data: any): any {
// Get schema from cache
let fnValidate = SchemaValidator.compiledSchemas.get(schemaID);
- if (!fnValidate) {
+ // if (!fnValidate) {
// Compile schema
fnValidate = this.ajv.compile(schema);
// Add it to cache
- SchemaValidator.compiledSchemas.set(schemaID, fnValidate);
- }
+ // SchemaValidator.compiledSchemas.set(schemaID, fnValidate);
+ // }
if (!fnValidate(data)) {
if (!fnValidate.errors) {
fnValidate.errors = [];

MODIFY
src/types/Authorization.ts
src/types/Authorization.ts
@@ -186,7 +186,7 @@ export interface AuthorizationActions {
canCreate?: boolean;
canUpdate?: boolean;
canDelete?: boolean;
- projectedFields?: string[];
+ projectFields?: string[];
}
export interface TagAuthorizationActions extends AuthorizationActions {

Site Admin can create users that are assigned to his sites