Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,7 @@ export class NewKombitCreationController {
try {
// Don't leak the passwordHash
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { passwordHash, ...user } = await this.userService.findOne(
id,
getExtendedInfo,
getExtendedInfo
);
const { passwordHash, ...user } = await this.userService.findOne(id, getExtendedInfo);

return user;
} catch (err) {
Expand Down
41 changes: 29 additions & 12 deletions src/controllers/user-management/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ import { CreateUserDto } from "@dto/user-management/create-user.dto";
import { UpdateUserDto } from "@dto/user-management/update-user.dto";
import { UserResponseDto } from "@dto/user-response.dto";
import { ErrorCodes } from "@entities/enum/error-codes.enum";
import { checkIfUserIsGlobalAdmin, checkIfUserHasAccessToOrganization, OrganizationAccessScope } from "@helpers/security-helper";
import {
checkIfUserIsGlobalAdmin,
checkIfUserHasAccessToOrganization,
OrganizationAccessScope,
} from "@helpers/security-helper";
import { UserService } from "@services/user-management/user.service";
import { ListAllUsersResponseDto } from "@dto/list-all-users-response.dto";
import { ListAllUsersMinimalResponseDto } from "@dto/list-all-users-minimal-response.dto";
Expand All @@ -56,7 +60,7 @@ export class UserController {
constructor(
private userService: UserService,
private organizationService: OrganizationService
) { }
) {}

private readonly logger = new Logger(UserController.name);

Expand Down Expand Up @@ -111,7 +115,11 @@ export class UserController {
@Req() req: AuthenticatedRequest,
@Body() body: RejectUserDto
): Promise<Organization> {
checkIfUserHasAccessToOrganization(req, body.orgId, OrganizationAccessScope.UserAdministrationWrite);
checkIfUserHasAccessToOrganization(
req,
body.orgId,
OrganizationAccessScope.UserAdministrationWrite
);

const user = await this.userService.findOne(body.userIdToReject);
const organization = await this.organizationService.findByIdWithUsers(body.orgId);
Expand All @@ -130,17 +138,22 @@ export class UserController {
// Verify that we have admin access to the user and that the user is on an organization
const dbUser = await this.userService.findOneWithOrganizations(id);

// Requesting user has to be admin for at least one organization containing the user
// Requesting user has to be admin for at least one organization containing the user
// _OR_ be global admin
if (!req.user.permissions.isGlobalAdmin && !dbUser.permissions.some(perm => req.user.permissions.hasUserAdminOnOrganization(perm.organization.id))) {
if (
!req.user.permissions.isGlobalAdmin &&
!dbUser.permissions.some(perm =>
req.user.permissions.hasUserAdminOnOrganization(perm.organization.id)
)
) {
throw new ForbiddenException();
}
}

// Only a global admin can modify a global admin user
if (dto.globalAdmin) {
checkIfUserIsGlobalAdmin(req);
}

// Don't leak the passwordHash
const { passwordHash: _, ...user } = await this.userService.updateUser(
id,
Expand Down Expand Up @@ -176,7 +189,7 @@ export class UserController {
req.user.username
);

return wasOk
return wasOk;
}

@Get("/awaitingUsers")
Expand Down Expand Up @@ -232,7 +245,6 @@ export class UserController {
// Don't leak the passwordHash
const { passwordHash: _, ...user } = await this.userService.findOne(
id,
getExtendedInfo,
getExtendedInfo
);

Expand All @@ -243,7 +255,10 @@ export class UserController {
}

@Get("organizationUsers/:organizationId")
@ApiOperation({ summary: "Get all users for an organization. Requires UserAdmin priviledges for the specified organization" })
@ApiOperation({
summary:
"Get all users for an organization. Requires UserAdmin priviledges for the specified organization",
})
async findByOrganizationId(
@Req() req: AuthenticatedRequest,
@Param("organizationId", new ParseIntPipe()) organizationId: number,
Expand All @@ -252,7 +267,9 @@ export class UserController {
try {
// Check if user has access to organization
if (!req.user.permissions.hasUserAdminOnOrganization(organizationId)) {
throw new ForbiddenException("User does not have org admin permissions for this organization");
throw new ForbiddenException(
"User does not have org admin permissions for this organization"
);
}

// Get user objects
Expand Down
8 changes: 4 additions & 4 deletions src/entities/dto/chirpstack/service-profile.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ export class ServiceProfileDto {

@ApiProperty({ required: false })
@IsInt()
@Min(0)
@Max(5)
@Min(0, { message: "Max data rate må ikke være negativ" })
@Max(7, { message: "Max data rate må ikke være større end 7" })
drMax?: number;

@ApiProperty({ required: true })
@IsInt()
@Min(0)
@Max(5)
@Min(0, { message: "Min data rate må ikke være negativ" })
@Max(7, { message: "Min data rate må ikke være større end 7" })
drMin?: number;

@ApiProperty({ required: false })
Expand Down
2 changes: 1 addition & 1 deletion src/entities/dto/list-all-permissions.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ListAllEntitiesDto } from "./list-all-entities.dto";

export class ListAllPermissionsDto extends ListAllEntitiesDto {
@ApiProperty({ type: String, required: false })
organisationId?: number;
organisationId?: string;

@ApiProperty({ type: String, required: false })
userId?: string;
Expand Down
2 changes: 2 additions & 0 deletions src/modules/data-management/payload-decoder-kafka.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { SharedModule } from "@modules/shared.module";
import { HttpModule } from "@nestjs/axios";
import { Module } from "@nestjs/common";
import { PayloadDecoderListenerService } from "@services/data-management/payload-decoder-listener.service";
import { ChirpstackAdministrationModule } from "@modules/device-integrations/chirpstack-administration.module";

@Module({
imports: [
Expand All @@ -22,6 +23,7 @@ import { PayloadDecoderListenerService } from "@services/data-management/payload
HttpModule,
ApplicationModule,
PayloadDecoderExecutorModuleModule,
ChirpstackAdministrationModule,
],
controllers: [PayloadDecoderController],
providers: [PayloadDecoderListenerService],
Expand Down
5 changes: 3 additions & 2 deletions src/modules/device-management/application.module.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { Module, forwardRef } from "@nestjs/common";

import { forwardRef, Module } from "@nestjs/common";
import { ApplicationController } from "@admin-controller/application.controller";
import { SharedModule } from "@modules/shared.module";
import { OrganizationModule } from "@modules/user-management/organization.module";
import { ApplicationService } from "@services/device-management/application.service";
import { ChirpstackAdministrationModule } from "@modules/device-integrations/chirpstack-administration.module";
import { PermissionModule } from "@modules/user-management/permission.module";
import { MulticastModule } from "./multicast.module";
import { DataTargetModule } from "@modules/device-management/data-target.module";

@Module({
imports: [
SharedModule,
forwardRef(() => OrganizationModule),
forwardRef(() => PermissionModule),
forwardRef(() => MulticastModule), // because of circular reference
forwardRef(() => DataTargetModule),
ChirpstackAdministrationModule,
],
exports: [ApplicationService],
Expand Down
4 changes: 2 additions & 2 deletions src/modules/device-management/data-target.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import configuration from "@config/configuration";
import { ApplicationModule } from "@modules/device-management/application.module";
import { SharedModule } from "@modules/shared.module";
import { OrganizationModule } from "@modules/user-management/organization.module";
import { Module } from "@nestjs/common";
import { forwardRef, Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { DataTargetService } from "@services/data-targets/data-target.service";
import { OS2IoTMail } from "@services/os2iot-mail.service";
Expand All @@ -15,7 +15,7 @@ import {
@Module({
imports: [
SharedModule,
ApplicationModule,
forwardRef(() => ApplicationModule),
OrganizationModule,
ConfigModule.forRoot({ load: [configuration] }),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ export class PayloadDecoderExecutorService {
rawPayload: JSON
): string {
const vm2Logger = new Logger(`${PayloadDecoderExecutorService.name}-VM2`);

// Make copies of inputs to untrusted code to avoid unintended side effects if the code chooses to modify these
const iotDeviceCopy = JSON.parse(JSON.stringify(iotDevice));
const payloadCopy = JSON.parse(JSON.stringify(rawPayload));

const vm = new VM({
timeout: 5000,
sandbox: {
Expand Down
24 changes: 19 additions & 5 deletions src/services/data-management/payload-decoder-listener.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@ import { RecordMetadata } from "kafkajs";
import * as _ from "lodash";
import { KafkaService } from "../kafka/kafka.service";
import { PayloadDecoderExecutorService } from "./payload-decoder-executor.service";
import { IoTDeviceType } from "@enum/device-type.enum";
import { ChirpstackDeviceService } from "@services/chirpstack/chirpstack-device.service";

@Injectable()
export class PayloadDecoderListenerService extends AbstractKafkaConsumer {
constructor(
private connectionService: IoTDevicePayloadDecoderDataTargetConnectionService,
private chirpstackDeviceService: ChirpstackDeviceService,
private kafkaService: KafkaService,
private executor: PayloadDecoderExecutorService
) {
Expand Down Expand Up @@ -52,10 +55,10 @@ export class PayloadDecoderListenerService extends AbstractKafkaConsumer {
dto: RawIoTDeviceRequestDto
) {
const uniqueCombinations = _.uniqBy(connections.data, x => x.payloadDecoder?.id);
uniqueCombinations.forEach(async connection => {
for (const connection of uniqueCombinations) {
try {
const iotDevice = connection.iotDevices.find(
x => x.id == dto.iotDeviceId
x => x.id === dto.iotDeviceId
);

await this.decodeAndSendTransformed(
Expand All @@ -66,7 +69,7 @@ export class PayloadDecoderListenerService extends AbstractKafkaConsumer {
} catch (err) {
this.logger.error(err);
}
});
}
}

private async decodeAndSendTransformed(
Expand Down Expand Up @@ -99,10 +102,21 @@ export class PayloadDecoderListenerService extends AbstractKafkaConsumer {
`Decoding payload of IoT-Device ${relatedIoTDevice.id} with decoder ${payloadDecoder?.id}`
);

let localDevice = relatedIoTDevice;
// Check if lorawanSettings are read, if they are the iotDevice needs enrichment
if (
relatedIoTDevice.type === IoTDeviceType.LoRaWAN &&
payloadDecoder.decodingFunction.includes("lorawanSettings")
) {
localDevice = await this.chirpstackDeviceService.enrichLoRaWANDevice(
relatedIoTDevice
);
}

// Decode the payload
res = await this.executor.callUntrustedCode(
res = this.executor.callUntrustedCode(
payloadDecoder.decodingFunction,
relatedIoTDevice,
localDevice,
rawPayload
);

Expand Down
21 changes: 16 additions & 5 deletions src/services/data-targets/data-target.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,21 @@ import { OpenDataDkDataset } from "@entities/open-data-dk-dataset.entity";
import { dataTargetTypeMap } from "@enum/data-target-type-mapping";
import { DataTargetType } from "@enum/data-target-type.enum";
import { ErrorCodes } from "@enum/error-codes.enum";
import { BadRequestException, Inject, Injectable, Logger } from "@nestjs/common";
import {
BadRequestException,
forwardRef,
Inject,
Injectable,
Logger,
} from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { ApplicationService } from "@services/device-management/application.service";
import { OS2IoTMail } from "@services/os2iot-mail.service";
import { DeleteResult, Repository, SelectQueryBuilder } from "typeorm";
import { CLIENT_SECRET_PROVIDER, ClientSecretProvider } from "../../helpers/fiware-token.helper";
import {
CLIENT_SECRET_PROVIDER,
ClientSecretProvider,
} from "../../helpers/fiware-token.helper";
import { User } from "@entities/user.entity";

@Injectable()
Expand All @@ -27,9 +36,11 @@ export class DataTargetService {
private dataTargetRepository: Repository<DataTarget>,
@InjectRepository(User)
private userRepository: Repository<User>,
@Inject(forwardRef(() => ApplicationService))
private applicationService: ApplicationService,
@Inject(CLIENT_SECRET_PROVIDER) private clientSecretProvider: ClientSecretProvider,
private oS2IoTMail: OS2IoTMail,
@Inject(CLIENT_SECRET_PROVIDER)
private clientSecretProvider: ClientSecretProvider,
private oS2IoTMail: OS2IoTMail
) {}
private readonly logger = new Logger(DataTargetService.name);

Expand Down Expand Up @@ -97,7 +108,7 @@ export class DataTargetService {
): Promise<DataTarget[]> {
const res = await this.dataTargetRepository
.createQueryBuilder("dt")
.addSelect('dt.clientSecret')
.addSelect("dt.clientSecret")
.innerJoin(
"iot_device_payload_decoder_data_target_connection",
"con",
Expand Down
12 changes: 9 additions & 3 deletions src/services/device-management/application.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { OrganizationService } from "@services/user-management/organization.serv
import { PermissionService } from "@services/user-management/permission.service";
import { DeleteResult, In, Repository } from "typeorm";
import { MulticastService } from "./multicast.service";
import { DataTargetService } from "@services/data-targets/data-target.service";

@Injectable()
export class ApplicationService {
Expand All @@ -40,7 +41,9 @@ export class ApplicationService {
private multicastService: MulticastService,
private chirpstackDeviceService: ChirpstackDeviceService,
@Inject(forwardRef(() => PermissionService))
private permissionService: PermissionService
private permissionService: PermissionService,
@Inject(forwardRef(() => DataTargetService))
private dataTargetService: DataTargetService
) {}

async findAndCountInList(
Expand Down Expand Up @@ -262,7 +265,7 @@ export class ApplicationService {
async delete(id: number): Promise<DeleteResult> {
const application = await this.applicationRepository.findOne({
where: { id },
relations: ["iotDevices", "multicasts"],
relations: ["iotDevices", "multicasts", "dataTargets"],
});

// Don't allow delete if this application contains any sigfox devices.
Expand All @@ -274,6 +277,10 @@ export class ApplicationService {
throw new ConflictException(ErrorCodes.DeleteNotAllowedHasSigfoxDevice);
}

for (const dataTarget of application.dataTargets) {
await this.dataTargetService.delete(dataTarget.id);
}

// Delete all LoRaWAN devices in ChirpStack
const loRaWANDevices = application.iotDevices.filter(
device => device.type === IoTDeviceType.LoRaWAN
Expand All @@ -293,7 +300,6 @@ export class ApplicationService {
dbMulticast.lorawanMulticastDefinition.chirpstackGroupId
);
}

return this.applicationRepository.delete(id);
}

Expand Down
5 changes: 4 additions & 1 deletion src/services/user-management/permission.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,10 @@ export class PermissionService {
}
if (orgs) {
qb = qb.andWhere({ organization: In(orgs) });
} else if (query?.organisationId) {
} else if (
query?.organisationId !== undefined &&
query.organisationId !== "undefined"
) {
qb = qb.andWhere("org.id = :orgId", { orgId: +query.organisationId });
}

Expand Down
Loading