From 389e29881724b90daa466247aef1b8a164511bb9 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Sat, 21 Aug 2021 03:49:12 +0300 Subject: ActivityPub: add Application as an actor type --- src/main/java/com/juick/KeystoreManager.java | 4 +-- src/main/java/com/juick/SignatureManager.java | 15 ++++----- .../com/juick/www/api/activity/model/Context.java | 3 +- .../api/activity/model/objects/Application.java | 25 +++++++++++++++ .../www/api/activity/model/objects/Person.java | 13 +------- .../api/activity/model/objects/SecurityObject.java | 36 ++++++++++++++++++++++ .../java/com/juick/server/tests/ServerTests.java | 18 ++++++++--- src/test/resources/flag.json | 2 +- src/test/resources/snapshots/activity/testapp.json | 13 ++++++++ 9 files changed, 102 insertions(+), 27 deletions(-) create mode 100644 src/main/java/com/juick/www/api/activity/model/objects/Application.java create mode 100644 src/main/java/com/juick/www/api/activity/model/objects/SecurityObject.java create mode 100644 src/test/resources/snapshots/activity/testapp.json diff --git a/src/main/java/com/juick/KeystoreManager.java b/src/main/java/com/juick/KeystoreManager.java index ce7d4a3b..f968f627 100644 --- a/src/main/java/com/juick/KeystoreManager.java +++ b/src/main/java/com/juick/KeystoreManager.java @@ -17,7 +17,7 @@ package com.juick; -import com.juick.www.api.activity.model.objects.Person; +import com.juick.www.api.activity.model.objects.SecurityObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.Resource; @@ -74,7 +74,7 @@ public class KeystoreManager { return String.format("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n", String.join("\n", key)); } - public static PublicKey publicKeyOf(Person person) { + public static PublicKey publicKeyOf(SecurityObject person) { String pubkeyPem = person.getPublicKey().getPublicKeyPem(); String[] rawKey = pubkeyPem.split("\\n"); String pubkeyData = String.join("", Arrays.asList(rawKey).subList(1, rawKey.length - 1)); diff --git a/src/main/java/com/juick/SignatureManager.java b/src/main/java/com/juick/SignatureManager.java index 35d6215f..d14eedd1 100644 --- a/src/main/java/com/juick/SignatureManager.java +++ b/src/main/java/com/juick/SignatureManager.java @@ -24,6 +24,7 @@ import com.juick.service.UserService; import com.juick.util.DateFormattersHolder; import com.juick.www.api.activity.model.Context; import com.juick.www.api.activity.model.objects.Person; +import com.juick.www.api.activity.model.objects.SecurityObject; import com.juick.www.api.webfinger.model.Account; import com.juick.www.api.webfinger.model.Link; import org.apache.commons.lang3.StringUtils; @@ -93,14 +94,14 @@ public class SignatureManager { logger.info("Remote response: {}", response.getStatusCodeValue()); } - public String addSignature(Person from, String host, String method, + public String addSignature(SecurityObject from, String host, String method, String path, String dateString, String digestHeader) throws IOException { return addSignature(from, host, method, path, dateString, digestHeader, keystoreManager); } - public String addSignature(Person from, String host, String method, + public String addSignature(SecurityObject from, String host, String method, String path, String dateString, String digestHeader, KeystoreManager keystoreManager) throws IOException { List requiredHeaders = StringUtils.isEmpty(digestHeader) ? @@ -126,9 +127,9 @@ public class SignatureManager { Signature signature = Signature.fromString(signatureString); Optional context = getContext(UriComponentsBuilder.fromUriString(signature.getKeyId()) .fragment(null).build().toUri()); - if (context.isPresent() && context.get() instanceof Person) { - Person person = (Person) context.get(); - Key key = KeystoreManager.publicKeyOf(person); + if (context.isPresent() && context.get() instanceof SecurityObject) { + SecurityObject securityObject = (SecurityObject) context.get(); + Key key = KeystoreManager.publicKeyOf(securityObject); Verifier verifier = new Verifier(key, signature); try { @@ -136,9 +137,9 @@ public class SignatureManager { logger.info("signature of {} is valid: {}", signature.getKeyId(), result); if (result) { User user = new User(); - user.setUri(URI.create(person.getId())); + user.setUri(URI.create(securityObject.getId())); if (key.equals(keystoreManager.getPublicKey())) { - return userService.getUserByName(person.getName()); + return userService.getUserByName(securityObject.getName()); } return user; } else { diff --git a/src/main/java/com/juick/www/api/activity/model/Context.java b/src/main/java/com/juick/www/api/activity/model/Context.java index a9d61617..fb726565 100644 --- a/src/main/java/com/juick/www/api/activity/model/Context.java +++ b/src/main/java/com/juick/www/api/activity/model/Context.java @@ -52,7 +52,8 @@ import java.util.Map; @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") + @JsonSubTypes.Type(value = Person.class, name = "Person"), + @JsonSubTypes.Type(value = Application.class, name = "Application") }) public abstract class Context { diff --git a/src/main/java/com/juick/www/api/activity/model/objects/Application.java b/src/main/java/com/juick/www/api/activity/model/objects/Application.java new file mode 100644 index 00000000..23a44e49 --- /dev/null +++ b/src/main/java/com/juick/www/api/activity/model/objects/Application.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2008-2021, Juick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.juick.www.api.activity.model.objects; + +public class Application extends SecurityObject { + @Override + public String getType() { + return "Application"; + } +} diff --git a/src/main/java/com/juick/www/api/activity/model/objects/Person.java b/src/main/java/com/juick/www/api/activity/model/objects/Person.java index af4ca27d..52626833 100644 --- a/src/main/java/com/juick/www/api/activity/model/objects/Person.java +++ b/src/main/java/com/juick/www/api/activity/model/objects/Person.java @@ -18,9 +18,8 @@ package com.juick.www.api.activity.model.objects; import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.juick.www.api.activity.model.Context; -public class Person extends Context { +public class Person extends SecurityObject { private String name; private String preferredUsername; @@ -29,7 +28,6 @@ public class Person extends Context { private String outbox; private String following; private String followers; - private Key publicKey; @Override public String getType() { @@ -85,15 +83,6 @@ public class Person extends Context { this.followers = followers; } - @JsonTypeInfo(use = JsonTypeInfo.Id.NONE) - public Key getPublicKey() { - return publicKey; - } - - public void setPublicKey(Key publicKey) { - this.publicKey = publicKey; - } - public String getPreferredUsername() { return preferredUsername; } diff --git a/src/main/java/com/juick/www/api/activity/model/objects/SecurityObject.java b/src/main/java/com/juick/www/api/activity/model/objects/SecurityObject.java new file mode 100644 index 00000000..8d17aa29 --- /dev/null +++ b/src/main/java/com/juick/www/api/activity/model/objects/SecurityObject.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2008-2021, Juick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.juick.www.api.activity.model.objects; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.juick.www.api.activity.model.Context; + +public class SecurityObject extends Context { + + private Key publicKey; + + @JsonTypeInfo(use = JsonTypeInfo.Id.NONE) + public Key getPublicKey() { + return publicKey; + } + + public void setPublicKey(Key publicKey) { + this.publicKey = publicKey; + } + +} diff --git a/src/test/java/com/juick/server/tests/ServerTests.java b/src/test/java/com/juick/server/tests/ServerTests.java index 5b98c41a..e25f0b0d 100644 --- a/src/test/java/com/juick/server/tests/ServerTests.java +++ b/src/test/java/com/juick/server/tests/ServerTests.java @@ -39,6 +39,7 @@ import com.juick.www.api.Users; import com.juick.www.api.activity.Profile; import com.juick.www.api.activity.model.Context; import com.juick.www.api.activity.model.activities.*; +import com.juick.www.api.activity.model.objects.Application; import com.juick.www.api.activity.model.objects.Note; import com.juick.www.api.activity.model.objects.Person; import com.juick.www.api.webfinger.model.Account; @@ -222,6 +223,8 @@ public class ServerTests { @Value("classpath:snapshots/activity/testuser.json") private Resource testuserResponse; + @Value("classpath:snapshots/activity/testapp.json") + private Resource testappResponse; @Value("classpath:snapshots/activity/testfollow.json") private Resource testfollowRequest; @Value("classpath:snapshots/email/subscription.html") @@ -1869,14 +1872,19 @@ public class ServerTests { User meUser = jsonMapper.readValue(me.getResponse().getContentAsString(), User.class); assertThat(meUser, is(ugnich)); String testuserResponseString = IOUtils.toString(testuserResponse.getInputStream(), StandardCharsets.UTF_8); + String testappResponseString = IOUtils.toString(testappResponse.getInputStream(), StandardCharsets.UTF_8); ClientHttpRequestFactory originalRequestFactory = apClient.getRequestFactory(); URI testuserUri = URI.create("https://example.com/u/testuser"); URI testuserkeyUri = URI.create("https://example.com/u/testuser#main-key"); + URI testAppUri = URI.create("https://example.com/actor"); + URI testAppkeyUri = URI.create("https://example.com/actor#main-key"); MockRestServiceServer restServiceServer = MockRestServiceServer.createServer(apClient); - restServiceServer.expect(times(5), requestTo(testuserUri)) + restServiceServer.expect(times(4), requestTo(testuserUri)) .andRespond(withSuccess(testuserResponseString, MediaType.APPLICATION_JSON)); - restServiceServer.expect(times(5), requestTo(testuserkeyUri)) + restServiceServer.expect(times(4), requestTo(testuserkeyUri)) .andRespond(withSuccess(testuserResponseString, MediaType.APPLICATION_JSON)); + restServiceServer.expect(times(2), requestTo(testAppUri)) + .andRespond(withSuccess(testappResponseString, MediaType.APPLICATION_JSON)); Person testuser = (Person) signatureManager.getContext(testuserUri).get(); assertThat(testuser.getPublicKey().getPublicKeyPem(), is(testKeystoreManager.getPublicKeyPem())); Instant now2 = Instant.now(); @@ -1897,13 +1905,15 @@ public class ServerTests { mockMvc.perform(post(inboxUri).header("Host", testHost).header("Date", testRequestDate) .header("Signature", testSignatureString).contentType(Context.LD_JSON_MEDIA_TYPE).content(payload)) .andExpect(status().isUnauthorized()); - // test flagging + // test flagging as application payload = IOUtils.toByteArray(flagPayload.getInputStream()); digest = MessageDigest.getInstance("SHA-256").digest(payload); // (1) digestHeader = "SHA-256=" + new String(Base64.encodeBase64(digest)); now2 = Instant.now(); testRequestDate = DateFormattersHolder.getHttpDateFormatter().format(now2); - testSignatureString = signatureManager.addSignature(testuser, "localhost", "POST", inboxUri, testRequestDate, + Application testapp = (Application) signatureManager.getContext(testAppUri).get(); + assertThat(testapp.getPublicKey().getPublicKeyPem(), is(testKeystoreManager.getPublicKeyPem())); + testSignatureString = signatureManager.addSignature(testapp, "localhost", "POST", inboxUri, testRequestDate, digestHeader, testKeystoreManager); mockMvc.perform(post(inboxUri).header("Host", testHost).header("Date", testRequestDate) .header("Signature", testSignatureString) diff --git a/src/test/resources/flag.json b/src/test/resources/flag.json index eae0d15f..c3363f01 100644 --- a/src/test/resources/flag.json +++ b/src/test/resources/flag.json @@ -1 +1 @@ -{"@context":"https://www.w3.org/ns/activitystreams","id":"https://localhost/32ac2d3e-c75c-46c1-b0a7-d9fac0986b9a","type":"Flag","actor":"https://example.com/u/testuser","content":"","object":["https://juick.com/u/rtfmpls","https://juick.com/n/2998271-0"]} \ No newline at end of file +{"@context":"https://www.w3.org/ns/activitystreams","id":"https://localhost/32ac2d3e-c75c-46c1-b0a7-d9fac0986b9a","type":"Flag","actor":"https://example.com/actor","content":"","object":["https://juick.com/u/rtfmpls","https://juick.com/n/2998271-0"]} \ No newline at end of file diff --git a/src/test/resources/snapshots/activity/testapp.json b/src/test/resources/snapshots/activity/testapp.json new file mode 100644 index 00000000..d1c2289e --- /dev/null +++ b/src/test/resources/snapshots/activity/testapp.json @@ -0,0 +1,13 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1" + ], + "id": "https://example.com/actor", + "type": "Application", + "publicKey": { + "id": "https://example.com/actor#main-key", + "owner": "https://example.com/actor", + "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" + } +} -- cgit v1.2.3