Skip to content

Commit

Permalink
add tests for the security layer
Browse files Browse the repository at this point in the history
  • Loading branch information
Athou committed Dec 16, 2023
1 parent cb4a8df commit 351701d
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 126 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,7 @@
@ExtendWith(MockServerExtension.class)
public abstract class BaseIT {

private static final CommaFeedDropwizardAppExtension EXT = new CommaFeedDropwizardAppExtension() {
@Override
protected JerseyClientBuilder clientBuilder() {
return super.clientBuilder().register(HttpAuthenticationFeature.basic("admin", "admin")).register(MultiPartFeature.class);
}
};
private final CommaFeedDropwizardAppExtension extension = buildExtension();

private Client client;

Expand All @@ -55,17 +50,26 @@ protected JerseyClientBuilder clientBuilder() {

private String webSocketUrl;

protected CommaFeedDropwizardAppExtension buildExtension() {
return new CommaFeedDropwizardAppExtension() {
@Override
protected JerseyClientBuilder clientBuilder() {
return super.clientBuilder().register(HttpAuthenticationFeature.basic("admin", "admin")).register(MultiPartFeature.class);
}
};
}

@BeforeEach
void init(MockServerClient mockServerClient) throws IOException {
URL resource = Objects.requireNonNull(getClass().getResource("/feed/rss.xml"));
mockServerClient.when(HttpRequest.request().withMethod("GET").withPath("/"))
.respond(HttpResponse.response().withBody(IOUtils.toString(resource, StandardCharsets.UTF_8)));

this.client = EXT.client();
this.client = extension.client();
this.feedUrl = "http://localhost:" + mockServerClient.getPort() + "/";
this.baseUrl = "http://localhost:" + EXT.getLocalPort() + "/";
this.baseUrl = "http://localhost:" + extension.getLocalPort() + "/";
this.apiBaseUrl = this.baseUrl + "rest/";
this.webSocketUrl = "ws://localhost:" + EXT.getLocalPort() + "/ws";
this.webSocketUrl = "ws://localhost:" + extension.getLocalPort() + "/ws";
}

protected String login() {
Expand All @@ -82,10 +86,7 @@ protected Long subscribe(String feedUrl) {
SubscribeRequest subscribeRequest = new SubscribeRequest();
subscribeRequest.setUrl(feedUrl);
subscribeRequest.setTitle("my title for this feed");
try (Response response = client.target(apiBaseUrl + "feed/subscribe").request().post(Entity.json(subscribeRequest))) {
Assertions.assertEquals(HttpStatus.OK_200, response.getStatus());
return response.readEntity(Long.class);
}
return client.target(apiBaseUrl + "feed/subscribe").request().post(Entity.json(subscribeRequest), Long.class);
}

protected Long subscribeAndWaitForEntries(String feedUrl) {
Expand All @@ -95,10 +96,7 @@ protected Long subscribeAndWaitForEntries(String feedUrl) {
}

protected Subscription getSubscription(Long subscriptionId) {
try (Response response = client.target(apiBaseUrl + "feed/get/{id}").resolveTemplate("id", subscriptionId).request().get()) {
Assertions.assertEquals(HttpStatus.OK_200, response.getStatus());
return response.readEntity(Subscription.class);
}
return client.target(apiBaseUrl + "feed/get/{id}").resolveTemplate("id", subscriptionId).request().get(Subscription.class);
}

protected Entries getFeedEntries(long subscriptionId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.commafeed.integration;

import java.util.Base64;

import javax.ws.rs.client.Entity;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;

import org.eclipse.jetty.http.HttpStatus;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import com.commafeed.CommaFeedDropwizardAppExtension;
import com.commafeed.frontend.model.Entries;
import com.commafeed.frontend.model.UserModel;
import com.commafeed.frontend.model.request.ProfileModificationRequest;
import com.commafeed.frontend.model.request.SubscribeRequest;

class SecurityIT extends BaseIT {

@Override
protected CommaFeedDropwizardAppExtension buildExtension() {
// override so we don't add http basic auth
return new CommaFeedDropwizardAppExtension();
}

@Test
void notLoggedIn() {
try (Response response = getClient().target(getApiBaseUrl() + "user/profile").request().get()) {
Assertions.assertEquals(HttpStatus.UNAUTHORIZED_401, response.getStatus());
}
}

@Test
void wrongPassword() {
String auth = "Basic " + Base64.getEncoder().encodeToString("admin:wrong-password".getBytes());
try (Response response = getClient().target(getApiBaseUrl() + "user/profile")
.request()
.header(HttpHeaders.AUTHORIZATION, auth)
.get()) {
Assertions.assertEquals(HttpStatus.UNAUTHORIZED_401, response.getStatus());
}
}

@Test
void missingRole() {
String auth = "Basic " + Base64.getEncoder().encodeToString("demo:demo".getBytes());
try (Response response = getClient().target(getApiBaseUrl() + "admin/settings")
.request()
.header(HttpHeaders.AUTHORIZATION, auth)
.get()) {
Assertions.assertEquals(HttpStatus.FORBIDDEN_403, response.getStatus());
}
}

@Test
void apiKey() {
String auth = "Basic " + Base64.getEncoder().encodeToString("admin:admin".getBytes());

// create api key
ProfileModificationRequest req = new ProfileModificationRequest();
req.setCurrentPassword("admin");
req.setNewApiKey(true);
getClient().target(getApiBaseUrl() + "user/profile")
.request()
.header(HttpHeaders.AUTHORIZATION, auth)
.post(Entity.json(req))
.close();

// fetch api key
String apiKey = getClient().target(getApiBaseUrl() + "user/profile")
.request()
.header(HttpHeaders.AUTHORIZATION, auth)
.get(UserModel.class)
.getApiKey();

// subscribe to a feed
SubscribeRequest subscribeRequest = new SubscribeRequest();
subscribeRequest.setUrl(getFeedUrl());
subscribeRequest.setTitle("my title for this feed");
long subscriptionId = getClient().target(getApiBaseUrl() + "feed/subscribe")
.request()
.header(HttpHeaders.AUTHORIZATION, auth)
.post(Entity.json(subscribeRequest), Long.class);

// get entries with api key
Entries entries = getClient().target(getApiBaseUrl() + "feed/entries")
.queryParam("id", subscriptionId)
.queryParam("readType", "unread")
.queryParam("apiKey", apiKey)
.request()
.get(Entries.class);
Assertions.assertEquals("my title for this feed", entries.getName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
import java.util.List;

import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Response;

import org.eclipse.jetty.http.HttpStatus;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
Expand All @@ -21,10 +19,8 @@ class AdminIT extends BaseIT {

@Test
void getApplicationSettings() {
try (Response response = getClient().target(getApiBaseUrl() + "admin/settings").request().get()) {
ApplicationSettings settings = response.readEntity(ApplicationSettings.class);
Assertions.assertTrue(settings.getAllowRegistrations());
}
ApplicationSettings settings = getClient().target(getApiBaseUrl() + "admin/settings").request().get(ApplicationSettings.class);
Assertions.assertTrue(settings.getAllowRegistrations());
}

@Nested
Expand All @@ -37,45 +33,38 @@ void saveThenDeleteNewUser() {
user.setName("test");
user.setPassword("test".getBytes());
user.setEmail("[email protected]");
getClient().target(getApiBaseUrl() + "admin/user/save").request().post(Entity.json(user), Void.TYPE);

try (Response response = getClient().target(getApiBaseUrl() + "admin/user/save").request().post(Entity.json(user))) {
Assertions.assertEquals(HttpStatus.OK_200, response.getStatus());

List<UserModel> newUsers = getAllUsers();
Assertions.assertEquals(existingUsers.size() + 1, newUsers.size());
List<UserModel> newUsers = getAllUsers();
Assertions.assertEquals(existingUsers.size() + 1, newUsers.size());

UserModel newUser = newUsers.stream().filter(u -> u.getName().equals("test")).findFirst().get();
user.setId(newUser.getId());
}
UserModel newUser = newUsers.stream()
.filter(u -> u.getName().equals("test"))
.findFirst()
.orElseThrow(() -> new NullPointerException("User not found"));
user.setId(newUser.getId());

IDRequest req = new IDRequest();
req.setId(user.getId());
try (Response response = getClient().target(getApiBaseUrl() + "admin/user/delete").request().post(Entity.json(req))) {
Assertions.assertEquals(HttpStatus.OK_200, response.getStatus());

List<UserModel> newUsers = getAllUsers();
Assertions.assertEquals(existingUsers.size(), newUsers.size());
}
getClient().target(getApiBaseUrl() + "admin/user/delete").request().post(Entity.json(req), Void.TYPE);
Assertions.assertEquals(existingUsers.size(), getAllUsers().size());
}

@Test
void editExistingUser() {
List<UserModel> existingUsers = getAllUsers();
UserModel user = existingUsers.stream().filter(u -> u.getName().equals("admin")).findFirst().get();
UserModel user = existingUsers.stream()
.filter(u -> u.getName().equals("admin"))
.findFirst()
.orElseThrow(() -> new NullPointerException("User not found"));
user.setEmail("[email protected]");

try (Response response = getClient().target(getApiBaseUrl() + "admin/user/save").request().post(Entity.json(user))) {
Assertions.assertEquals(HttpStatus.OK_200, response.getStatus());

List<UserModel> newUsers = getAllUsers();
Assertions.assertEquals(existingUsers.size(), newUsers.size());
}
getClient().target(getApiBaseUrl() + "admin/user/save").request().post(Entity.json(user), Void.TYPE);
Assertions.assertEquals(existingUsers.size(), getAllUsers().size());
}

private List<UserModel> getAllUsers() {
try (Response response = getClient().target(getApiBaseUrl() + "admin/user/getAll").request().get()) {
return Arrays.asList(response.readEntity(UserModel[].class));
}
return Arrays.asList(getClient().target(getApiBaseUrl() + "admin/user/getAll").request().get(UserModel[].class));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,9 @@ void fetchFeed() {
FeedInfoRequest req = new FeedInfoRequest();
req.setUrl(getFeedUrl());

try (Response response = getClient().target(getApiBaseUrl() + "feed/fetch").request().post(Entity.json(req))) {
Assertions.assertEquals(HttpStatus.OK_200, response.getStatus());

FeedInfo feedInfo = response.readEntity(FeedInfo.class);
Assertions.assertEquals("CommaFeed test feed", feedInfo.getTitle());
Assertions.assertEquals(getFeedUrl(), feedInfo.getUrl());
}

FeedInfo feedInfo = getClient().target(getApiBaseUrl() + "feed/fetch").request().post(Entity.json(req), FeedInfo.class);
Assertions.assertEquals("CommaFeed test feed", feedInfo.getTitle());
Assertions.assertEquals(getFeedUrl(), feedInfo.getUrl());
}
}

Expand Down Expand Up @@ -110,10 +105,7 @@ void mark() {
private void markFeedEntries(long subscriptionId) {
MarkRequest request = new MarkRequest();
request.setId(String.valueOf(subscriptionId));

try (Response response = getClient().target(getApiBaseUrl() + "feed/mark").request().post(Entity.json(request))) {
Assertions.assertEquals(HttpStatus.OK_200, response.getStatus());
}
getClient().target(getApiBaseUrl() + "feed/mark").request().post(Entity.json(request), Void.TYPE);
}
}

Expand All @@ -124,7 +116,9 @@ void refresh() {
Long subscriptionId = subscribeAndWaitForEntries(getFeedUrl());

Date now = new Date();
refreshFeed(subscriptionId);
IDRequest request = new IDRequest();
request.setId(subscriptionId);
getClient().target(getApiBaseUrl() + "feed/refresh").request().post(Entity.json(request), Void.TYPE);

Awaitility.await()
.atMost(Duration.ofSeconds(15))
Expand All @@ -136,28 +130,12 @@ void refreshAll() {
Long subscriptionId = subscribeAndWaitForEntries(getFeedUrl());

Date now = new Date();
refreshAllFeeds();
getClient().target(getApiBaseUrl() + "feed/refreshAll").request().get(Void.TYPE);

Awaitility.await()
.atMost(Duration.ofSeconds(15))
.until(() -> getSubscription(subscriptionId), f -> f.getLastRefresh().after(now));
}

private void refreshFeed(Long subscriptionId) {
IDRequest request = new IDRequest();
request.setId(subscriptionId);

try (Response response = getClient().target(getApiBaseUrl() + "feed/refresh").request().post(Entity.json(request))) {
Assertions.assertEquals(HttpStatus.OK_200, response.getStatus());
}
}

private void refreshAllFeeds() {
try (Response response = getClient().target(getApiBaseUrl() + "feed/refreshAll").request().get()) {
Assertions.assertEquals(HttpStatus.OK_200, response.getStatus());
}
}

}

@Nested
Expand All @@ -172,9 +150,7 @@ void modify() {
req.setId(subscriptionId);
req.setName("new name");
req.setCategoryId(subscription.getCategoryId());
try (Response response = getClient().target(getApiBaseUrl() + "feed/modify").request().post(Entity.json(req))) {
Assertions.assertEquals(HttpStatus.OK_200, response.getStatus());
}
getClient().target(getApiBaseUrl() + "feed/modify").request().post(Entity.json(req), Void.TYPE);

subscription = getSubscription(subscriptionId);
Assertions.assertEquals("new name", subscription.getName());
Expand All @@ -187,25 +163,21 @@ class Favicon {
void favicon() throws IOException {
Long subscriptionId = subscribe(getFeedUrl());

try (Response response = getClient().target(getApiBaseUrl() + "feed/favicon/{id}")
byte[] icon = getClient().target(getApiBaseUrl() + "feed/favicon/{id}")
.resolveTemplate("id", subscriptionId)
.request()
.get()) {
Assertions.assertEquals(HttpStatus.OK_200, response.getStatus());
byte[] icon = response.readEntity(byte[].class);

byte[] defaultFavicon = IOUtils.toByteArray(Objects.requireNonNull(getClass().getResource("/images/default_favicon.gif")));
Assertions.assertArrayEquals(defaultFavicon, icon);
}
.get(byte[].class);
byte[] defaultFavicon = IOUtils.toByteArray(Objects.requireNonNull(getClass().getResource("/images/default_favicon.gif")));
Assertions.assertArrayEquals(defaultFavicon, icon);
}
}

@Nested
class Opml {
@Test
void importExportOpml() throws IOException {
void importExportOpml() {
importOpml();
String opml = exportOpml();
String opml = getClient().target(getApiBaseUrl() + "feed/export").request().get(String.class);
String expextedOpml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<opml version=\"1.0\">\n" + " <head>\n"
+ " <title>admin subscriptions in CommaFeed</title>\n" + " </head>\n" + " <body>\n"
+ " <outline text=\"out1\" title=\"out1\">\n"
Expand All @@ -219,18 +191,9 @@ void importOpml() {
MultiPart multiPart = new MultiPart().bodyPart(new StreamDataBodyPart("file", stream));
multiPart.setMediaType(MediaType.MULTIPART_FORM_DATA_TYPE);

try (Response response = getClient().target(getApiBaseUrl() + "feed/import")
getClient().target(getApiBaseUrl() + "feed/import")
.request()
.post(Entity.entity(multiPart, multiPart.getMediaType()))) {
Assertions.assertEquals(HttpStatus.OK_200, response.getStatus());
}
}

String exportOpml() {
try (Response response = getClient().target(getApiBaseUrl() + "feed/export").request().get()) {
Assertions.assertEquals(HttpStatus.OK_200, response.getStatus());
return response.readEntity(String.class);
}
.post(Entity.entity(multiPart, multiPart.getMediaType()), Void.TYPE);
}
}

Expand Down
Loading

0 comments on commit 351701d

Please sign in to comment.