Skip to content
Draft
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
* [Running the Crons](#running-the-crons)
* [Troubleshooting](#troubleshooting)
* [Common Issues](#common-issues)
* [Monitoring and Logging](#monitoring-and-logging)
* [Application Insights](#application-insights)
* [License](#license)

## Introduction
Expand Down
2 changes: 1 addition & 1 deletion infrastructure/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ module "pre_api" {
api_mgmt_rg = "ss-${var.env}-network-rg"
api_mgmt_name = "sds-api-mgmt-${var.env}"
display_name = "Pre Recorded Evidence API"
revision = "106"
revision = "107"
product_id = module.pre_product[0].product_id
path = "pre-api"
service_url = local.apim_service_url
Expand Down
29 changes: 29 additions & 0 deletions pre-api-stg.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2250,6 +2250,35 @@ paths:
summary: Accept terms and conditions for a user
tags:
- terms-and-conditions-controller
'/admin/{id}':
get:
description: >-
Checks if a UUID exists in any of the tables: User, Recording,
CaptureSession, Booking, Case, Court.
operationId: checkUuid
parameters:
- format: uuid
in: path
name: id
required: true
type: string
- description: The User Id of the User making the request
format: uuid
in: header
name: X-User-Id
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

beware: user ID and X-User-Id are not quite the same. User ID = ID from users table. X-User-ID = ID from app_access or portal_access table. Or at least should be in theory, they do get a little bit interchanged.

required: false
type: string
x-example: 123e4567-e89b-12d3-a456-426614174000
produces:
- application/octet-stream
responses:
'200':
description: OK
schema:
type: string
summary: Check if a UUID exists in the system
tags:
- admin-controller
/app-terms-and-conditions/latest:
get:
operationId: getLatestTermsForApp
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package uk.gov.hmcts.reform.preapi.controllers;

import com.fasterxml.jackson.core.JsonProcessingException;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import uk.gov.hmcts.reform.preapi.controllers.params.TestingSupportRoles;
import uk.gov.hmcts.reform.preapi.dto.CreateUserDTO;
import uk.gov.hmcts.reform.preapi.util.FunctionalTestBase;

import java.util.Set;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;

public class AdminControllerFT extends FunctionalTestBase {

protected static final String ADMIN_ENDPOINT = "/admin";

@DisplayName("Should get back table type UUID belongs to")
@Test
void checkUuidExists() throws JsonProcessingException {
UUID randomUuid = UUID.randomUUID();
var user = createUserDto(randomUuid);
putUser(user);

var response =
doGetRequest(ADMIN_ENDPOINT + "/" + user.getId(), TestingSupportRoles.SUPER_USER);

assertThat(response.body().prettyPrint())
.isEqualTo("Uuid relates to a USER");
assertResponseCode(response, 200);
}

@DisplayName("Should return bad request when UUID not present in relevant tables")
@Test
void checkRequestFails() throws JsonProcessingException {

UUID randomUuid = UUID.randomUUID();

var response =
doGetRequest(ADMIN_ENDPOINT + "/" + randomUuid, TestingSupportRoles.SUPER_USER);

assertThat(response.body().jsonPath().getString("message"))
.isEqualTo("Not found: " + randomUuid + " does not exist in any relevant table");
assertResponseCode(response, 404);
}

@DisplayName("Should return not authorised due to role")
@Test
void checkIfUserAuthorised() throws JsonProcessingException {
UUID randomUuid = UUID.randomUUID();
var user = createUserDto(randomUuid);
putUser(user);

var response =
doGetRequest(ADMIN_ENDPOINT + "/" + user.getId(), TestingSupportRoles.LEVEL_1);

assertThat(response.body().jsonPath().getString("error"))
.isEqualTo("Forbidden");
assertResponseCode(response, 403);
}

private CreateUserDTO createUserDto(UUID uuid) {
var dto = new CreateUserDTO();
dto.setId(uuid);
dto.setEmail("[email protected]");
dto.setFirstName("Example");
dto.setLastName("User");
dto.setPortalAccess(Set.of());
dto.setAppAccess(Set.of());
dto.setOrganisation("Example Organisation");
dto.setPhoneNumber("1234567890");
return dto;
}

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package uk.gov.hmcts.reform.preapi.services.admin;

import jakarta.persistence.EntityManager;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import uk.gov.hmcts.reform.preapi.entities.Booking;
import uk.gov.hmcts.reform.preapi.entities.CaptureSession;
import uk.gov.hmcts.reform.preapi.entities.Case;
import uk.gov.hmcts.reform.preapi.entities.Court;
import uk.gov.hmcts.reform.preapi.entities.Recording;
import uk.gov.hmcts.reform.preapi.entities.Role;
import uk.gov.hmcts.reform.preapi.entities.User;
import uk.gov.hmcts.reform.preapi.enums.CourtType;
import uk.gov.hmcts.reform.preapi.enums.RecordingOrigin;
import uk.gov.hmcts.reform.preapi.exception.NotFoundException;
import uk.gov.hmcts.reform.preapi.repositories.admin.AdminRepository;
import uk.gov.hmcts.reform.preapi.util.HelperFactory;
import uk.gov.hmcts.reform.preapi.utils.IntegrationTestBase;

import java.sql.Timestamp;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

public class AdminServiceIT extends IntegrationTestBase {

@Autowired
AdminService adminService;

@Autowired
AdminRepository adminRepository;

@Autowired
protected EntityManager entityManager;

@Test
@Transactional
public void shouldCheckUuidExists() {
Timestamp now = new Timestamp(System.currentTimeMillis());
//Put a user in the database to test UUID against
User user = HelperFactory.createUser("Example", "One", "[email protected]", null, null, null);
entityManager.persist(user);
Court court = HelperFactory.createCourt(CourtType.CROWN, "Test Court", "Test123");
entityManager.persist(court);
Case case1 = HelperFactory.createCase(court, "null", true, now);
entityManager.persist(case1);
Booking booking = HelperFactory.createBooking(case1, now, now);
entityManager.persist(booking);
CaptureSession captureSession = HelperFactory.createCaptureSession(
booking, RecordingOrigin.PRE,
null, null, null, null, null,
null, null, null
);
entityManager.persist(captureSession);
Recording recording = HelperFactory.createRecording(captureSession, null, 1, "filename", null);
entityManager.persist(recording);

entityManager.flush();
UUID userGeneratedId = user.getId();
UUID bookingGeneratedId = booking.getId();
UUID caseGeneratedId = case1.getId();
UUID courtGeneratedId = court.getId();
UUID captureSessionGeneratedId = captureSession.getId();
UUID recordingGeneratedId = recording.getId();

assertThat(adminService.findUuidType(userGeneratedId)).isEqualTo(AdminService.UuidTableType.USER);
assertThat(adminService.findUuidType(bookingGeneratedId)).isEqualTo(AdminService.UuidTableType.BOOKING);
assertThat(adminService.findUuidType(caseGeneratedId)).isEqualTo(AdminService.UuidTableType.CASE);
assertThat(adminService.findUuidType(courtGeneratedId)).isEqualTo(AdminService.UuidTableType.COURT);
assertThat(adminService.findUuidType(captureSessionGeneratedId))
.isEqualTo(AdminService.UuidTableType.CAPTURE_SESSION);
assertThat(adminService.findUuidType(recordingGeneratedId)).isEqualTo(AdminService.UuidTableType.RECORDING);

}

@Test
@Transactional
public void shouldThrowExceptionWhenUuidNotFound() {

UUID randomUuid = UUID.randomUUID();

assertThatThrownBy(() -> adminService.findUuidType(randomUuid))
.isInstanceOf(NotFoundException.class);

}

@Test
@Transactional
public void shouldThrowExceptionWhenUuidIsNotInRelevantTable() {

Role role = HelperFactory.createRole("role");
entityManager.persist(role);

entityManager.flush();

UUID roleGeneratedId = role.getId();

assertThatThrownBy(() -> adminService.findUuidType(roleGeneratedId))
.isInstanceOf(NotFoundException.class);

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package uk.gov.hmcts.reform.preapi.controllers.admin;

import io.swagger.v3.oas.annotations.Operation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import uk.gov.hmcts.reform.preapi.services.admin.AdminService;

import java.util.UUID;

/**
* Helper endpoints intended for developers to use in troubleshooting/investigating
* incidents.
* Your user will need to have the role ROLE_SUPER_USER to access these endpoints.
*/
@RestController
@RequestMapping("/admin")
@PreAuthorize("hasRole('ROLE_SUPER_USER')")
public class AdminController {

private final AdminService adminService;

@Autowired
public AdminController(AdminService adminService) {
this.adminService = adminService;
}

/**
* Endpoint for getting back what type of item a UUID relates to.
* @param id UUID to search for
* @return returns a string
*/
@GetMapping("/{id}")
@Operation(operationId = "checkUuid", summary = "Check if a UUID exists in the system",
description = "Checks if a UUID exists in any of the tables: User, Recording, CaptureSession, "
+ "Booking, Case, Court.")
public ResponseEntity<String> checkUuidExists(@PathVariable UUID id) {
return ResponseEntity.ok("Uuid relates to a " + adminService.findUuidType(id));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We send back a pure string don't know if you want us to change to a valid json?

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package uk.gov.hmcts.reform.preapi.repositories.admin;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.Optional;
import java.util.UUID;

@Repository
public class AdminRepository {

private final JdbcTemplate jdbcTemplate;

public AdminRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

/**
* Searches tables to find what the given UUID is an ID for.
* Takes the given UUID id and passes it to JDBC Template to replace the placeholders ('?') in the query.
* The query uses SQL union all to select items across multiple tables (User, Recording, CaptureSession, Booking,
* Case, Court) that could contain the UUID. If found in that table, it returns a string
* e.g. if found in the users table, it will return "user". It will return the first match it finds.
* @param id the UUID to check for
* @return if found, an Optional containing the type of item the UUID relates to, otherwise empty Optional
*/
public Optional<String> findUuidType(UUID id) {
String query = """
(SELECT 'user' AS table_name FROM users WHERE id = ?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might need to optimise this if it gets slow, but we can do that at a later date

UNION ALL
SELECT 'recording' FROM recordings WHERE id = ?
UNION ALL
SELECT 'capture_session' FROM capture_sessions WHERE id = ?
UNION ALL
SELECT 'booking' FROM bookings WHERE id = ?
UNION ALL
SELECT 'case' FROM cases WHERE id = ?
UNION ALL
SELECT 'court' FROM courts WHERE id = ?)
LIMIT 1
""";

try {
String type = jdbcTemplate.queryForObject(query, String.class, id, id, id, id, id, id);
return Optional.ofNullable(type);
} catch (org.springframework.dao.EmptyResultDataAccessException e) {
return Optional.empty();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package uk.gov.hmcts.reform.preapi.services.admin;

import lombok.Getter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import uk.gov.hmcts.reform.preapi.exception.NotFoundException;
import uk.gov.hmcts.reform.preapi.repositories.admin.AdminRepository;

import java.util.Objects;
import java.util.UUID;

@Service
public class AdminService {

private final AdminRepository adminRepository;

@Autowired
public AdminService(AdminRepository adminRepository) {
this.adminRepository = adminRepository;
}

/**
* Checks if a UUID exists in relevant tables.
* Uses the AdminRepository to search the database for the UUID. The String returned from the repository
* is then converted to an enum value.
* @param id the UUID to check the tables for
* @return a string indicating the type of table related to the UUID
* @throws NotFoundException if the UUID does not exist in the tables being checked
*/
public UuidTableType findUuidType(UUID id) {

String tableName = adminRepository.findUuidType(Objects.requireNonNull(id, "UUID should not be null"))
.orElseThrow(() -> new NotFoundException(id + " does not exist in any relevant table"));

return UuidTableType.valueOf(tableName.toUpperCase());
}

/**
* Enum representing the tables the UUID could belong to. Restricts to relevant tables only.
*/
@Getter
public enum UuidTableType {
USER,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

app_access and portal_access would be useful too, as these are the X-User-Ids that sometimes pop up in e.g. the audit table

RECORDING,
CAPTURE_SESSION,
BOOKING,
CASE,
COURT
}
}
Loading