aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/com/juick/server/KeystoreManager.java9
-rw-r--r--src/main/java/com/juick/server/SignatureManager.java60
-rw-r--r--src/main/java/com/juick/server/api/activity/Profile.java26
-rw-r--r--src/main/java/com/juick/server/configuration/BaseWebConfiguration.java10
-rw-r--r--src/main/java/com/juick/server/configuration/SecurityConfig.java5
-rw-r--r--src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java68
-rw-r--r--src/test/java/com/juick/server/configuration/TestActivityConfiguration.java19
-rw-r--r--src/test/java/com/juick/server/tests/ServerTests.java75
-rw-r--r--src/test/resources/mocks/activity/testfollow.json15
-rw-r--r--src/test/resources/mocks/activity/testuser.json27
-rw-r--r--src/test/resources/test.p12bin0 -> 2386 bytes
11 files changed, 260 insertions, 54 deletions
diff --git a/src/main/java/com/juick/server/KeystoreManager.java b/src/main/java/com/juick/server/KeystoreManager.java
index 67a24f11..3ae7b866 100644
--- a/src/main/java/com/juick/server/KeystoreManager.java
+++ b/src/main/java/com/juick/server/KeystoreManager.java
@@ -19,20 +19,17 @@ import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.stream.Collectors;
-@Component
public class KeystoreManager {
private static final Logger logger = LoggerFactory.getLogger("com.juick.server");
- @Value("${keystore:juick.p12}")
- private String keystore;
- @Value("${keystore_password:secret}")
+
private String keystorePassword;
private KeyStore ks;
private KeyManagerFactory kmf;
- @PostConstruct
- public void init() {
+ public KeystoreManager(String keystore, String keystorePassword) {
+ this.keystorePassword = keystorePassword;
try (InputStream ksIs = new FileInputStream(keystore)) {
ks = KeyStore.getInstance("PKCS12");
ks.load(ksIs, keystorePassword.toCharArray());
diff --git a/src/main/java/com/juick/server/SignatureManager.java b/src/main/java/com/juick/server/SignatureManager.java
index 9ecdaad5..23f5c37a 100644
--- a/src/main/java/com/juick/server/SignatureManager.java
+++ b/src/main/java/com/juick/server/SignatureManager.java
@@ -2,6 +2,7 @@ package com.juick.server;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.juick.User;
+import com.juick.model.AnonymousUser;
import com.juick.server.api.activity.model.Context;
import com.juick.server.api.activity.model.objects.Person;
import com.juick.server.api.webfinger.model.Account;
@@ -11,7 +12,6 @@ import com.juick.util.DateFormattersHolder;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
@@ -53,28 +53,43 @@ public class SignatureManager {
URI inbox = uriComponentsBuilder.build().toUri();
Instant now = Instant.now();
String requestDate = DateFormattersHolder.getHttpDateFormatter().format(now);
- Signature templateSignature = new Signature(from.getPublicKey().getId(), "rsa-sha256", null,
- "(request-target)", "host", "date");
- Signer signer = new Signer(keystoreManager.getPrivateKey(), templateSignature);
- Map<String, String> headers = new HashMap<>();
- headers.put("host", inbox.getHost());
- headers.put("date", requestDate);
- Signature signature = signer.sign("POST", inbox.getPath(), headers);
+ String host = inbox.getPort() > 0 ? String.format("%s:%d", inbox.getHost(), inbox.getPort()) : inbox.getHost();
+ String signatureString = addSignature(from, host, "POST", inbox.getPath(), requestDate);
+
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add("Content-Type", Context.ACTIVITYSTREAMS_PROFILE_MEDIA_TYPE);
requestHeaders.add("Date", requestDate);
- requestHeaders.add("Signature", signature.toString().substring(10));
+ requestHeaders.add("Host", host);
+ requestHeaders.add("Signature", signatureString);
HttpEntity<Context> request = new HttpEntity<>(Context.build(data), requestHeaders);
- //boolean valid = verifySignature(Signature.fromString(requestHeaders.getFirst("Signature")),
- // keystoreManager.getPublicKey(), "POST", inbox.getPath(), headers);
logger.info("Sending context: {}", jsonMapper.writeValueAsString(data));
logger.info("Request date: {}", requestDate);
ResponseEntity<Void> response = apClient.postForEntity(inbox, request, Void.class);
logger.info("accepted follower: {}", response.getStatusCodeValue());
+ }
+
+ public String addSignature(Person from, String host, String method, String path, String dateString) throws IOException {
+ return addSignature(from, host, method, path, dateString, keystoreManager);
+ }
+ public String addSignature(Person from, String host, String method, String path, String dateString, KeystoreManager keystoreManager) throws IOException {
+ Signature templateSignature = new Signature(from.getPublicKey().getId(), "rsa-sha256", null,
+ "(request-target)", "host", "date");
+ Map<String, String> headers = new HashMap<>();
+ headers.put("host", host);
+ headers.put("date", dateString);
+ Signer signer = new Signer(keystoreManager.getPrivateKey(), templateSignature);
+ Signature signature = signer.sign(method, path, headers);
+ // remove "Signature: " from result
+ return signature.toString().substring(10);
}
+
public User verifySignature(String method, String path, Map<String, String> headers) throws IOException {
- Signature signature = Signature.fromString(headers.get("signature"));
+ String signatureString = headers.get("signature");
+ if (StringUtils.isEmpty(signatureString)) {
+ return AnonymousUser.INSTANCE;
+ }
+ Signature signature = Signature.fromString(signatureString);
Optional<Context> context = getContext(URI.create(signature.getKeyId()));
if (context.isPresent() && context.get() instanceof Person) {
Person person = (Person) context.get();
@@ -84,12 +99,16 @@ public class SignatureManager {
try {
boolean result = verifier.verify(method, path, headers);
logger.info("signature is valid: {}", result);
- User user = new User();
- user.setUri(URI.create(person.getId()));
- if (key.equals(keystoreManager.getPublicKey())) {
- return userService.getUserByName(person.getName());
+ if (result) {
+ User user = new User();
+ user.setUri(URI.create(person.getId()));
+ if (key.equals(keystoreManager.getPublicKey())) {
+ return userService.getUserByName(person.getName());
+ }
+ return user;
+ } else {
+ return AnonymousUser.INSTANCE;
}
- return user;
} catch (NoSuchAlgorithmException | SignatureException | IOException e) {
throw new IOException("Invalid signature");
}
@@ -110,9 +129,12 @@ public class SignatureManager {
return Optional.empty();
}
public Optional<Context> discoverPerson(String acct) {
- Jid acctId = Jid.of(acct);
+ String[] accountParts = acct.split(":", 2);
+ String account = accountParts[0];
+ int port = accountParts.length > 1 ? Integer.valueOf(accountParts[1]) : 80;
+ Jid acctId = Jid.of(account);
URI resourceUri = UriComponentsBuilder.fromUriString(
- String.format("https://%s/.well-known/webfinger?resource=acct:%s", acctId.getDomain(), acct)).build().toUri();
+ String.format("http://%s:%d/.well-known/webfinger?resource=acct:%s", acctId.getDomain(), port, account)).build().toUri();
Account acctData = apClient.getForEntity(resourceUri, Account.class).getBody();
if (acctData != null) {
for (Link l : acctData.getLinks()) {
diff --git a/src/main/java/com/juick/server/api/activity/Profile.java b/src/main/java/com/juick/server/api/activity/Profile.java
index 2614cded..404f0f84 100644
--- a/src/main/java/com/juick/server/api/activity/Profile.java
+++ b/src/main/java/com/juick/server/api/activity/Profile.java
@@ -44,6 +44,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
@@ -252,30 +253,11 @@ public class Profile {
}
@PostMapping(value = "/api/inbox", consumes = {Context.LD_JSON_MEDIA_TYPE, Context.ACTIVITYSTREAMS_PROFILE_MEDIA_TYPE})
- public ResponseEntity<Void> processInbox(@RequestBody Activity activity,
- @RequestHeader(name = "Host") String host,
- @RequestHeader(name = "Date") String date,
- @RequestHeader(name = "Digest", required = false) String digest,
- @RequestHeader(name = "Content-Type") String contentType,
- @RequestHeader(name = "User-Agent", required = false) String userAgent,
- @RequestHeader(name = "Accept-Encoding", required = false) String acceptEncoding,
- @RequestHeader(name = "Signature", required = false) String signature) throws Exception {
- UriComponents componentsBuilder = ServletUriComponentsBuilder.fromCurrentRequestUri().build();
- Map<String, String> headers = new HashMap<>();
- headers.put("host", host.split(":", 2)[0]);
- headers.put("date", date);
- headers.put("digest", digest);
- headers.put("content-type", contentType);
- headers.put("user-agent", userAgent);
- headers.put("accept-encoding", acceptEncoding);
- headers.put("signature", signature);
- User signedUser = signatureManager.verifySignature( "POST",
- componentsBuilder.getPath(), headers);
- if ((StringUtils.isNotEmpty(signedUser.getUri().toString()) && signedUser.getUri().equals(URI.create(activity.getActor()))) || !signedUser.isAnonymous()) {
+ public ResponseEntity<Void> processInbox(@RequestBody Activity activity) throws Exception {
+ User visitor = UserUtils.getCurrentUser();
+ if ((StringUtils.isNotEmpty(visitor.getUri().toString()) && visitor.getUri().equals(URI.create(activity.getActor()))) || !visitor.isAnonymous()) {
if (activity instanceof Follow) {
Follow followRequest = (Follow) activity;
- String actor = followRequest.getActor();
- Person follower = (Person) signatureManager.getContext(URI.create(actor)).orElseThrow(HttpBadRequestException::new);
applicationEventPublisher.publishEvent(
new FollowEvent(this, followRequest));
return new ResponseEntity<>(HttpStatus.ACCEPTED);
diff --git a/src/main/java/com/juick/server/configuration/BaseWebConfiguration.java b/src/main/java/com/juick/server/configuration/BaseWebConfiguration.java
index 6a2a8142..16693995 100644
--- a/src/main/java/com/juick/server/configuration/BaseWebConfiguration.java
+++ b/src/main/java/com/juick/server/configuration/BaseWebConfiguration.java
@@ -17,6 +17,8 @@
package com.juick.server.configuration;
+import com.juick.server.KeystoreManager;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
@@ -36,6 +38,10 @@ import java.util.concurrent.Executors;
@Configuration
public class BaseWebConfiguration implements WebMvcConfigurer, SchedulingConfigurer {
+ @Value("${keystore:juick.p12}")
+ private String keystore;
+ @Value("${keystore_password:secret}")
+ private String keystorePassword;
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
@@ -61,4 +67,8 @@ public class BaseWebConfiguration implements WebMvcConfigurer, SchedulingConfigu
public ExecutorService executorService() {
return Executors.newCachedThreadPool();
}
+ @Bean
+ public KeystoreManager keystoreManager() {
+ return new KeystoreManager(keystore, keystorePassword);
+ }
}
diff --git a/src/main/java/com/juick/server/configuration/SecurityConfig.java b/src/main/java/com/juick/server/configuration/SecurityConfig.java
index 7145e9d5..d2d3ab13 100644
--- a/src/main/java/com/juick/server/configuration/SecurityConfig.java
+++ b/src/main/java/com/juick/server/configuration/SecurityConfig.java
@@ -17,7 +17,9 @@
package com.juick.server.configuration;
+import com.juick.server.SignatureManager;
import com.juick.service.UserService;
+import com.juick.service.security.HTTPSignatureAuthenticationFilter;
import com.juick.service.security.HashParamAuthenticationFilter;
import com.juick.service.security.JuickUserDetailsService;
import com.juick.service.security.deprecated.RequestParamHashRememberMeServices;
@@ -93,6 +95,8 @@ public class SecurityConfig {
private String webDomain;
@Resource
private UserService userService;
+ @Resource
+ private SignatureManager signatureManager;
ApiConfig() {
super(true);
}
@@ -109,6 +113,7 @@ public class SecurityConfig {
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**")
.addFilterBefore(apiAuthenticationFilter(), BasicAuthenticationFilter.class)
+ .addFilterBefore(new HTTPSignatureAuthenticationFilter(signatureManager, userService), BasicAuthenticationFilter.class)
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.antMatchers("/api/", "/api/messages", "/api/messages/discussions", "/api/users", "/api/thread", "/api/tags", "/api/tlgmbtwbhk", "/api/fbwbhk",
diff --git a/src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java b/src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java
new file mode 100644
index 00000000..8332fc8c
--- /dev/null
+++ b/src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java
@@ -0,0 +1,68 @@
+package com.juick.service.security;
+
+import com.juick.User;
+import com.juick.server.SignatureManager;
+import com.juick.service.UserService;
+import com.juick.service.security.entities.JuickUser;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.annotation.Nonnull;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.net.URI;
+import java.util.Collections;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class HTTPSignatureAuthenticationFilter extends OncePerRequestFilter {
+
+ private final SignatureManager signatureManager;
+ private final UserService userService;
+
+
+ public HTTPSignatureAuthenticationFilter(
+ final SignatureManager signatureManager,
+ final UserService userService) {
+ this.signatureManager = signatureManager;
+ this.userService = userService;
+ }
+ @Override
+ protected void doFilterInternal(@Nonnull HttpServletRequest request, @Nonnull HttpServletResponse response, @Nonnull FilterChain filterChain) throws IOException, ServletException {
+ if (authenticationIsRequired()) {
+ Map<String, String> headers = Collections.list(request.getHeaderNames())
+ .stream()
+ .collect(Collectors.toMap(String::toLowerCase, request::getHeader));
+ User user = signatureManager.verifySignature(request.getMethod(), request.getRequestURI(), headers);
+ if (!user.isAnonymous()) {
+ String userUri = user.getUri().toString();
+ if (userUri.length() == 0) {
+ User userWithPassword = userService.getUserByName(user.getName());
+ userWithPassword.setAuthHash(userService.getHashByUID(userWithPassword.getUid()));
+ Authentication authentication = new UsernamePasswordAuthenticationToken(userWithPassword.getName(), userWithPassword.getCredentials());
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ } else {
+ Authentication authentication = new AnonymousAuthenticationToken(userUri, user, Collections.singletonList(new SimpleGrantedAuthority("ROLE_ANONYMOUS")));
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ }
+ }
+ }
+
+ filterChain.doFilter(request, response);
+ }
+
+ private boolean authenticationIsRequired() {
+ Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication();
+
+ return existingAuth == null ||
+ !existingAuth.isAuthenticated() ||
+ existingAuth instanceof AnonymousAuthenticationToken;
+ }
+}
diff --git a/src/test/java/com/juick/server/configuration/TestActivityConfiguration.java b/src/test/java/com/juick/server/configuration/TestActivityConfiguration.java
new file mode 100644
index 00000000..5daf4900
--- /dev/null
+++ b/src/test/java/com/juick/server/configuration/TestActivityConfiguration.java
@@ -0,0 +1,19 @@
+package com.juick.server.configuration;
+
+import com.juick.server.KeystoreManager;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.Resource;
+
+import java.io.IOException;
+
+@Configuration
+public class TestActivityConfiguration {
+ @Value("classpath:test.p12")
+ Resource keystoreFile;
+ @Bean
+ public KeystoreManager testKeystoreManager() throws IOException {
+ return new KeystoreManager(keystoreFile.getFile().getAbsolutePath(), "secret");
+ }
+}
diff --git a/src/test/java/com/juick/server/tests/ServerTests.java b/src/test/java/com/juick/server/tests/ServerTests.java
index fa2e2ce9..fedbaba0 100644
--- a/src/test/java/com/juick/server/tests/ServerTests.java
+++ b/src/test/java/com/juick/server/tests/ServerTests.java
@@ -65,10 +65,13 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
import org.springframework.http.*;
+import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
@@ -76,6 +79,7 @@ import org.springframework.util.DigestUtils;
import org.springframework.util.FileSystemUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import org.w3c.dom.Document;
@@ -118,6 +122,9 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
+import static org.springframework.test.web.client.ExpectedCount.times;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
+import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@@ -130,9 +137,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@TestPropertySource(properties = {
"broken_ssl_hosts=localhost,serverstorageisfull.tld",
"ios_app_id=12345678.com.juick.ExampleApp",
- "xmppbot_jid=juick@localhost/Juick",
- "hostname=localhost",
- "componentname=localhost",
"spring.jackson.default-property-inclusion=non_default"
})
@AutoConfigureMockMvc
@@ -174,10 +178,6 @@ public class ServerTests {
private ServerManager serverManager;
@Inject
private KeystoreManager keystoreManager;
- @Value("${hostname:localhost}")
- private Jid jid;
- @Value("${xmppbot_jid:juick@localhost}")
- private Jid botJid;
@Value("${upload_tmp_dir:#{systemEnvironment['TEMP'] ?: '/tmp'}}")
private String tmpDir;
@Value("${img_path:#{systemEnvironment['TEMP'] ?: '/tmp'}}")
@@ -192,6 +192,18 @@ public class ServerTests {
private ActivityPubManager activityPubManager;
@Inject
private WebApp webApp;
+ @Inject
+ private RestTemplate apClient;
+
+ @Value("classpath:mocks/activity/testuser.json")
+ private Resource testuserResponse;
+ @Value("classpath:mocks/activity/testfollow.json")
+ private Resource testfollowRequest;
+
+ @Inject
+ private KeystoreManager testKeystoreManager;
+
+ private MockRestServiceServer restServiceServer;
private static User ugnich, freefd, juick;
static String ugnichName, ugnichPassword, freefdName, freefdPassword, juickName, juickPassword;
@@ -1699,6 +1711,55 @@ public class ServerTests {
signatureManager.post(from, to, follow);
}
@Test
+ public void serviceSignatureAuth() throws Exception {
+ String meUri = "/api/me";
+ String testHost = "localhost:8080";
+ Person ugnichPerson = (Person) signatureManager.discoverPerson("ugnich@localhost:8080").get();
+ Instant now = Instant.now();
+ String requestDate = DateFormattersHolder.getHttpDateFormatter().format(now);
+ String signatureString = signatureManager.addSignature(ugnichPerson, testHost, "GET", meUri, requestDate);
+ MvcResult me = mockMvc.perform(get("/api/me")
+ .header("Host", testHost)
+ .header("Date", requestDate)
+ .header("Signature", signatureString))
+ .andExpect(status().isOk())
+ .andReturn();
+ User meUser = jsonMapper.readValue(me.getResponse().getContentAsString(), User.class);
+ assertThat(meUser, is(ugnich));
+ String testuserResponseString = IOUtils.toString(testuserResponse.getInputStream(), StandardCharsets.UTF_8);
+ ClientHttpRequestFactory originalRequestFactory = apClient.getRequestFactory();
+ restServiceServer = MockRestServiceServer.bindTo(apClient).build();
+ URI testuserUri = URI.create("https://example.com/u/testuser");
+ URI testuserkeyUri = URI.create("https://example.com/u/testuser#main-key");
+ restServiceServer.expect(times(3), requestTo(testuserUri))
+ .andRespond(withSuccess(testuserResponseString, MediaType.APPLICATION_JSON_UTF8));
+ restServiceServer.expect(times(3), requestTo(testuserkeyUri))
+ .andRespond(withSuccess(testuserResponseString, MediaType.APPLICATION_JSON_UTF8));
+ Person testuser = (Person)signatureManager.getContext(testuserUri).get();
+ Assert.assertThat(testuser.getPublicKey().getPublicKeyPem(), is(testKeystoreManager.getPublicKeyPem()));
+ Instant now2 = Instant.now();
+ String testRequestDate = DateFormattersHolder.getHttpDateFormatter().format(now2);
+ String inboxUri = "/api/inbox";
+ String testSignatureString =
+ signatureManager.addSignature(testuser, testHost, "POST",
+ inboxUri, testRequestDate, testKeystoreManager);
+ mockMvc.perform(post(inboxUri)
+ .header("Host", testHost)
+ .header("Date", testRequestDate)
+ .header("Signature", testSignatureString)
+ .contentType(Context.LD_JSON_MEDIA_TYPE)
+ .content(IOUtils.toByteArray(testfollowRequest.getInputStream())))
+ .andExpect(status().isAccepted());
+ mockMvc.perform(post(inboxUri)
+ .header("Host", "wronghost")
+ .header("Date", testRequestDate)
+ .header("Signature", testSignatureString)
+ .contentType(Context.LD_JSON_MEDIA_TYPE)
+ .content(IOUtils.toByteArray(testfollowRequest.getInputStream())))
+ .andExpect(status().isUnauthorized());
+ apClient.setRequestFactory(originalRequestFactory);
+ }
+ @Test
public void hostmeta() throws Exception {
MvcResult result = mockMvc.perform(get("/.well-known/host-meta"))
.andExpect(status().isOk()).andReturn();
diff --git a/src/test/resources/mocks/activity/testfollow.json b/src/test/resources/mocks/activity/testfollow.json
new file mode 100644
index 00000000..e308e52e
--- /dev/null
+++ b/src/test/resources/mocks/activity/testfollow.json
@@ -0,0 +1,15 @@
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ {
+ "schema": "http://schema.org#",
+ "PropertyValue": "schema:PropertyValue",
+ "value": "schema:value"
+ }
+ ],
+ "id": "https://example.com/12345678",
+ "type": "Follow",
+ "actor": "https://example.com/u/testuser",
+ "object": "http://localhost:8080/u/ugnich"
+} \ No newline at end of file
diff --git a/src/test/resources/mocks/activity/testuser.json b/src/test/resources/mocks/activity/testuser.json
new file mode 100644
index 00000000..95fc2aa9
--- /dev/null
+++ b/src/test/resources/mocks/activity/testuser.json
@@ -0,0 +1,27 @@
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ {
+ "schema": "http://schema.org#",
+ "PropertyValue": "schema:PropertyValue",
+ "value": "schema:value"
+ }
+ ],
+ "id": "https://example.com/u/testuser",
+ "type": "Person",
+ "following": "https://example.com/u/testuser/following",
+ "followers": "https://example.com/u/testuser/followers",
+ "inbox": "https://example.com/u/testuser/inbox",
+ "outbox": "https://example.com/u/testuser/outbox",
+ "preferredUsername": "testuser",
+ "url": "https://example.com/@testuser",
+ "publicKey": {
+ "id": "https://example.com/u/testuser#main-key",
+ "owner": "https://example.com/u/testuser",
+ "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiHKRdKFFeT4P/MVlNbxC\nbbgXOkEdeQzvJB/wAJgSYbUwm9SzNFzttePQXk3/MWoK2awWUInZTduVHsWt8zU7\nO3d9PAW6YH6L1oDkjgMLAb9aUWV2ClQWMwsn88WKK9Rb1WOmd8BrXjPfmeFK2ypQ\n9eg8aKpH36WAXiiaTDfBupBZ0Ki2+E87BrWxpbUeDC1dkV+zbl8BMm7X0rp+reoC\nYUWMcjQMzhMmQOXUd4zwJIDPZDMdF4beq/y6WPSUTVgjs4kPDS1HT60ATnsUqyPE\n6tuGxG4j0msb4TTre87PKxMU5YPOxSiqNL0O/3u9/2shVPpjDa/uy9W+VaeBHbFm\nSQIDAQAB\n-----END PUBLIC KEY-----\n"
+ },
+ "endpoints": {
+ "sharedInbox": "https://example.com/inbox"
+ }
+} \ No newline at end of file
diff --git a/src/test/resources/test.p12 b/src/test/resources/test.p12
new file mode 100644
index 00000000..7f7457eb
--- /dev/null
+++ b/src/test/resources/test.p12
Binary files differ