Skip to content

Commit

Permalink
GAP-2349: Fix open redirects (#81)
Browse files Browse the repository at this point in the history
* initial

* add tests

* throw err when slug not in contentful

* change log method

* remove one liner insane variable

* mv typecast
  • Loading branch information
john-tco authored Dec 19, 2023
1 parent 4d98f2c commit eef19d2
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package gov.cabinetoffice.gap.applybackend.service;


import com.contentful.java.cda.CDAArray;
import com.contentful.java.cda.CDAClient;
import com.contentful.java.cda.CDAEntry;
import com.contentful.java.cda.CDAResourceNotFoundException;
import com.contentful.java.cda.*;
import gov.cabinetoffice.gap.applybackend.dto.api.GetGrantAdvertDto;
import gov.cabinetoffice.gap.applybackend.dto.api.GetGrantMandatoryQuestionDto;
import gov.cabinetoffice.gap.applybackend.enums.GrantAdvertStatus;
Expand All @@ -20,6 +17,12 @@
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@Service
@RequiredArgsConstructor
@Slf4j
Expand Down Expand Up @@ -86,6 +89,35 @@ public boolean advertExistsInContentful(final String advertSlug) {
return advertExists;
}

private String getGrantWebpageUrl(final CDAArray contentfulEntry){
CDAEntry entry = ((CDAEntry) contentfulEntry.items().get(0));
Map<String, Object> rawFields = entry.rawFields();
Optional<String> optionalUrl = ((Map<String, String>) rawFields.get("grantWebpageUrl")).values().stream().findFirst();
if(optionalUrl.isEmpty()){
throw new NotFoundException("Grant webpage url not found");
}
return optionalUrl.get();
}

public void validateGrantWebpageUrl(final String contentfulSlug, final String grantWebpageUrl) {
try {
final CDAArray contentfulEntry = contentfulDeliveryClient
.fetch(CDAEntry.class)
.withContentType("grantDetails")
.where("fields.label", contentfulSlug).all();

String url = this.getGrantWebpageUrl(contentfulEntry);

if (!url.equals(grantWebpageUrl)) {
throw new NotFoundException("Grant webpage url does not match the url in contentful");
}
} catch (CDAResourceNotFoundException error) {
log.error(String.format("Advert with slug %s not found in Contentful", contentfulSlug));
throw error;
}
}


public GrantAdvert getAdvertBySchemeId(String schemeId) {
final GrantAdvert grantAdvert = grantAdvertRepository.findBySchemeId(Integer.parseInt(schemeId))
.orElseThrow(() -> new NotFoundException("Advert with schemeId " + schemeId + " not found"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.springframework.web.bind.annotation.RestController;

import javax.validation.constraints.NotBlank;
import java.net.URL;

@Slf4j
@RequiredArgsConstructor
Expand Down Expand Up @@ -99,14 +100,14 @@ public ResponseEntity<GetGrantAdvertDto> generateGetGrantAdvertDtoFromSchemeId(@
return ResponseEntity.ok(grantAdvertService.generateGetGrantAdvertDto(advert, mandatoryQuestionsDto));
}


@GetMapping("{advertSlug}/exists-in-contentful")
@Operation(summary = "Check whether a grant advert exists in Contentful with the given slug")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Successfully got grant advert with contentful slug provided"),
@ApiResponse(responseCode = "400", description = "Required path variable not provided in expected format",
content = @Content(mediaType = "application/json"))
})

public ResponseEntity<GetContentfulAdvertExistsDto> advertExistsInContentful(@PathVariable final String advertSlug) {
final boolean advertExists = grantAdvertService.advertExistsInContentful(advertSlug);

Expand All @@ -117,4 +118,13 @@ public ResponseEntity<GetContentfulAdvertExistsDto> advertExistsInContentful(@Pa
.build()
);
}

@GetMapping("/validate-grant-webpage-url")
@Operation(summary = "Check if a grant webpage url matches the grant-webpage-url attached to the slug")
public ResponseEntity<String> validateGrantWebpageUrl(
@RequestParam @NotBlank String contentfulSlug,@RequestParam @NotBlank String grantWebpageUrl) {
grantAdvertService.validateGrantWebpageUrl(contentfulSlug, grantWebpageUrl);

return ResponseEntity.ok("Success");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.HashMap;
Expand All @@ -31,7 +32,10 @@
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
Expand All @@ -52,6 +56,9 @@ class GrantAdvertServiceTest {
@Mock
private AbsQuery query;

@Mock
CDAEntry mockCDAEntry;

@Mock
private FetchQuery fetchQuery;

Expand Down Expand Up @@ -423,4 +430,54 @@ void getAdvertBySchemeId_throwsNotFoundException() {
assertThrows(NotFoundException.class, () -> grantAdvertService.getAdvertBySchemeId(schemeId));
}
}
}

@Nested
class validateGrantWebpageUrl {
@Test
void SuccessfullyValidatesWebpageUrl() {
final String advertSlug = "chargepoint-grant-for-homeowners-1";
final String grantWebpageUrl = "https://example.domain.org/some/deeper/path";
final Map<String, String> entry = new HashMap<>();
final Map<String, Object> entries = new HashMap<>();
entry.put("0", grantWebpageUrl);
entries.put("grantWebpageUrl", entry);
when((mockCDAEntry).rawFields()).thenReturn(entries);
when(contentfulDeliveryClient.fetch(CDAEntry.class))
.thenReturn(fetchQuery);
when(fetchQuery.withContentType("grantDetails"))
.thenReturn(fetchQuery);
when(fetchQuery.where("fields.label", advertSlug))
.thenReturn(fetchQuery);
when(fetchQuery.all())
.thenReturn(contentfulResults);
when(contentfulResults.items())
.thenReturn(List.of(mockCDAEntry));

assertThatNoException().isThrownBy(() -> grantAdvertService.validateGrantWebpageUrl(advertSlug, grantWebpageUrl));
}

@Test
void throwsNotFound__WhenProvidedInvalidWebpageUrl() {
final String advertSlug = "chargepoint-grant-for-homeowners-1";
final String grantWebpageUrl = "https://malicious.domain.org/path";
final String contentfulGrantWebpageUrl = "https://example.domain.org/some/deeper/path";
final Map<String, String> entry = new HashMap<>();
final Map<String, Object> entries = new HashMap<>();
entry.put("0", contentfulGrantWebpageUrl);
entries.put("grantWebpageUrl", entry);
when((mockCDAEntry).rawFields()).thenReturn(entries);
when(contentfulDeliveryClient.fetch(CDAEntry.class))
.thenReturn(fetchQuery);
when(fetchQuery.withContentType("grantDetails"))
.thenReturn(fetchQuery);
when(fetchQuery.where("fields.label", advertSlug))
.thenReturn(fetchQuery);
when(fetchQuery.all())
.thenReturn(contentfulResults);
when(contentfulResults.items())
.thenReturn(List.of(mockCDAEntry));

assertThrows(NotFoundException.class, () -> grantAdvertService.validateGrantWebpageUrl(advertSlug, grantWebpageUrl));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class GrantAdvertControllerTest {
Expand Down Expand Up @@ -204,4 +204,33 @@ void generateGetGrantAdvertDtoFromSchemeId_ThrowsAnyOtherKindOfException() {
assertThrows(IllegalArgumentException.class, () -> grantAdvertController.generateGetGrantAdvertDtoFromSchemeId(String.valueOf(schemeId)));
}
}

@Nested
class validateGrantWebpageUrl {
@Test
void validatesGrantWebpageUrl_returnsSuccessWithValidArgs(){
final String grantWebpageUrl = "https://www.example.com/external-advert";
final String contentfulSlug = "internal-contentful-slug";
doNothing().when(grantAdvertService).validateGrantWebpageUrl(grantWebpageUrl, contentfulSlug);
ResponseEntity<String> response = grantAdvertController.validateGrantWebpageUrl(grantWebpageUrl, contentfulSlug);

assertThat(response).isEqualTo(ResponseEntity.ok("Success"));
}

@Test
void validatesGrantWebpageUrl_returnsNotFound(){
final String grantWebpageUrl = "https://www.maliciousdomain.com/extenal";
final String contentfulSlug = "internal-contentful-slug";
doThrow(NotFoundException.class).when(grantAdvertService).validateGrantWebpageUrl(grantWebpageUrl, contentfulSlug);
assertThrows(NotFoundException.class, ()-> grantAdvertController.validateGrantWebpageUrl(grantWebpageUrl, contentfulSlug));
}

@Test
void validatesGrantWebpageUrl_ThrowsNotFoundException(){
final String grantWebpageUrl = "https://www.maliciousdomain.com/extenal";
final String contentfulSlug = "internal-contentful-slug";
doThrow(NotFoundException.class).when(grantAdvertService).validateGrantWebpageUrl(grantWebpageUrl, contentfulSlug);
assertThrows(NotFoundException.class, ()-> grantAdvertController.validateGrantWebpageUrl(grantWebpageUrl, contentfulSlug));
}
}
}

0 comments on commit eef19d2

Please sign in to comment.