From bac87790c6d044e3bfe9781dd285dfa4b33e49ee Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Mon, 1 Oct 2018 17:58:46 +0300 Subject: ActivityPub: HTTP Signatures and autoaccept followers --- .../java/com/juick/server/ActivityPubManager.java | 37 ++++++++++ .../java/com/juick/server/KeystoreManager.java | 27 ++++++- .../java/com/juick/server/SignatureManager.java | 79 +++++++++++++++++++++ .../com/juick/server/api/activity/Profile.java | 82 ++++++++++++++++------ .../juick/server/api/activity/model/Activity.java | 23 ++++++ .../server/api/activity/model/ActivityObject.java | 52 -------------- .../juick/server/api/activity/model/Context.java | 82 ++++++++++++++++++++++ .../juick/server/api/activity/model/Create.java | 34 --------- .../com/juick/server/api/activity/model/Image.java | 2 +- .../com/juick/server/api/activity/model/Key.java | 2 +- .../com/juick/server/api/activity/model/Link.java | 2 +- .../com/juick/server/api/activity/model/Note.java | 2 +- .../api/activity/model/OrderedCollection.java | 2 +- .../api/activity/model/OrderedCollectionPage.java | 8 +-- .../juick/server/api/activity/model/Person.java | 6 +- .../api/activity/model/activities/Accept.java | 6 ++ .../api/activity/model/activities/Create.java | 6 ++ .../api/activity/model/activities/Delete.java | 6 ++ .../api/activity/model/activities/Follow.java | 6 ++ .../server/api/activity/model/activities/Undo.java | 6 ++ .../juick/server/configuration/SecurityConfig.java | 2 +- 21 files changed, 354 insertions(+), 118 deletions(-) create mode 100644 juick-server/src/main/java/com/juick/server/ActivityPubManager.java create mode 100644 juick-server/src/main/java/com/juick/server/SignatureManager.java create mode 100644 juick-server/src/main/java/com/juick/server/api/activity/model/Activity.java delete mode 100644 juick-server/src/main/java/com/juick/server/api/activity/model/ActivityObject.java create mode 100644 juick-server/src/main/java/com/juick/server/api/activity/model/Context.java delete mode 100644 juick-server/src/main/java/com/juick/server/api/activity/model/Create.java create mode 100644 juick-server/src/main/java/com/juick/server/api/activity/model/activities/Accept.java create mode 100644 juick-server/src/main/java/com/juick/server/api/activity/model/activities/Create.java create mode 100644 juick-server/src/main/java/com/juick/server/api/activity/model/activities/Delete.java create mode 100644 juick-server/src/main/java/com/juick/server/api/activity/model/activities/Follow.java create mode 100644 juick-server/src/main/java/com/juick/server/api/activity/model/activities/Undo.java (limited to 'juick-server/src/main/java/com/juick/server') 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 { + 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 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 request = new HttpEntity<>(Context.build(data), requestHeaders); + //boolean valid = verifySignature(Signature.fromString(requestHeaders.getFirst("Signature")), + // keystoreManager.getPublicKey(), "POST", inbox.getPath(), headers); + ResponseEntity 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 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 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 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 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 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 context; + + private String id; + + private Instant published; + + + private List to; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getType() { + return getClass().getSimpleName(); + } + + @JsonProperty("@context") + public List 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 getTo() { + return to; + } + + public void setTo(List 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 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 getTo() { - return to; - } - - public void setTo(List 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 orderedItems; + private List orderedItems; public String getNext() { return next; @@ -22,11 +22,11 @@ public class OrderedCollectionPage extends ActivityObject { this.next = next; } - public List getOrderedItems() { + public List getOrderedItems() { return orderedItems; } - public void setOrderedItems(List orderedItems) { + public void setOrderedItems(List 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) -- cgit v1.2.3