aboutsummaryrefslogtreecommitdiff
path: root/juick-server/src/main
diff options
context:
space:
mode:
authorGravatar Vitaly Takmazov2018-10-01 17:58:46 +0300
committerGravatar Vitaly Takmazov2018-10-03 09:06:00 +0300
commitbac87790c6d044e3bfe9781dd285dfa4b33e49ee (patch)
treecafe620a09bf41c85a5c6512ee2611f45b0ab3c1 /juick-server/src/main
parente04371500a9dd469f02024f63ef39117f8a4d649 (diff)
ActivityPub: HTTP Signatures and autoaccept followers
Diffstat (limited to 'juick-server/src/main')
-rw-r--r--juick-server/src/main/java/com/juick/server/ActivityPubManager.java37
-rw-r--r--juick-server/src/main/java/com/juick/server/KeystoreManager.java27
-rw-r--r--juick-server/src/main/java/com/juick/server/SignatureManager.java79
-rw-r--r--juick-server/src/main/java/com/juick/server/api/activity/Profile.java82
-rw-r--r--juick-server/src/main/java/com/juick/server/api/activity/model/Activity.java23
-rw-r--r--juick-server/src/main/java/com/juick/server/api/activity/model/ActivityObject.java52
-rw-r--r--juick-server/src/main/java/com/juick/server/api/activity/model/Context.java82
-rw-r--r--juick-server/src/main/java/com/juick/server/api/activity/model/Create.java34
-rw-r--r--juick-server/src/main/java/com/juick/server/api/activity/model/Image.java2
-rw-r--r--juick-server/src/main/java/com/juick/server/api/activity/model/Key.java2
-rw-r--r--juick-server/src/main/java/com/juick/server/api/activity/model/Link.java2
-rw-r--r--juick-server/src/main/java/com/juick/server/api/activity/model/Note.java2
-rw-r--r--juick-server/src/main/java/com/juick/server/api/activity/model/OrderedCollection.java2
-rw-r--r--juick-server/src/main/java/com/juick/server/api/activity/model/OrderedCollectionPage.java8
-rw-r--r--juick-server/src/main/java/com/juick/server/api/activity/model/Person.java6
-rw-r--r--juick-server/src/main/java/com/juick/server/api/activity/model/activities/Accept.java6
-rw-r--r--juick-server/src/main/java/com/juick/server/api/activity/model/activities/Create.java6
-rw-r--r--juick-server/src/main/java/com/juick/server/api/activity/model/activities/Delete.java6
-rw-r--r--juick-server/src/main/java/com/juick/server/api/activity/model/activities/Follow.java6
-rw-r--r--juick-server/src/main/java/com/juick/server/api/activity/model/activities/Undo.java6
-rw-r--r--juick-server/src/main/java/com/juick/server/configuration/SecurityConfig.java2
-rw-r--r--juick-server/src/main/java/com/juick/service/activities/FollowEvent.java21
22 files changed, 375 insertions, 118 deletions
diff --git a/juick-server/src/main/java/com/juick/server/ActivityPubManager.java b/juick-server/src/main/java/com/juick/server/ActivityPubManager.java
new file mode 100644
index 00000000..362754fd
--- /dev/null
+++ b/juick-server/src/main/java/com/juick/server/ActivityPubManager.java
@@ -0,0 +1,37 @@
+package com.juick.server;
+
+import com.juick.server.api.activity.model.Person;
+import com.juick.server.api.activity.model.activities.Accept;
+import com.juick.service.activities.FollowEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationListener;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Nonnull;
+import javax.inject.Inject;
+import java.io.IOException;
+import java.net.URI;
+
+@Component
+public class ActivityPubManager implements ApplicationListener<FollowEvent> {
+ private static final Logger logger = LoggerFactory.getLogger(ActivityPubManager.class);
+ @Inject
+ SignatureManager signatureManager;
+ @Override
+ public void onApplicationEvent(@Nonnull FollowEvent followEvent) {
+ logger.info("received follower request");
+ // automatically accept follower requests
+ Person me = (Person) signatureManager.getContext(URI.create((String)followEvent.getRequest().getObject()));
+ Person follower = (Person) signatureManager.getContext(URI.create(followEvent.getRequest().getActor()));
+ Accept accept = new Accept();
+ accept.setActor(me.getId());
+ accept.setObject(followEvent.getRequest());
+ try {
+ signatureManager.post(me, follower, accept);
+ } catch (IOException e) {
+ logger.info("activitypub exception", e);
+ }
+
+ }
+}
diff --git a/juick-server/src/main/java/com/juick/server/KeystoreManager.java b/juick-server/src/main/java/com/juick/server/KeystoreManager.java
index 75d72e72..855052c4 100644
--- a/juick-server/src/main/java/com/juick/server/KeystoreManager.java
+++ b/juick-server/src/main/java/com/juick/server/KeystoreManager.java
@@ -1,5 +1,6 @@
package com.juick.server;
+import com.juick.server.api.activity.model.Person;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
@@ -14,8 +15,11 @@ import java.io.InputStream;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
+import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
+import java.util.List;
import java.util.stream.Collectors;
+import java.util.stream.IntStream;
@Component
public class KeystoreManager {
@@ -61,9 +65,30 @@ public class KeystoreManager {
}
return null;
}
- public String getPublicKey() {
+ public PrivateKey getPrivateKey() {
+ return getKeyPair().getPrivate();
+ }
+ public PublicKey getPublicKey() {
+ return getKeyPair().getPublic();
+ }
+ public String getPublicKeyPem() {
String[] key = Base64Utils.encodeToString(getKeyPair().getPublic().getEncoded()).split("(?<=\\G.{64})");
return String.format("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n",
Arrays.asList(key).stream().collect(Collectors.joining("\n")));
}
+ public static PublicKey publicKeyOf(Person person) {
+ String pubkeyPem = person.getPublicKey().getPublicKeyPem();
+ String[] rawKey = pubkeyPem.split("\\n");
+ String pubkeyData = String.join("", Arrays.asList(rawKey).subList(1, rawKey.length - 1));
+ try{
+ byte[] byteKey = Base64Utils.decodeFromString(pubkeyData);
+ X509EncodedKeySpec X509publicKey = new X509EncodedKeySpec(byteKey);
+ KeyFactory kf = KeyFactory.getInstance("RSA");
+ return kf.generatePublic(X509publicKey);
+ }
+ catch(Exception e){
+ e.printStackTrace();
+ }
+ return null;
+ }
}
diff --git a/juick-server/src/main/java/com/juick/server/SignatureManager.java b/juick-server/src/main/java/com/juick/server/SignatureManager.java
new file mode 100644
index 00000000..d89919f0
--- /dev/null
+++ b/juick-server/src/main/java/com/juick/server/SignatureManager.java
@@ -0,0 +1,79 @@
+package com.juick.server;
+
+import com.juick.server.api.activity.model.Context;
+import com.juick.server.api.activity.model.Person;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+import org.tomitribe.auth.signatures.Signature;
+import org.tomitribe.auth.signatures.Signer;
+import org.tomitribe.auth.signatures.Verifier;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.net.URI;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.SignatureException;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.HashMap;
+import java.util.Map;
+
+@Component
+public class SignatureManager {
+ private static final Logger logger = LoggerFactory.getLogger(ActivityPubManager.class);
+ @Inject
+ private KeystoreManager keystoreManager;
+
+ public void post(Person from, Person to, Context data) throws IOException {
+ UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(to.getInbox());
+ URI inbox = uriComponentsBuilder.build().toUri();
+ Instant now = Instant.now();
+ String requestDate = DateTimeFormatter.RFC_1123_DATE_TIME.withZone(ZoneId.of("UTC")).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);
+ HttpHeaders requestHeaders = new HttpHeaders();
+ requestHeaders.add("Content-Type", Context.ACTIVITY_JSON_MEDIA_TYPE);
+ requestHeaders.add("Date", requestDate);
+ requestHeaders.add("Signature", signature.toString().substring(10));
+ HttpEntity<Context> request = new HttpEntity<>(Context.build(data), requestHeaders);
+ //boolean valid = verifySignature(Signature.fromString(requestHeaders.getFirst("Signature")),
+ // keystoreManager.getPublicKey(), "POST", inbox.getPath(), headers);
+ ResponseEntity<Void> response = new RestTemplate().postForEntity(inbox, request, Void.class);
+ logger.info("accepted follower: {}", response.getStatusCode().is2xxSuccessful());
+ }
+ public boolean verifySignature(String signatureString, URI actor, String method, String path, Map<String, String> headers) {
+ Context context = getContext(actor);
+ if (context instanceof Person) {
+ Person person = (Person) context;
+ Key key = KeystoreManager.publicKeyOf(person);
+ logger.info("data signed by person with key {}", key);
+ Verifier verifier = new Verifier(key, Signature.fromString(signatureString));
+ try {
+ boolean result = verifier.verify(method, path, headers);
+ logger.info("signature is valid: {}", result);
+ return result;
+ } catch (NoSuchAlgorithmException | SignatureException | IOException e) {
+ logger.info("signature exception", e);
+ return false;
+ }
+ }
+ logger.info("person not found");
+ return false;
+ }
+ public Context getContext(URI contextUri) {
+ return new RestTemplate().getForEntity(contextUri, Context.class).getBody();
+ }
+}
diff --git a/juick-server/src/main/java/com/juick/server/api/activity/Profile.java b/juick-server/src/main/java/com/juick/server/api/activity/Profile.java
index 9f98b4ea..656d85dd 100644
--- a/juick-server/src/main/java/com/juick/server/api/activity/Profile.java
+++ b/juick-server/src/main/java/com/juick/server/api/activity/Profile.java
@@ -2,24 +2,33 @@ package com.juick.server.api.activity;
import com.juick.User;
import com.juick.server.KeystoreManager;
+import com.juick.server.SignatureManager;
import com.juick.server.api.activity.model.*;
+import com.juick.server.api.activity.model.activities.Create;
+import com.juick.server.api.activity.model.activities.Follow;
+import com.juick.server.api.activity.model.activities.Undo;
import com.juick.server.util.HttpNotFoundException;
import com.juick.server.util.UserUtils;
import com.juick.service.MessagesService;
import com.juick.service.UserService;
+import com.juick.service.activities.FollowEvent;
import com.juick.util.MessageUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
+import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import javax.inject.Inject;
+import java.net.URI;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -31,6 +40,10 @@ public class Profile {
private MessagesService messagesService;
@Inject
private KeystoreManager keystoreManager;
+ @Inject
+ private SignatureManager signatureManager;
+ @Inject
+ private ApplicationEventPublisher applicationEventPublisher;
@Value("${web_domain:localhost}")
private String domain;
@Value("${ap_base_uri:http://localhost:8080/}")
@@ -38,7 +51,7 @@ public class Profile {
@Value("${img_url:http://localhost:8080/i/}")
private String baseImagesUri;
- @GetMapping(value = "/u/{userName}", produces = { ActivityObject.LD_JSON_MEDIA_TYPE, ActivityObject.ACTIVITY_JSON_MEDIA_TYPE })
+ @GetMapping(value = "/u/{userName}", produces = { Context.LD_JSON_MEDIA_TYPE, Context.ACTIVITY_JSON_MEDIA_TYPE })
public Person getUser(@PathVariable String userName) {
User user = userService.getUserByName(userName);
if (!user.isAnonymous()) {
@@ -52,9 +65,9 @@ public class Profile {
Key publicKey = new Key();
publicKey.setId(person.getId() + "#main-key");
publicKey.setOwner(person.getId());
- publicKey.setPublicKeyPem(keystoreManager.getPublicKey());
+ publicKey.setPublicKeyPem(keystoreManager.getPublicKeyPem());
person.setPublicKey(publicKey);
- uri.replacePath("/post");
+ uri.replacePath("/api/inbox");
person.setInbox(uri.toUriString());
person.setOutbox(uri.replacePath(String.format("/u/%s/blog/toc", userName)).toUriString());
person.setFollowers(uri.replacePath(String.format("/u/%s/followers/toc", userName)).toUriString());
@@ -65,11 +78,11 @@ public class Profile {
avatar.setUrl(image.toUriString());
avatar.setMediaType("image/png");
person.setIcon(avatar);
- return (Person) ActivityObject.build(person);
+ return (Person) Context.build(person);
}
throw new HttpNotFoundException();
}
- @GetMapping(value = "/u/{userName}/blog/toc", produces = { ActivityObject.LD_JSON_MEDIA_TYPE, ActivityObject.ACTIVITY_JSON_MEDIA_TYPE })
+ @GetMapping(value = "/u/{userName}/blog/toc", produces = { Context.LD_JSON_MEDIA_TYPE, Context.ACTIVITY_JSON_MEDIA_TYPE })
public OrderedCollection getOutbox(@PathVariable String userName) {
User user = userService.getUserByName(userName);
if (!user.isAnonymous()) {
@@ -78,11 +91,11 @@ public class Profile {
blog.setId(ServletUriComponentsBuilder.fromCurrentRequestUri().toUriString());
blog.setTotalItems(userService.getStatsMessages(user.getUid()));
blog.setFirst(uriComponentsBuilder.path(String.format("/u/%s/blog", userName)).toUriString());
- return (OrderedCollection) ActivityObject.build(blog);
+ return (OrderedCollection) Context.build(blog);
}
throw new HttpNotFoundException();
}
- @GetMapping(value = "/u/{userName}/blog", produces = { ActivityObject.LD_JSON_MEDIA_TYPE, ActivityObject.ACTIVITY_JSON_MEDIA_TYPE })
+ @GetMapping(value = "/u/{userName}/blog", produces = { Context.LD_JSON_MEDIA_TYPE, Context.ACTIVITY_JSON_MEDIA_TYPE })
public OrderedCollectionPage getOutboxPage(@PathVariable String userName,
@RequestParam(required = false, defaultValue = "0") int before) {
User visitor = UserUtils.getCurrentUser();
@@ -127,11 +140,11 @@ public class Profile {
page.setNext(uri.queryParam("before", beforeNext).toUriString());
}
page.setLast(uri.replaceQueryParam("before", "1").toUriString());
- return (OrderedCollectionPage) ActivityObject.build(page);
+ return (OrderedCollectionPage) Context.build(page);
}
throw new HttpNotFoundException();
}
- @GetMapping(value = "/u/{userName}/followers/toc", produces = { ActivityObject.LD_JSON_MEDIA_TYPE, ActivityObject.ACTIVITY_JSON_MEDIA_TYPE })
+ @GetMapping(value = "/u/{userName}/followers/toc", produces = { Context.LD_JSON_MEDIA_TYPE, Context.ACTIVITY_JSON_MEDIA_TYPE })
public OrderedCollection getFollowers(@PathVariable String userName) {
User user = userService.getUserByName(userName);
if (!user.isAnonymous()) {
@@ -140,11 +153,11 @@ public class Profile {
followers.setId(ServletUriComponentsBuilder.fromCurrentRequestUri().toUriString());
followers.setTotalItems(userService.getStatsMyReaders(user.getUid()));
followers.setFirst(uriComponentsBuilder.path(String.format("/u/%s/followers", userName)).toUriString());
- return (OrderedCollection) ActivityObject.build(followers);
+ return (OrderedCollection) Context.build(followers);
}
throw new HttpNotFoundException();
}
- @GetMapping(value = "/u/{userName}/followers", produces = { ActivityObject.LD_JSON_MEDIA_TYPE, ActivityObject.ACTIVITY_JSON_MEDIA_TYPE })
+ @GetMapping(value = "/u/{userName}/followers", produces = { Context.LD_JSON_MEDIA_TYPE, Context.ACTIVITY_JSON_MEDIA_TYPE })
public OrderedCollectionPage getFollowersPage(@PathVariable String userName,
@RequestParam(required = false, defaultValue = "0") int page) {
User user = userService.getUserByName(userName);
@@ -169,11 +182,11 @@ public class Profile {
if (hasNext) {
result.setNext(uriComponentsBuilder.queryParam("page", page + 1).toUriString());
}
- return (OrderedCollectionPage) ActivityObject.build(result);
+ return (OrderedCollectionPage) Context.build(result);
}
throw new HttpNotFoundException();
}
- @GetMapping(value = "/u/{userName}/following/toc", produces = { ActivityObject.LD_JSON_MEDIA_TYPE, ActivityObject.ACTIVITY_JSON_MEDIA_TYPE })
+ @GetMapping(value = "/u/{userName}/following/toc", produces = { Context.LD_JSON_MEDIA_TYPE, Context.ACTIVITY_JSON_MEDIA_TYPE })
public OrderedCollection getFollowing(@PathVariable String userName) {
User user = userService.getUserByName(userName);
if (!user.isAnonymous()) {
@@ -182,11 +195,11 @@ public class Profile {
following.setId(ServletUriComponentsBuilder.fromCurrentRequestUri().toUriString());
following.setTotalItems(userService.getUserFriends(user.getUid()).size());
following.setFirst(uriComponentsBuilder.path(String.format("/u/%s/followers", userName)).toUriString());
- return (OrderedCollection) ActivityObject.build(following);
+ return (OrderedCollection) Context.build(following);
}
throw new HttpNotFoundException();
}
- @GetMapping(value = "/u/{userName}/following", produces = { ActivityObject.LD_JSON_MEDIA_TYPE, ActivityObject.ACTIVITY_JSON_MEDIA_TYPE })
+ @GetMapping(value = "/u/{userName}/following", produces = { Context.LD_JSON_MEDIA_TYPE, Context.ACTIVITY_JSON_MEDIA_TYPE })
public OrderedCollectionPage getFollowingPage(@PathVariable String userName,
@RequestParam(required = false, defaultValue = "0") int page) {
User user = userService.getUserByName(userName);
@@ -211,8 +224,37 @@ public class Profile {
if (hasNext) {
result.setNext(uriComponentsBuilder.queryParam("page", page + 1).toUriString());
}
- return (OrderedCollectionPage) ActivityObject.build(result);
+ return (OrderedCollectionPage) Context.build(result);
}
throw new HttpNotFoundException();
}
+ @PostMapping(value = "/api/inbox", consumes = { Context.LD_JSON_MEDIA_TYPE, Context.ACTIVITY_JSON_MEDIA_TYPE })
+ public ResponseEntity<Void> processInbox(@RequestBody Context activity,
+ @RequestHeader(name = "Host") String host,
+ @RequestHeader(name = "Date") String date,
+ @RequestHeader(name = "Digest") String digest,
+ @RequestHeader(name = "Content-Type") String contentType,
+ @RequestHeader(name = "Signature") String signature) {
+ if (activity instanceof Follow) {
+ Follow followRequest = (Follow) activity;
+ UriComponents componentsBuilder = ServletUriComponentsBuilder.fromCurrentRequestUri().build();
+ Map<String, String> headers = new HashMap<>();
+ headers.put("host", host);
+ headers.put("date", date);
+ headers.put("digest", digest);
+ headers.put("content-type", contentType);
+ boolean valid = signatureManager.verifySignature(signature, URI.create(followRequest.getActor()), "POST",
+ componentsBuilder.getPath(), headers);
+ if (valid) {
+ applicationEventPublisher.publishEvent(
+ new FollowEvent(this, followRequest));
+ return new ResponseEntity<>(HttpStatus.ACCEPTED);
+ }
+ return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
+ }
+ if (activity instanceof Undo) {
+ return new ResponseEntity<>(HttpStatus.OK);
+ }
+ return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
+ }
}
diff --git a/juick-server/src/main/java/com/juick/server/api/activity/model/Activity.java b/juick-server/src/main/java/com/juick/server/api/activity/model/Activity.java
new file mode 100644
index 00000000..ec126b88
--- /dev/null
+++ b/juick-server/src/main/java/com/juick/server/api/activity/model/Activity.java
@@ -0,0 +1,23 @@
+package com.juick.server.api.activity.model;
+
+public abstract class Activity extends Context {
+
+ private String actor;
+ private Object object;
+
+ public String getActor() {
+ return actor;
+ }
+
+ public void setActor(String actor) {
+ this.actor = actor;
+ }
+
+ public Object getObject() {
+ return object;
+ }
+
+ public void setObject(Object object) {
+ this.object = object;
+ }
+}
diff --git a/juick-server/src/main/java/com/juick/server/api/activity/model/ActivityObject.java b/juick-server/src/main/java/com/juick/server/api/activity/model/ActivityObject.java
deleted file mode 100644
index fceb3612..00000000
--- a/juick-server/src/main/java/com/juick/server/api/activity/model/ActivityObject.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package com.juick.server.api.activity.model;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-import java.time.Instant;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-public abstract class ActivityObject {
-
- private List<String> context;
-
- private String id;
-
- private Instant published;
-
- public String getId() {
- return id;
- }
-
- public void setId(String id) {
- this.id = id;
- }
-
- public String getType() {
- return getClass().getSimpleName();
- }
-
- @JsonProperty("@context")
- public List<String> getContext() {
- return context;
- }
-
- public final static String ACTIVITY_STREAMS_URI = "https://www.w3.org/ns/activitystreams";
- public final static String SECURITY_URI = "https://w3id.org/security/v1";
- public final static String LD_JSON_MEDIA_TYPE = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"";
- public final static String ACTIVITY_JSON_MEDIA_TYPE = "application/activity+json; profile=\"https://www.w3.org/ns/activitystreams\"";
-
- public Instant getPublished() {
- return published;
- }
-
- public void setPublished(Instant published) {
- this.published = published;
- }
-
- public static ActivityObject build(ActivityObject response) {
- response.context = Arrays.asList(ACTIVITY_STREAMS_URI, SECURITY_URI);
- return response;
- }
-}
diff --git a/juick-server/src/main/java/com/juick/server/api/activity/model/Context.java b/juick-server/src/main/java/com/juick/server/api/activity/model/Context.java
new file mode 100644
index 00000000..984eb2cd
--- /dev/null
+++ b/juick-server/src/main/java/com/juick/server/api/activity/model/Context.java
@@ -0,0 +1,82 @@
+package com.juick.server.api.activity.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.juick.server.api.activity.model.activities.*;
+
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.List;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property="type")
+@JsonSubTypes({
+ @JsonSubTypes.Type(value = Create.class, name = "Create"),
+ @JsonSubTypes.Type(value = Delete.class, name = "Delete"),
+ @JsonSubTypes.Type(value = Follow.class, name = "Follow"),
+ @JsonSubTypes.Type(value = Accept.class, name = "Accept"),
+ @JsonSubTypes.Type(value = Undo.class, name = "Undo"),
+ @JsonSubTypes.Type(value = Image.class, name = "Image"),
+ @JsonSubTypes.Type(value = Key.class, name = "Key"),
+ @JsonSubTypes.Type(value = Link.class, name = "Link"),
+ @JsonSubTypes.Type(value = Note.class, name = "Note"),
+ @JsonSubTypes.Type(value = OrderedCollection.class, name = "OrderedCollection"),
+ @JsonSubTypes.Type(value = OrderedCollectionPage.class, name = "OrderedCollectionPage"),
+ @JsonSubTypes.Type(value = Person.class, name = "Person")
+})
+public abstract class Context {
+
+ private List<Object> context;
+
+ private String id;
+
+ private Instant published;
+
+
+ private List<String> to;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getType() {
+ return getClass().getSimpleName();
+ }
+
+ @JsonProperty("@context")
+ public List<Object> getContext() {
+ return context;
+ }
+
+ public final static String ACTIVITY_STREAMS_URI = "https://www.w3.org/ns/activitystreams";
+ public final static String SECURITY_URI = "https://w3id.org/security/v1";
+ public final static String LD_JSON_MEDIA_TYPE = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"";
+ public final static String ACTIVITY_JSON_MEDIA_TYPE = "application/activity+json; profile=\"https://www.w3.org/ns/activitystreams\"";
+
+ public Instant getPublished() {
+ return published;
+ }
+
+ public void setPublished(Instant published) {
+ this.published = published;
+ }
+
+ public List<String> getTo() {
+ return to;
+ }
+
+ public void setTo(List<String> to) {
+ this.to = to;
+ }
+
+ public static Context build(Context response) {
+ response.context = Arrays.asList(ACTIVITY_STREAMS_URI, SECURITY_URI);
+ return response;
+ }
+}
diff --git a/juick-server/src/main/java/com/juick/server/api/activity/model/Create.java b/juick-server/src/main/java/com/juick/server/api/activity/model/Create.java
deleted file mode 100644
index 2acdd6a6..00000000
--- a/juick-server/src/main/java/com/juick/server/api/activity/model/Create.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.juick.server.api.activity.model;
-
-import java.util.List;
-
-public class Create extends ActivityObject {
-
- private String actor;
- private Note object;
- private List<String> to;
-
- public String getActor() {
- return actor;
- }
-
- public void setActor(String actor) {
- this.actor = actor;
- }
-
- public Note getObject() {
- return object;
- }
-
- public void setObject(Note object) {
- this.object = object;
- }
-
- public List<String> getTo() {
- return to;
- }
-
- public void setTo(List<String> to) {
- this.to = to;
- }
-}
diff --git a/juick-server/src/main/java/com/juick/server/api/activity/model/Image.java b/juick-server/src/main/java/com/juick/server/api/activity/model/Image.java
index 9a3b1659..c8f869a5 100644
--- a/juick-server/src/main/java/com/juick/server/api/activity/model/Image.java
+++ b/juick-server/src/main/java/com/juick/server/api/activity/model/Image.java
@@ -1,6 +1,6 @@
package com.juick.server.api.activity.model;
-public class Image extends ActivityObject {
+public class Image extends Context {
private String mediaType;
private String url;
diff --git a/juick-server/src/main/java/com/juick/server/api/activity/model/Key.java b/juick-server/src/main/java/com/juick/server/api/activity/model/Key.java
index 32417778..bc41b460 100644
--- a/juick-server/src/main/java/com/juick/server/api/activity/model/Key.java
+++ b/juick-server/src/main/java/com/juick/server/api/activity/model/Key.java
@@ -1,6 +1,6 @@
package com.juick.server.api.activity.model;
-public class Key extends ActivityObject {
+public class Key extends Context {
private String owner;
private String publicKeyPem;
diff --git a/juick-server/src/main/java/com/juick/server/api/activity/model/Link.java b/juick-server/src/main/java/com/juick/server/api/activity/model/Link.java
index b57dabbe..543b5f0c 100644
--- a/juick-server/src/main/java/com/juick/server/api/activity/model/Link.java
+++ b/juick-server/src/main/java/com/juick/server/api/activity/model/Link.java
@@ -1,6 +1,6 @@
package com.juick.server.api.activity.model;
-public class Link extends ActivityObject {
+public class Link extends Context {
private String href;
public String getHref() {
diff --git a/juick-server/src/main/java/com/juick/server/api/activity/model/Note.java b/juick-server/src/main/java/com/juick/server/api/activity/model/Note.java
index ac58b033..ff64c4b9 100644
--- a/juick-server/src/main/java/com/juick/server/api/activity/model/Note.java
+++ b/juick-server/src/main/java/com/juick/server/api/activity/model/Note.java
@@ -2,7 +2,7 @@ package com.juick.server.api.activity.model;
import java.util.List;
-public class Note extends ActivityObject {
+public class Note extends Context {
private String content;
private String attributedTo;
private Link attachment;
diff --git a/juick-server/src/main/java/com/juick/server/api/activity/model/OrderedCollection.java b/juick-server/src/main/java/com/juick/server/api/activity/model/OrderedCollection.java
index 90f04de3..d66c55be 100644
--- a/juick-server/src/main/java/com/juick/server/api/activity/model/OrderedCollection.java
+++ b/juick-server/src/main/java/com/juick/server/api/activity/model/OrderedCollection.java
@@ -1,6 +1,6 @@
package com.juick.server.api.activity.model;
-public class OrderedCollection extends ActivityObject {
+public class OrderedCollection extends Context {
private int totalItems;
diff --git a/juick-server/src/main/java/com/juick/server/api/activity/model/OrderedCollectionPage.java b/juick-server/src/main/java/com/juick/server/api/activity/model/OrderedCollectionPage.java
index af7f2cec..bcae87d0 100644
--- a/juick-server/src/main/java/com/juick/server/api/activity/model/OrderedCollectionPage.java
+++ b/juick-server/src/main/java/com/juick/server/api/activity/model/OrderedCollectionPage.java
@@ -2,7 +2,7 @@ package com.juick.server.api.activity.model;
import java.util.List;
-public class OrderedCollectionPage extends ActivityObject {
+public class OrderedCollectionPage extends Context {
private String partOf;
@@ -12,7 +12,7 @@ public class OrderedCollectionPage extends ActivityObject {
private String last;
- private List<? extends ActivityObject> orderedItems;
+ private List<? extends Context> orderedItems;
public String getNext() {
return next;
@@ -22,11 +22,11 @@ public class OrderedCollectionPage extends ActivityObject {
this.next = next;
}
- public List<? extends ActivityObject> getOrderedItems() {
+ public List<? extends Context> getOrderedItems() {
return orderedItems;
}
- public void setOrderedItems(List<? extends ActivityObject> orderedItems) {
+ public void setOrderedItems(List<? extends Context> orderedItems) {
this.orderedItems = orderedItems;
}
diff --git a/juick-server/src/main/java/com/juick/server/api/activity/model/Person.java b/juick-server/src/main/java/com/juick/server/api/activity/model/Person.java
index e314624d..8a817c18 100644
--- a/juick-server/src/main/java/com/juick/server/api/activity/model/Person.java
+++ b/juick-server/src/main/java/com/juick/server/api/activity/model/Person.java
@@ -1,6 +1,8 @@
package com.juick.server.api.activity.model;
-public class Person extends ActivityObject {
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+
+public class Person extends Context {
private String name;
private String preferredUsername;
@@ -25,6 +27,7 @@ public class Person extends ActivityObject {
this.name = name;
}
+ @JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
public Image getIcon() {
return icon;
}
@@ -73,6 +76,7 @@ public class Person extends ActivityObject {
this.url = url;
}
+ @JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
public Key getPublicKey() {
return publicKey;
}
diff --git a/juick-server/src/main/java/com/juick/server/api/activity/model/activities/Accept.java b/juick-server/src/main/java/com/juick/server/api/activity/model/activities/Accept.java
new file mode 100644
index 00000000..1e0a9968
--- /dev/null
+++ b/juick-server/src/main/java/com/juick/server/api/activity/model/activities/Accept.java
@@ -0,0 +1,6 @@
+package com.juick.server.api.activity.model.activities;
+
+import com.juick.server.api.activity.model.Activity;
+
+public class Accept extends Activity {
+}
diff --git a/juick-server/src/main/java/com/juick/server/api/activity/model/activities/Create.java b/juick-server/src/main/java/com/juick/server/api/activity/model/activities/Create.java
new file mode 100644
index 00000000..52507373
--- /dev/null
+++ b/juick-server/src/main/java/com/juick/server/api/activity/model/activities/Create.java
@@ -0,0 +1,6 @@
+package com.juick.server.api.activity.model.activities;
+
+import com.juick.server.api.activity.model.Activity;
+
+public class Create extends Activity {
+}
diff --git a/juick-server/src/main/java/com/juick/server/api/activity/model/activities/Delete.java b/juick-server/src/main/java/com/juick/server/api/activity/model/activities/Delete.java
new file mode 100644
index 00000000..f4392020
--- /dev/null
+++ b/juick-server/src/main/java/com/juick/server/api/activity/model/activities/Delete.java
@@ -0,0 +1,6 @@
+package com.juick.server.api.activity.model.activities;
+
+import com.juick.server.api.activity.model.Activity;
+
+public class Delete extends Activity {
+}
diff --git a/juick-server/src/main/java/com/juick/server/api/activity/model/activities/Follow.java b/juick-server/src/main/java/com/juick/server/api/activity/model/activities/Follow.java
new file mode 100644
index 00000000..573ecc6e
--- /dev/null
+++ b/juick-server/src/main/java/com/juick/server/api/activity/model/activities/Follow.java
@@ -0,0 +1,6 @@
+package com.juick.server.api.activity.model.activities;
+
+import com.juick.server.api.activity.model.Activity;
+
+public class Follow extends Activity {
+}
diff --git a/juick-server/src/main/java/com/juick/server/api/activity/model/activities/Undo.java b/juick-server/src/main/java/com/juick/server/api/activity/model/activities/Undo.java
new file mode 100644
index 00000000..4e87e9d0
--- /dev/null
+++ b/juick-server/src/main/java/com/juick/server/api/activity/model/activities/Undo.java
@@ -0,0 +1,6 @@
+package com.juick.server.api.activity.model.activities;
+
+import com.juick.server.api.activity.model.Activity;
+
+public class Undo extends Activity {
+}
diff --git a/juick-server/src/main/java/com/juick/server/configuration/SecurityConfig.java b/juick-server/src/main/java/com/juick/server/configuration/SecurityConfig.java
index 23e2a4e6c..9ce8621e 100644
--- a/juick-server/src/main/java/com/juick/server/configuration/SecurityConfig.java
+++ b/juick-server/src/main/java/com/juick/server/configuration/SecurityConfig.java
@@ -97,7 +97,7 @@ public class SecurityConfig {
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.antMatchers("/api/", "/api/messages", "/api/messages/discussions", "/api/users", "/api/thread", "/api/tags", "/api/tlgmbtwbhk", "/api/fbwbhk",
- "/api/skypebotendpoint", "/api/_fblogin", "/api/_vklogin", "/api/_tglogin", "/api/u/**", "/.well-known/webfinger", "/rss/**").permitAll()
+ "/api/skypebotendpoint", "/api/_fblogin", "/api/_vklogin", "/api/_tglogin", "/api/inbox", "/api/u/**", "/.well-known/webfinger", "/rss/**").permitAll()
.anyRequest().hasRole("USER")
.and()
.anonymous().principal(JuickUser.ANONYMOUS_USER).authorities(JuickUser.ANONYMOUS_AUTHORITY)
diff --git a/juick-server/src/main/java/com/juick/service/activities/FollowEvent.java b/juick-server/src/main/java/com/juick/service/activities/FollowEvent.java
new file mode 100644
index 00000000..c96613ba
--- /dev/null
+++ b/juick-server/src/main/java/com/juick/service/activities/FollowEvent.java
@@ -0,0 +1,21 @@
+package com.juick.service.activities;
+
+import com.juick.server.api.activity.model.activities.Follow;
+import org.springframework.context.ApplicationEvent;
+
+public class FollowEvent extends ApplicationEvent {
+ private Follow request;
+ /**
+ * Create a new ApplicationEvent.
+ *
+ * @param source the object on which the event initially occurred (never {@code null})
+ */
+ public FollowEvent(Object source, Follow followRequest) {
+ super(source);
+ this.request = followRequest;
+ }
+
+ public Follow getRequest() {
+ return request;
+ }
+}