Skip to content

Commit d1c1fde

Browse files
augusthjerrildfcv-iteratorItAugustHA-Iterator
authored
Feature/iot 1433 uptimize access control (#285)
* Wip * More work * Last fixes * Package update * went through endpoints to verify no extra data should be shown * npm i * addd minimal on org * get one with security and removed minimal from org controller --------- Co-authored-by: Frederik Christ Vestergaard <[email protected]> Co-authored-by: August Andersen <[email protected]>
1 parent e191d03 commit d1c1fde

14 files changed

+114
-108
lines changed

src/controllers/admin-controller/chirpstack/chirpstack-gateway.controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ export class ChirpstackGatewayController {
126126

127127
@Put("updateGatewayOrganization/:id")
128128
@ApiProduces("application/json")
129-
@ApiOperation({ summary: "Create a new Chirpstack Gateway" })
129+
@ApiOperation({ summary: "Update gateway organization" })
130130
@ApiBadRequestResponse()
131131
@GatewayAdmin()
132132
async changeOrganization(

src/controllers/admin-controller/data-target-log.controller.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ import { ComposeAuthGuard } from "@auth/compose-auth.guard";
22
import { Read } from "@auth/roles.decorator";
33
import { RolesGuard } from "@auth/roles.guard";
44
import { ApiAuth } from "@auth/swagger-auth-decorator";
5+
import { AuthenticatedRequest } from "@dto/internal/authenticated-request";
56
import { DatatargetLog } from "@entities/datatarget-log.entity";
6-
import { Controller, Get, Param, ParseIntPipe, UseGuards } from "@nestjs/common";
7+
import { ApplicationAccessScope, checkIfUserHasAccessToApplication } from "@helpers/security-helper";
8+
import { Controller, Get, Param, ParseIntPipe, Req, UseGuards } from "@nestjs/common";
79
import { ApiForbiddenResponse, ApiTags, ApiUnauthorizedResponse } from "@nestjs/swagger";
810
import { InjectRepository } from "@nestjs/typeorm";
11+
import { DataTargetService } from "@services/data-targets/data-target.service";
912
import { Repository } from "typeorm";
1013

1114
@ApiTags("Data Target Logs")
@@ -18,11 +21,18 @@ import { Repository } from "typeorm";
1821
export class DatatargetLogController {
1922
constructor(
2023
@InjectRepository(DatatargetLog)
21-
private datatargetLogRepository: Repository<DatatargetLog>
24+
private datatargetLogRepository: Repository<DatatargetLog>,
25+
private dataTargetService: DataTargetService
2226
) {}
2327

2428
@Get(":datatargetId")
25-
async getDatatargetLogs(@Param("datatargetId", new ParseIntPipe()) datatargetId: number): Promise<DatatargetLog[]> {
29+
async getDatatargetLogs(
30+
@Req() req: AuthenticatedRequest,
31+
@Param("datatargetId", new ParseIntPipe()) datatargetId: number
32+
): Promise<DatatargetLog[]> {
33+
const dataTarget = await this.dataTargetService.findOne(datatargetId);
34+
checkIfUserHasAccessToApplication(req, dataTarget.application.id, ApplicationAccessScope.Read);
35+
2636
return await this.datatargetLogRepository.find({
2737
where: {
2838
datatarget: { id: datatargetId },

src/controllers/admin-controller/data-target.controller.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,18 @@ export class DataTargetController {
7575
@Get(":id")
7676
@ApiOperation({ summary: "Find DataTarget by id" })
7777
async findOne(@Req() req: AuthenticatedRequest, @Param("id", new ParseIntPipe()) id: number): Promise<DataTarget> {
78+
let dataTarget;
79+
try {
80+
dataTarget = await this.dataTargetService.findOneWithHasRecentError(id);
81+
} catch (err) {
82+
throw new NotFoundException(ErrorCodes.IdDoesNotExists);
83+
}
84+
7885
try {
79-
const dataTarget = await this.dataTargetService.findOneWithHasRecentError(id);
8086
checkIfUserHasAccessToApplication(req, dataTarget.application.id, ApplicationAccessScope.Read);
8187
return dataTarget;
8288
} catch (err) {
83-
throw new NotFoundException(ErrorCodes.IdDoesNotExists);
89+
throw err;
8490
}
8591
}
8692

@@ -197,8 +203,13 @@ export class DataTargetController {
197203

198204
@Post("testDataTarget")
199205
@ApiOperation({ summary: "Send a ping or test data packet to a data target" })
200-
async testDataTarget(@Body() testDto: TestDataTargetDto): Promise<TestDataTargetResultDto> {
206+
async testDataTarget(
207+
@Req() req: AuthenticatedRequest,
208+
@Body() testDto: TestDataTargetDto
209+
): Promise<TestDataTargetResultDto> {
210+
const dataTarget = await this.dataTargetService.findOne(testDto.dataTargetId);
211+
checkIfUserHasAccessToApplication(req, dataTarget.application.id, ApplicationAccessScope.Read);
201212
// Send package
202-
return await this.dataTargetService.testDataTarget(testDto);
213+
return await this.dataTargetService.testDataTarget(testDto, dataTarget);
203214
}
204215
}

src/controllers/admin-controller/iot-device-payload-decoder-data-target-connection.controller.ts

Lines changed: 1 addition & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -56,38 +56,6 @@ export class IoTDevicePayloadDecoderDataTargetConnectionController {
5656
private iotDeviceService: IoTDeviceService
5757
) {}
5858

59-
@Get()
60-
@ApiProduces("application/json")
61-
@ApiOperation({
62-
summary: "Find all connections between IoT-Devices, PayloadDecoders and DataTargets (paginated)",
63-
})
64-
@ApiResponse({
65-
status: 200,
66-
description: "Success",
67-
type: ListAllApplicationsResponseDto,
68-
})
69-
async findAll(
70-
@Req() req: AuthenticatedRequest,
71-
@Query() query?: ListAllEntitiesDto
72-
): Promise<ListAllConnectionsResponseDto> {
73-
if (req.user.permissions.isGlobalAdmin) {
74-
return await this.service.findAndCountWithPagination(query);
75-
} else {
76-
const allowed = req.user.permissions.getAllApplicationsWithAtLeastRead();
77-
return await this.service.findAndCountWithPagination(query, allowed);
78-
}
79-
}
80-
81-
@Get(":id")
82-
@ApiNotFoundResponse({
83-
description: "If the id of the entity doesn't exist",
84-
})
85-
async findOne(
86-
@Req() req: AuthenticatedRequest,
87-
@Param("id", new ParseIntPipe()) id: number
88-
): Promise<IoTDevicePayloadDecoderDataTargetConnection> {
89-
return await this.service.findOne(id);
90-
}
9159

9260
@Get("byIoTDevice/:id")
9361
@ApiOperation({
@@ -104,24 +72,6 @@ export class IoTDevicePayloadDecoderDataTargetConnectionController {
10472
}
10573
}
10674

107-
@Get("byPayloadDecoder/:id")
108-
@ApiOperation({
109-
summary: "Find all connections by PayloadDecoder id",
110-
})
111-
async findByPayloadDecoderId(
112-
@Req() req: AuthenticatedRequest,
113-
@Param("id", new ParseIntPipe()) id: number
114-
): Promise<ListAllConnectionsResponseDto> {
115-
if (req.user.permissions.isGlobalAdmin) {
116-
return await this.service.findAllByPayloadDecoderId(id);
117-
} else {
118-
return await this.service.findAllByPayloadDecoderId(
119-
id,
120-
req.user.permissions.getAllOrganizationsWithAtLeastApplicationRead()
121-
);
122-
}
123-
}
124-
12575
@Get("byDataTarget/:id")
12676
@ApiOperation({
12777
summary: "Find all connections by DataTarget id",
@@ -196,7 +146,7 @@ export class IoTDevicePayloadDecoderDataTargetConnectionController {
196146
}
197147

198148
private async checkIfUpdateIsAllowed(updateDto: UpdateConnectionDto, req: AuthenticatedRequest, id: number) {
199-
const newIotDevice = await this.iotDeviceService.findOne(updateDto.iotDeviceIds[0]);
149+
const newIotDevice = await this.iotDeviceService.findOneWithApplicationAndMetadata(updateDto.iotDeviceIds[0]);
200150
checkIfUserHasAccessToApplication(req, newIotDevice.application.id, ApplicationAccessScope.Write);
201151
const oldConnection = await this.service.findOne(id);
202152
await this.checkUserHasWriteAccessToAllIotDevices(updateDto.iotDeviceIds, req);

src/controllers/admin-controller/iot-device-payload-decoder.controller.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,29 @@ export class IoTDevicePayloadDecoderController {
3131
@Query() query: PayloadDecoderIoDeviceMinimalQuery
3232
): Promise<ListAllIoTDevicesMinimalResponseDto> {
3333
try {
34-
return await this.iotDeviceService.findAllByPayloadDecoder(req, payloadDecoderId, +query.limit, +query.offset);
34+
const iotDevices = await this.iotDeviceService.findAllByPayloadDecoder(
35+
req,
36+
payloadDecoderId,
37+
+query.limit,
38+
+query.offset
39+
);
40+
41+
if (req.user.permissions.isGlobalAdmin) {
42+
return iotDevices;
43+
}
44+
45+
const allowedAppIds = req.user.permissions.getAllApplicationsWithAtLeastRead();
46+
47+
const filteredIotDevices = iotDevices.data.filter(device =>
48+
allowedAppIds.find(appId => appId === device.applicationId)
49+
);
50+
51+
const response: ListAllIoTDevicesMinimalResponseDto = {
52+
data: filteredIotDevices,
53+
count: filteredIotDevices.length,
54+
};
55+
56+
return response;
3557
} catch (err) {
3658
throw new NotFoundException(ErrorCodes.IdDoesNotExists);
3759
}

src/controllers/admin-controller/iot-device.controller.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,7 @@ export class IoTDeviceController {
433433
return new StreamableFile(csvFile);
434434
} catch (err) {
435435
this.logger.error(err);
436+
throw err;
436437
}
437438
}
438439
}

src/controllers/admin-controller/test-payload-decoder.controller.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import { AuthenticatedRequest } from "@dto/internal/authenticated-request";
12
import { TestPayloadDecoderDto } from "@dto/test-payload-decoder.dto";
2-
import { BadRequestException, Body, Controller, Post } from "@nestjs/common";
3+
import { BadRequestException, Body, Controller, Post, Req } from "@nestjs/common";
34
import { ApiOperation, ApiTags } from "@nestjs/swagger";
45
import { PayloadDecoderExecutorService } from "@services/data-management/payload-decoder-executor.service";
56

src/controllers/user-management/new-kombit-creation.controller.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import {
1919
Param,
2020
ParseIntPipe,
2121
Put,
22-
Query,
2322
Req,
2423
UseGuards,
2524
} from "@nestjs/common";
@@ -35,6 +34,7 @@ import { OrganizationService } from "@services/user-management/organization.serv
3534
import { PermissionService } from "@services/user-management/permission.service";
3635
import { UserService } from "@services/user-management/user.service";
3736
import { ApiAuth } from "@auth/swagger-auth-decorator";
37+
import { checkIfUserHasAccessToUser } from "@helpers/security-helper";
3838

3939
@UseGuards(JwtAuthGuard)
4040
@ApiAuth()
@@ -133,19 +133,28 @@ export class NewKombitCreationController {
133133

134134
@Get(":id")
135135
@ApiOperation({ summary: "Get one user" })
136-
async find(
137-
@Param("id", new ParseIntPipe()) id: number,
138-
@Query("extendedInfo") extendedInfo?: boolean
139-
): Promise<UserResponseDto> {
140-
const getExtendedInfo = extendedInfo != null ? extendedInfo : false;
136+
async find(@Req() req: AuthenticatedRequest, @Param("id", new ParseIntPipe()) id: number): Promise<UserResponseDto> {
137+
let dbUser;
138+
139+
try {
140+
dbUser = await this.userService.findOne(id);
141+
} catch (err) {
142+
throw new NotFoundException(ErrorCodes.IdDoesNotExists);
143+
}
144+
141145
try {
146+
checkIfUserHasAccessToUser(req, dbUser);
147+
148+
dbUser.permissions.forEach(perm => {
149+
delete perm.organization;
150+
});
151+
142152
// Don't leak the passwordHash
143-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
144-
const { passwordHash, ...user } = await this.userService.findOne(id, getExtendedInfo);
153+
const { passwordHash: _, ...user } = dbUser;
145154

146155
return user;
147156
} catch (err) {
148-
throw new NotFoundException(ErrorCodes.IdDoesNotExists);
157+
throw err;
149158
}
150159
}
151160
}

src/controllers/user-management/organization.controller.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,6 @@ export class OrganizationController {
8989
}
9090
}
9191

92-
@Get("minimal")
93-
@ApiOperation({
94-
summary: "Get list of the minimal representation of organizations, i.e. id and name.",
95-
})
96-
@Read()
97-
async findAllMinimal(): Promise<ListAllMinimalOrganizationsResponseDto> {
98-
return await this.organizationService.findAllMinimal();
99-
}
100-
10192
@Get()
10293
@ApiOperation({ summary: "Get list of all Organizations" })
10394
@UserAdmin()

src/controllers/user-management/user.controller.ts

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { UserResponseDto } from "@dto/user-response.dto";
2828
import { ErrorCodes } from "@entities/enum/error-codes.enum";
2929
import {
3030
checkIfUserHasAccessToOrganization,
31+
checkIfUserHasAccessToUser,
3132
checkIfUserIsGlobalAdmin,
3233
OrganizationAccessScope,
3334
} from "@helpers/security-helper";
@@ -54,12 +55,6 @@ export class UserController {
5455

5556
constructor(private userService: UserService, private organizationService: OrganizationService) {}
5657

57-
@Get("minimal")
58-
@ApiOperation({ summary: "Get all id,names of users" })
59-
async findAllMinimal(): Promise<ListAllUsersMinimalResponseDto> {
60-
return await this.userService.findAllMinimal();
61-
}
62-
6358
@Post()
6459
@ApiOperation({ summary: "Create a new User" })
6560
async create(@Req() req: AuthenticatedRequest, @Body() createUserDto: CreateUserDto): Promise<UserResponseDto> {
@@ -189,18 +184,28 @@ export class UserController {
189184

190185
@Get(":id")
191186
@ApiOperation({ summary: "Get one user" })
192-
async find(
193-
@Param("id", new ParseIntPipe()) id: number,
194-
@Query("extendedInfo") extendedInfo?: boolean
195-
): Promise<UserResponseDto> {
196-
const getExtendedInfo = extendedInfo != null ? extendedInfo : false;
187+
async find(@Req() req: AuthenticatedRequest, @Param("id", new ParseIntPipe()) id: number): Promise<UserResponseDto> {
188+
let dbUser;
189+
190+
try {
191+
dbUser = await this.userService.findOne(id);
192+
} catch (err) {
193+
throw new NotFoundException(ErrorCodes.IdDoesNotExists);
194+
}
195+
197196
try {
197+
checkIfUserHasAccessToUser(req, dbUser);
198+
199+
dbUser.permissions.forEach(perm => {
200+
delete perm.organization;
201+
});
202+
198203
// Don't leak the passwordHash
199-
const { passwordHash: _, ...user } = await this.userService.findOne(id, getExtendedInfo);
204+
const { passwordHash: _, ...user } = dbUser;
200205

201206
return user;
202207
} catch (err) {
203-
throw new NotFoundException(ErrorCodes.IdDoesNotExists);
208+
throw err;
204209
}
205210
}
206211

@@ -213,13 +218,13 @@ export class UserController {
213218
@Param("organizationId", new ParseIntPipe()) organizationId: number,
214219
@Query() query?: ListAllEntitiesDto
215220
): Promise<ListAllUsersResponseDto> {
216-
try {
217-
// Check if user has access to organization
218-
if (!req.user.permissions.hasUserAdminOnOrganization(organizationId)) {
219-
throw new ForbiddenException("User does not have org admin permissions for this organization");
220-
}
221+
// Check if user has access to organization
222+
if (!req.user.permissions.hasUserAdminOnOrganization(organizationId)) {
223+
throw new ForbiddenException("User does not have org admin permissions for this organization");
224+
}
221225

222-
// Get user objects
226+
// Get user objects
227+
try {
223228
return await this.userService.getUsersOnOrganization(organizationId, query);
224229
} catch (err) {
225230
throw new NotFoundException(ErrorCodes.IdDoesNotExists);

0 commit comments

Comments
 (0)