From 3b0a8eb5dc85c7d695cfe4fa79b95b8e82d24cc5 Mon Sep 17 00:00:00 2001 From: jaivalis Date: Wed, 21 Aug 2024 16:32:07 +0200 Subject: [PATCH] feat: added redirectUrl config and functionality (#126) * feat: added redirectUrl config and functionality Signed-off-by: jaivalis * chore: fixed changelog Signed-off-by: jaivalis * chore: renamed config Signed-off-by: jaivalis --------- Signed-off-by: jaivalis --- CHANGELOG.md | 4 + http/rr.http | 6 +- .../java/com/raccoon/user/RedirectConfig.java | 13 +++ .../com/raccoon/user/UserProfileResource.java | 18 ++- .../RaccoonUserProfileResourceIT.java | 103 +++++++++++++++++- .../scraper/lastfm/LastfmScraperTest.java | 9 -- 6 files changed, 136 insertions(+), 17 deletions(-) create mode 100644 release-raccoon-app/src/main/java/com/raccoon/user/RedirectConfig.java diff --git a/CHANGELOG.md b/CHANGELOG.md index d366cca3..d0ef026b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased](https://github.com/jaivalis/release-raccoon/compare/0.3.0...jdevelop) +## [0.3.1](https://github.com/jaivalis/release-raccoon/compare/0.3.0...0.3.1) - 21/08/2024 + +- Redirect on /me for UI + ## [0.3.0](https://github.com/jaivalis/release-raccoon/compare/0.2.7...0.3.0) - 01/08/2024 - default postgres db diff --git a/http/rr.http b/http/rr.http index 8eb2057b..560e9e15 100644 --- a/http/rr.http +++ b/http/rr.http @@ -1,8 +1,8 @@ ### Artists -GET {{ baseUrl }}/artists?page=0&size=10 +GET {{baseUrl}}/artists?page=0&size=10 ### Scrape releases -PUT {{ baseUrl }}/release-scrape +PUT {{baseUrl}}/release-scrape ### Notify -GET {{ baseUrl }}/notify-users +GET {{baseUrl}}/notify-users diff --git a/release-raccoon-app/src/main/java/com/raccoon/user/RedirectConfig.java b/release-raccoon-app/src/main/java/com/raccoon/user/RedirectConfig.java new file mode 100644 index 00000000..2d54d1cf --- /dev/null +++ b/release-raccoon-app/src/main/java/com/raccoon/user/RedirectConfig.java @@ -0,0 +1,13 @@ +package com.raccoon.user; + +import java.util.List; +import java.util.Optional; + +import io.smallrye.config.ConfigMapping; + +@ConfigMapping(prefix = "redirect") +public interface RedirectConfig { + + Optional> getWhitelistedUrls(); + +} diff --git a/release-raccoon-app/src/main/java/com/raccoon/user/UserProfileResource.java b/release-raccoon-app/src/main/java/com/raccoon/user/UserProfileResource.java index fd14e772..4093f07c 100644 --- a/release-raccoon-app/src/main/java/com/raccoon/user/UserProfileResource.java +++ b/release-raccoon-app/src/main/java/com/raccoon/user/UserProfileResource.java @@ -8,6 +8,8 @@ import org.jboss.resteasy.annotations.jaxrs.QueryParam; import java.net.URI; +import java.util.Collections; +import java.util.Objects; import java.util.Optional; import io.quarkus.oidc.IdToken; @@ -35,12 +37,14 @@ public class UserProfileResource { UserProfileService userProfileService; + RedirectConfig redirectConfig; @IdToken JsonWebToken idToken; @Inject - public UserProfileResource(final UserProfileService userProfileService) { + public UserProfileResource(final UserProfileService userProfileService, final RedirectConfig redirectConfig) { this.userProfileService = userProfileService; + this.redirectConfig = redirectConfig; } /** @@ -51,9 +55,13 @@ public UserProfileResource(final UserProfileService userProfileService) { @NoCache @Produces(MediaType.TEXT_HTML) @Transactional - public Response registrationCallback() { + public Response registrationCallback(@QueryParam("redirectUrl") String redirectUrl) { final String email = idToken.getClaim(EMAIL_CLAIM); userProfileService.completeRegistration(email); + if (shouldRedirect(redirectUrl)) { + log.info("Redirecting to "); + return Response.temporaryRedirect(URI.create(redirectUrl)).build(); + } return Response.ok(userProfileService.renderTemplateInstance(email)).build(); } @@ -109,4 +117,10 @@ public FollowedArtistsResponse getFollowedArtists() { return userProfileService.getFollowedArtists(email); } + private boolean shouldRedirect(String redirectUrl) { + return Objects.nonNull(redirectUrl) + && Objects.nonNull(redirectConfig.getWhitelistedUrls()) + && redirectConfig.getWhitelistedUrls().orElse(Collections.emptyList()).contains(redirectUrl); + } + } diff --git a/release-raccoon-app/src/test/java/com/raccoon/integration/resource/RaccoonUserProfileResourceIT.java b/release-raccoon-app/src/test/java/com/raccoon/integration/resource/RaccoonUserProfileResourceIT.java index c8a900a5..b8df07a7 100644 --- a/release-raccoon-app/src/test/java/com/raccoon/integration/resource/RaccoonUserProfileResourceIT.java +++ b/release-raccoon-app/src/test/java/com/raccoon/integration/resource/RaccoonUserProfileResourceIT.java @@ -7,6 +7,7 @@ import com.raccoon.entity.repository.UserArtistRepository; import com.raccoon.entity.repository.UserRepository; import com.raccoon.search.dto.SearchResultArtistDto; +import com.raccoon.user.RedirectConfig; import com.raccoon.user.UserProfileResource; import org.junit.jupiter.api.BeforeEach; @@ -14,8 +15,11 @@ import org.junit.jupiter.api.Test; import java.util.List; +import java.util.Optional; import io.quarkus.mailer.MockMailbox; +import io.quarkus.test.InjectMock; +import io.quarkus.test.Mock; import io.quarkus.test.TestTransaction; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.common.http.TestHTTPEndpoint; @@ -25,6 +29,9 @@ import io.quarkus.test.security.oidc.Claim; import io.quarkus.test.security.oidc.OidcSecurity; import io.restassured.http.ContentType; +import io.restassured.response.Response; +import io.smallrye.config.SmallRyeConfig; +import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.transaction.Transactional; @@ -32,8 +39,11 @@ import static io.restassured.RestAssured.given; import static org.apache.http.HttpStatus.SC_NO_CONTENT; import static org.apache.http.HttpStatus.SC_OK; +import static org.apache.http.HttpStatus.SC_TEMPORARY_REDIRECT; import static org.apache.http.HttpStatus.SC_UNAUTHORIZED; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; @QuarkusTest @TestHTTPEndpoint(UserProfileResource.class) @@ -54,6 +64,18 @@ class RaccoonUserProfileResourceIT { @Inject ArtistReleaseRepository artistReleaseRepository; + @Inject + SmallRyeConfig smallRyeConfig; + + @InjectMock + RedirectConfig redirectConfig; + + @ApplicationScoped + @Mock + RedirectConfig featuresConfig() { + return smallRyeConfig.getConfigMapping(RedirectConfig.class); + } + @BeforeEach @Transactional public void setup() { @@ -69,11 +91,86 @@ public void setup() { void getProfileOnce() { given() .contentType(ContentType.JSON) - .when().get() + .when() + .get() .then() .statusCode(SC_OK); - assertEquals(1, mockMailbox.getMailsSentTo("getProfileOnce@gmail.com").size()); + assertThat(mockMailbox.getMailsSentTo("getProfileOnce@gmail.com")) + .hasSize(1); + } + + @Test + @TestSecurity(user = EXISTING_USERNAME, roles = "user") + @OidcSecurity(claims = { + @Claim(key = EMAIL_CLAIM, value = "getProfileOnce@gmail.com") + }) + @DisplayName("get should redirect when url in whitelist") + void getWithRedirect_should_redirect_when_urlInWhitelist() { + String redirectUrl = "https://mock.url.from.whitelist"; + when(redirectConfig.getWhitelistedUrls()).thenReturn(Optional.of(List.of(redirectUrl))); + + Response response = given() + .queryParam("redirectUrl", redirectUrl) + .redirects().follow(false) + .contentType(ContentType.JSON) + .when() + .get() + .then() + .statusCode(SC_TEMPORARY_REDIRECT) + .extract() + .response(); + + assertThat(response.getHeader("Location")) + .isEqualTo(redirectUrl); + } + + @Test + @TestSecurity(user = EXISTING_USERNAME, roles = "user") + @OidcSecurity(claims = { + @Claim(key = EMAIL_CLAIM, value = "getProfileOnce@gmail.com") + }) + @DisplayName("get should not redirect when url not in whitelist") + void getWithRedirect_should_not_redirect_when_urlNotInWhitelist() { + String redirectUrl = "https://mock.url.from.whitelist"; + when(redirectConfig.getWhitelistedUrls()).thenReturn(Optional.of(List.of("not" + redirectUrl))); + + Response response = given() + .queryParam("redirectUrl", redirectUrl) + .contentType(ContentType.JSON) + .when() + .get() + .then() + .statusCode(SC_OK) + .extract() + .response(); + + assertThat(response.getHeader("Location")) + .isNull(); + } + + @Test + @TestSecurity(user = EXISTING_USERNAME, roles = "user") + @OidcSecurity(claims = { + @Claim(key = EMAIL_CLAIM, value = "getProfileOnce@gmail.com") + }) + @DisplayName("get should not redirect when url not in whitelist") + void getWithRedirect_should_not_redirect_when_whitelistEmpty() { + String redirectUrl = "https://mock.url.from.whitelist"; + when(redirectConfig.getWhitelistedUrls()).thenReturn(Optional.empty()); + + Response response = given() + .queryParam("redirectUrl", redirectUrl) + .contentType(ContentType.JSON) + .when() + .get() + .then() + .statusCode(SC_OK) + .extract() + .response(); + + assertThat(response.getHeader("Location")) + .isNull(); } @Test @@ -215,7 +312,7 @@ void getUserArtists() { .statusCode(SC_OK) .extract().body().jsonPath().getList("rows", ArtistDto.class); - assertEquals(1, list.size()); + assertThat(list).hasSize(1); assertEquals(artistDto.getName(), list.get(0).getName()); assertEquals(artistDto.getSpotifyUri(), list.get(0).getSpotifyUri()); assertEquals(artistDto.getLastfmUri(), list.get(0).getLastfmUri()); diff --git a/scraping/src/test/java/com/raccoon/scraper/lastfm/LastfmScraperTest.java b/scraping/src/test/java/com/raccoon/scraper/lastfm/LastfmScraperTest.java index d76e680d..d809881f 100644 --- a/scraping/src/test/java/com/raccoon/scraper/lastfm/LastfmScraperTest.java +++ b/scraping/src/test/java/com/raccoon/scraper/lastfm/LastfmScraperTest.java @@ -45,15 +45,6 @@ void setUp() { scraper = new LastfmScraper(artistFactoryMock, artistRepositoryMock, lastfmApiMock); } - private de.umass.lastfm.Artist stubArtist(int id) { - var uri = "uri" + id; - var name = "name" + id; - var lastfmArtist = mock(de.umass.lastfm.Artist.class); - when(lastfmArtist.getName()).thenReturn(name); - when(lastfmArtist.getUrl()).thenReturn(uri); - return lastfmArtist; - } - @Test @DisplayName("no play history") void scrapeTasteNoHistory() {