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 --- .../com/juick/server/api/activity/Profile.java | 82 ++++++++++++++++------ 1 file changed, 62 insertions(+), 20 deletions(-) (limited to 'juick-server/src/main/java/com/juick/server/api/activity/Profile.java') 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); + } } -- cgit v1.2.3