├── .github └── workflows │ └── maven.yml ├── .gitignore ├── README.md ├── aggregates ├── pom.xml └── src │ └── main │ ├── java │ └── social │ │ └── pantheon │ │ └── aggregates │ │ ├── Actor.java │ │ └── AggregatesServer.java │ └── resources │ └── bootstrap.properties ├── api ├── activitypub │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── social │ │ │ └── pantheon │ │ │ └── api │ │ │ └── activitypub │ │ │ ├── ActivityPubServer.java │ │ │ ├── controllers │ │ │ ├── ActorController.java │ │ │ └── InboxController.java │ │ │ ├── filters │ │ │ └── SignatureVerificationFilter.java │ │ │ └── model │ │ │ └── Actor.java │ │ └── resources │ │ └── bootstrap.properties ├── graphql │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── social │ │ │ └── pantheon │ │ │ └── graphql │ │ │ ├── GraphQLServer.java │ │ │ ├── controllers │ │ │ └── ActorsController.java │ │ │ └── model │ │ │ ├── Actor.java │ │ │ └── Relationship.java │ │ └── resources │ │ └── bootstrap.properties ├── nodeinfo │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── social │ │ │ └── pantheon │ │ │ └── api │ │ │ └── nodeinfo │ │ │ └── NodeInfoServer.java │ │ └── resources │ │ └── bootstrap.properties ├── pom.xml └── webfinger │ ├── pom.xml │ └── src │ └── main │ ├── java │ └── social │ │ └── pantheon │ │ └── api │ │ └── webfinger │ │ ├── WebFingerServer.java │ │ ├── controllers │ │ └── WebFingerController.java │ │ ├── converters │ │ └── ActorToJRD.java │ │ └── model │ │ ├── JsonResourceDescriptor.java │ │ └── LinkRelation.java │ └── resources │ └── bootstrap.properties ├── cloud ├── axon │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── social │ │ │ └── pantheon │ │ │ └── cloud │ │ │ └── axon │ │ │ └── AxonServer.java │ │ └── resources │ │ └── application.properties ├── config │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── social │ │ │ └── pantheon │ │ │ └── cloud │ │ │ └── config │ │ │ └── ConfigServer.java │ │ └── resources │ │ ├── application.properties │ │ └── bootstrap.yaml ├── discovery │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── social │ │ │ └── pantheon │ │ │ └── cloud │ │ │ └── discovery │ │ │ └── DiscoveryServer.java │ │ └── resources │ │ └── application.properties ├── gateway │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── social │ │ │ └── pantheon │ │ │ └── cloud │ │ │ └── gateway │ │ │ └── GatewayServer.java │ │ └── resources │ │ └── bootstrap.properties ├── pom.xml └── status │ ├── pom.xml │ └── src │ └── main │ ├── java │ └── social │ │ └── pantheon │ │ └── cloud │ │ └── monitor │ │ └── StatusServer.java │ └── resources │ └── bootstrap.properties ├── commons ├── pom.xml └── src │ └── main │ ├── java │ └── social │ │ └── pantheon │ │ ├── CommonsAutoConfiguration.java │ │ ├── jsonld │ │ ├── Context.java │ │ └── JsonLDSerializer.java │ │ ├── model │ │ ├── commands │ │ │ ├── CreateActorCommand.java │ │ │ ├── FollowActorCommand.java │ │ │ └── UnfollowActorCommand.java │ │ ├── dto │ │ │ ├── ActorDTO.java │ │ │ └── RelationshipDTO.java │ │ ├── events │ │ │ ├── ActorCreatedEvent.java │ │ │ ├── ActorFollowedEvent.java │ │ │ └── ActorUnfollowedEvent.java │ │ ├── queries │ │ │ ├── FetchExternalPublicKeyById.java │ │ │ ├── GetActorById.java │ │ │ ├── GetFollowersForActor.java │ │ │ ├── GetFollowingForActor.java │ │ │ ├── GetPublicKeyById.java │ │ │ ├── GetSignatureForInput.java │ │ │ └── VerifySignatureForInput.java │ │ └── value │ │ │ ├── ActorId.java │ │ │ └── PublicKey.java │ │ └── services │ │ └── QueryService.java │ └── resources │ └── META-INF │ └── spring.factories ├── docker-compose.yml ├── pom.xml └── views ├── actorgraph ├── pom.xml └── src │ └── main │ ├── java │ └── social │ │ └── pantheon │ │ └── views │ │ └── actorgraph │ │ ├── ActorGraphServer.java │ │ ├── Neo4jConfiguration.java │ │ ├── Neo4jTokenStore.java │ │ ├── serializers │ │ ├── ActorSerializer.java │ │ └── NodeSerializer.java │ │ └── services │ │ └── ActorService.java │ └── resources │ └── bootstrap.properties ├── keyholder ├── pom.xml └── src │ └── main │ ├── java │ └── social │ │ └── pantheon │ │ └── views │ │ └── keyholder │ │ ├── KeyHolderServer.java │ │ ├── VaultConfiguration.java │ │ └── services │ │ ├── KeyService.java │ │ └── SignatureService.java │ └── resources │ └── bootstrap.properties └── pom.xml /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Set up JDK 11 13 | uses: actions/setup-java@v1 14 | with: 15 | java-version: 11 16 | - name: Build with Maven 17 | run: mvn package --file pom.xml 18 | - name: Deploy 19 | run: mvn deploy --file pom.xml -Dregistry=https://maven.pkg.github.com/TGNThump 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **.idea/** 2 | **.iml 3 | configuration/** 4 | **/target/** -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pantheon 2 | A federated platform for building communities. -------------------------------------------------------------------------------- /aggregates/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | pantheon 7 | social.pantheon 8 | 0.0.6-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | aggregates 13 | 14 | 15 | 16 | social.pantheon 17 | commons 18 | 19 | 20 | org.axonframework 21 | axon-spring-boot-starter 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-web 26 | 27 | 28 | 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-maven-plugin 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /aggregates/src/main/java/social/pantheon/aggregates/Actor.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.aggregates; 2 | 3 | import lombok.NoArgsConstructor; 4 | import lombok.extern.log4j.Log4j2; 5 | import org.axonframework.commandhandling.CommandHandler; 6 | import org.axonframework.eventsourcing.EventSourcingHandler; 7 | import org.axonframework.modelling.command.AggregateIdentifier; 8 | import org.axonframework.spring.stereotype.Aggregate; 9 | import social.pantheon.model.commands.CreateActorCommand; 10 | import social.pantheon.model.commands.FollowActorCommand; 11 | import social.pantheon.model.commands.UnfollowActorCommand; 12 | import social.pantheon.model.events.ActorCreatedEvent; 13 | import social.pantheon.model.events.ActorFollowedEvent; 14 | import social.pantheon.model.events.ActorUnfollowedEvent; 15 | import social.pantheon.model.value.ActorId; 16 | 17 | import javax.validation.Valid; 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | import static org.axonframework.modelling.command.AggregateLifecycle.apply; 22 | 23 | @Log4j2 24 | @Aggregate 25 | @NoArgsConstructor 26 | public class Actor { 27 | 28 | @AggregateIdentifier 29 | ActorId id; 30 | 31 | List blockedActors; 32 | List following; 33 | 34 | @CommandHandler 35 | public Actor(@Valid CreateActorCommand command) { 36 | apply(new ActorCreatedEvent(command.getId(), command.getKeyId())); 37 | } 38 | 39 | @EventSourcingHandler 40 | public void on(ActorCreatedEvent event){ 41 | id = event.getId(); 42 | blockedActors = new ArrayList<>(); 43 | following = new ArrayList<>(); 44 | } 45 | 46 | @CommandHandler 47 | public void handle(FollowActorCommand command){ 48 | if (following.contains(command.getTarget())) return; 49 | apply(new ActorFollowedEvent(command.getSource(), command.getTarget())); 50 | } 51 | 52 | @EventSourcingHandler 53 | public void on(ActorFollowedEvent event){ 54 | following.add(event.getTarget()); 55 | } 56 | 57 | @CommandHandler 58 | public void handle(UnfollowActorCommand command){ 59 | if (!following.contains(command.getTarget())) return; 60 | apply(new ActorUnfollowedEvent(command.getSource(), command.getTarget())); 61 | } 62 | 63 | @EventSourcingHandler 64 | public void on(ActorUnfollowedEvent event){ 65 | following.remove(event.getTarget()); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /aggregates/src/main/java/social/pantheon/aggregates/AggregatesServer.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.aggregates; 2 | 3 | import lombok.extern.log4j.Log4j2; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 7 | 8 | @Log4j2 9 | @SpringBootApplication 10 | @EnableDiscoveryClient 11 | public class AggregatesServer { 12 | 13 | public static void main(String[] args){ 14 | SpringApplication.run(AggregatesServer.class, args); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /aggregates/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=aggregates -------------------------------------------------------------------------------- /api/activitypub/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | api 7 | social.pantheon 8 | 0.0.6-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | social.pantheon.api 13 | activitypub 14 | 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-maven-plugin 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /api/activitypub/src/main/java/social/pantheon/api/activitypub/ActivityPubServer.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.api.activitypub; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ActivityPubServer { 8 | 9 | public static void main(String[] args){ 10 | SpringApplication.run(ActivityPubServer.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /api/activitypub/src/main/java/social/pantheon/api/activitypub/controllers/ActorController.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.api.activitypub.controllers; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.PathVariable; 6 | import org.springframework.web.bind.annotation.RestController; 7 | import reactor.core.publisher.Mono; 8 | import social.pantheon.api.activitypub.model.Actor; 9 | import social.pantheon.model.dto.ActorDTO; 10 | import social.pantheon.model.queries.GetActorById; 11 | import social.pantheon.model.queries.GetPublicKeyById; 12 | import social.pantheon.model.value.ActorId; 13 | import social.pantheon.model.value.PublicKey; 14 | import social.pantheon.services.QueryService; 15 | 16 | @RestController 17 | @RequiredArgsConstructor 18 | public class ActorController { 19 | 20 | private final QueryService queryService; 21 | private final ActorId.Provider actorIdProvider; 22 | 23 | @GetMapping("/@{username}") 24 | public Mono getActor(@PathVariable String username){ 25 | ActorId actorId = actorIdProvider.get(username); 26 | 27 | Mono actorMono = queryService.mono(new GetActorById(actorId), ActorDTO.class); 28 | Mono keyMono = queryService.mono(new GetPublicKeyById(actorId.getLocalUrl() + "#main-key"), PublicKey.class); 29 | 30 | return Mono.zip(actorMono, keyMono, (actorDTO, publicKey) -> { 31 | Actor actor = new Actor(); 32 | actor.setId("https://" + actorDTO.getId().getLocalUrl()); 33 | actor.setSharedInbox("https://" + actorDTO.getId().getDomain() + "/inbox"); 34 | actor.setPreferredUsername(actorDTO.getId().getUsername()); 35 | actor.setPublicKey(publicKey); 36 | return actor; 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /api/activitypub/src/main/java/social/pantheon/api/activitypub/controllers/InboxController.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.api.activitypub.controllers; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.log4j.Log4j2; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.web.bind.annotation.RestController; 7 | import org.springframework.web.reactive.function.server.RouterFunction; 8 | import org.springframework.web.reactive.function.server.RouterFunctions; 9 | import org.springframework.web.reactive.function.server.ServerRequest; 10 | import org.springframework.web.reactive.function.server.ServerResponse; 11 | import reactor.core.publisher.Mono; 12 | import social.pantheon.api.activitypub.filters.SignatureVerificationFilter; 13 | 14 | import static org.springframework.web.reactive.function.server.RequestPredicates.POST; 15 | import static org.springframework.web.reactive.function.server.ServerResponse.ok; 16 | 17 | @Log4j2 18 | @RestController 19 | @RequiredArgsConstructor 20 | public class InboxController { 21 | 22 | private final SignatureVerificationFilter signatureVerificationFilter; 23 | 24 | @Bean 25 | public RouterFunction route(SignatureVerificationFilter filter) { 26 | return RouterFunctions.route(POST("/@{username}/inbox").or(POST("/inbox")), this::processActivity).filter(signatureVerificationFilter); 27 | } 28 | 29 | public Mono processActivity(ServerRequest request){ 30 | log.debug(request.bodyToMono(String.class)); 31 | return ok().build(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /api/activitypub/src/main/java/social/pantheon/api/activitypub/filters/SignatureVerificationFilter.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.api.activitypub.filters; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.http.HttpHeaders; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.web.reactive.function.server.HandlerFilterFunction; 8 | import org.springframework.web.reactive.function.server.HandlerFunction; 9 | import org.springframework.web.reactive.function.server.ServerRequest; 10 | import org.springframework.web.reactive.function.server.ServerResponse; 11 | import org.springframework.web.server.ResponseStatusException; 12 | import reactor.core.publisher.Mono; 13 | import social.pantheon.model.queries.VerifySignatureForInput; 14 | import social.pantheon.services.QueryService; 15 | 16 | import java.nio.ByteBuffer; 17 | import java.security.MessageDigest; 18 | import java.security.NoSuchAlgorithmException; 19 | import java.time.Instant; 20 | import java.time.format.DateTimeParseException; 21 | import java.util.Arrays; 22 | import java.util.Base64; 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | import java.util.regex.Matcher; 26 | import java.util.regex.Pattern; 27 | import java.util.stream.Collectors; 28 | 29 | @Component 30 | @RequiredArgsConstructor 31 | public class SignatureVerificationFilter implements HandlerFilterFunction { 32 | 33 | private static final Pattern pattern = Pattern.compile("([a-z]+)=\"([^\"]+)\"", Pattern.CASE_INSENSITIVE); 34 | private static final MessageDigest digester = getDigester(); 35 | private static final String REQUEST_TARGET = "(request-target)"; 36 | 37 | private static MessageDigest getDigester(){ 38 | try { 39 | return MessageDigest.getInstance("SHA-256"); 40 | } catch (NoSuchAlgorithmException e) { 41 | e.printStackTrace(); 42 | } 43 | return null; 44 | } 45 | 46 | private final QueryService queryService; 47 | 48 | @Override 49 | public Mono filter(ServerRequest request, HandlerFunction next) { 50 | try { 51 | HttpHeaders headers = request.headers().asHttpHeaders(); 52 | 53 | if (!headers.containsKey("signature")) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Request not signed."); 54 | if (!headers.containsKey("date")) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Signed request date not present."); 55 | if (!checkTimeWindow(headers)) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Signed request date outside acceptable time window."); 56 | 57 | String rawSignature = headers.getFirst("signature"); 58 | Map params = new HashMap<>(); 59 | 60 | for (String part : rawSignature.split(",")){ 61 | Matcher matcher = pattern.matcher(part); 62 | if (!matcher.matches()) continue; 63 | params.put(matcher.group(1), matcher.group(2)); 64 | } 65 | 66 | if (!params.containsKey("keyId") || !params.containsKey("signature")) 67 | throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Signature header does not contain required properties keyId and signature."); 68 | 69 | String keyId = params.get("keyId"); 70 | String signature = params.get("signature"); 71 | String signedHeaders = params.containsKey("headers") ? params.get("headers") : "date"; 72 | 73 | String message = Arrays.asList(signedHeaders.toLowerCase().split(" ")).stream().map(signedHeader -> { 74 | if (signedHeader.equals(REQUEST_TARGET)){ 75 | return REQUEST_TARGET + ": " + request.methodName().toLowerCase() + " " + request.path(); 76 | } else if (signedHeader.equals("digest")){ 77 | digester.update(request.bodyToMono(ByteBuffer.class).block()); 78 | return "digest: " + Base64.getEncoder().encodeToString(digester.digest()); 79 | } else { 80 | return signedHeader + ": " + String.join(",", headers.get(signedHeader)); 81 | } 82 | }).collect(Collectors.joining("\n")); 83 | 84 | Mono verify = queryService.mono(new VerifySignatureForInput(keyId, message, signature), Boolean.class); 85 | 86 | return verify.flatMap(verified -> { 87 | if (verified) return next.handle(request); 88 | else throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid request signature."); 89 | }); 90 | } catch (Throwable e){ 91 | return Mono.error(e); 92 | } 93 | } 94 | 95 | private boolean checkTimeWindow(HttpHeaders headers) { 96 | try{ 97 | Instant timeSent = Instant.parse(headers.getFirst("date")); 98 | return timeSent.isAfter(Instant.now().minusSeconds(30)) && timeSent.isBefore(Instant.now().plusSeconds(30)); 99 | } catch (DateTimeParseException | NullPointerException ex){ return false; } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /api/activitypub/src/main/java/social/pantheon/api/activitypub/model/Actor.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.api.activitypub.model; 2 | 3 | import lombok.Data; 4 | import social.pantheon.jsonld.Context; 5 | import social.pantheon.model.value.PublicKey; 6 | 7 | @Data 8 | @Context("https://www.w3.org/ns/activitystreams") 9 | public class Actor { 10 | private String id; 11 | private String type = "Person"; 12 | private String preferredUsername; 13 | private String summary; 14 | private String sharedInbox; 15 | 16 | private PublicKey publicKey; 17 | 18 | public String getInbox(){ 19 | return getId() + "/inbox"; 20 | } 21 | 22 | public String getOutbox(){ 23 | return getId() + "/outbox"; 24 | } 25 | 26 | public String getFollowing(){ 27 | return getId() + "/following"; 28 | } 29 | 30 | public String getFollowers(){ 31 | return getId() + "/followers"; 32 | } 33 | 34 | public String getLiked(){ 35 | return getId() + "/liked"; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /api/activitypub/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=activitypub -------------------------------------------------------------------------------- /api/graphql/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | api 7 | social.pantheon 8 | 0.0.6-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | social.pantheon.api 13 | graphql 14 | 15 | 16 | 17 | io.leangen.graphql 18 | graphql-spqr-spring-boot-starter 19 | 20 | 21 | 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-maven-plugin 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /api/graphql/src/main/java/social/pantheon/graphql/GraphQLServer.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.graphql; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class GraphQLServer { 8 | 9 | public static void main(String[] args){ 10 | SpringApplication.run(GraphQLServer.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /api/graphql/src/main/java/social/pantheon/graphql/controllers/ActorsController.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.graphql.controllers; 2 | 3 | import io.leangen.graphql.annotations.GraphQLMutation; 4 | import io.leangen.graphql.annotations.GraphQLQuery; 5 | import io.leangen.graphql.spqr.spring.annotations.GraphQLApi; 6 | import lombok.RequiredArgsConstructor; 7 | import org.axonframework.commandhandling.gateway.CommandGateway; 8 | import org.springframework.beans.factory.ObjectProvider; 9 | import org.springframework.stereotype.Controller; 10 | import reactor.core.publisher.Mono; 11 | import social.pantheon.model.commands.CreateActorCommand; 12 | import social.pantheon.model.commands.FollowActorCommand; 13 | import social.pantheon.model.commands.UnfollowActorCommand; 14 | import social.pantheon.model.dto.ActorDTO; 15 | import social.pantheon.model.queries.GetActorById; 16 | import social.pantheon.model.value.ActorId; 17 | import social.pantheon.graphql.model.Actor; 18 | import social.pantheon.services.QueryService; 19 | 20 | import java.util.concurrent.CompletableFuture; 21 | 22 | @GraphQLApi 23 | @Controller 24 | @RequiredArgsConstructor 25 | public class ActorsController { 26 | 27 | private final QueryService queryService; 28 | private final CommandGateway commandGateway; 29 | private final ObjectProvider actorProvider; 30 | 31 | @GraphQLQuery 32 | public Mono getActor(ActorId id){ 33 | return queryService.mono(new GetActorById(id), ActorDTO.class, actorProvider); 34 | } 35 | 36 | @GraphQLMutation 37 | public boolean create(CreateActorCommand cmd){ 38 | commandGateway.sendAndWait(cmd); 39 | return true; 40 | } 41 | 42 | @GraphQLMutation 43 | public boolean follow(FollowActorCommand cmd){ 44 | commandGateway.sendAndWait(cmd); 45 | return true; 46 | } 47 | 48 | @GraphQLMutation 49 | public boolean unfollow(UnfollowActorCommand cmd){ 50 | commandGateway.sendAndWait(cmd); 51 | return true; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /api/graphql/src/main/java/social/pantheon/graphql/model/Actor.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.graphql.model; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.experimental.Delegate; 5 | import org.springframework.beans.factory.ObjectProvider; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.beans.factory.config.BeanDefinition; 8 | import org.springframework.context.annotation.Scope; 9 | import org.springframework.stereotype.Component; 10 | import reactor.core.publisher.Flux; 11 | import social.pantheon.model.dto.ActorDTO; 12 | import social.pantheon.model.dto.RelationshipDTO; 13 | import social.pantheon.model.queries.GetFollowersForActor; 14 | import social.pantheon.model.queries.GetFollowingForActor; 15 | import social.pantheon.services.QueryService; 16 | 17 | import java.util.List; 18 | import java.util.concurrent.CompletableFuture; 19 | 20 | @Component 21 | @Scope(BeanDefinition.SCOPE_PROTOTYPE) 22 | @RequiredArgsConstructor 23 | public class Actor{ 24 | @Delegate 25 | private final ActorDTO actorDTO; 26 | 27 | @Autowired 28 | private QueryService queryService; 29 | 30 | private @Autowired ObjectProvider relationshipProvider; 31 | 32 | public Flux getFollowers(){ 33 | return queryService.flux(new GetFollowersForActor(getId()), RelationshipDTO.class, relationshipProvider); 34 | } 35 | 36 | public Flux getFollowing(){ 37 | return queryService.flux(new GetFollowingForActor(getId()), RelationshipDTO.class, relationshipProvider); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /api/graphql/src/main/java/social/pantheon/graphql/model/Relationship.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.graphql.model; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.beans.factory.ObjectProvider; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.beans.factory.config.BeanDefinition; 7 | import org.springframework.context.annotation.Scope; 8 | import org.springframework.stereotype.Component; 9 | import social.pantheon.model.dto.RelationshipDTO; 10 | 11 | import java.time.Instant; 12 | 13 | @Component 14 | @Scope(BeanDefinition.SCOPE_PROTOTYPE) 15 | @RequiredArgsConstructor 16 | public class Relationship { 17 | 18 | private final RelationshipDTO relationship; 19 | 20 | private @Autowired ObjectProvider actorProvider; 21 | 22 | public Actor getSource(){ 23 | return actorProvider.getObject(relationship.getSource()); 24 | } 25 | 26 | public Actor getTarget(){ 27 | return actorProvider.getObject(relationship.getTarget()); 28 | } 29 | 30 | public Instant getCreatedAt(){ 31 | return relationship.getCreatedAt(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /api/graphql/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=graphql -------------------------------------------------------------------------------- /api/nodeinfo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | api 7 | social.pantheon 8 | 0.0.6-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | social.pantheon.api 13 | nodeinfo 14 | 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-maven-plugin 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /api/nodeinfo/src/main/java/social/pantheon/api/nodeinfo/NodeInfoServer.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.api.nodeinfo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class NodeInfoServer { 8 | 9 | public static void main(String[] args){ 10 | SpringApplication.run(NodeInfoServer.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /api/nodeinfo/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=nodeinfo -------------------------------------------------------------------------------- /api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | pantheon 7 | social.pantheon 8 | 0.0.6-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | social.pantheon 13 | api 14 | pom 15 | 16 | 17 | graphql 18 | activitypub 19 | webfinger 20 | nodeinfo 21 | 22 | 23 | 24 | 25 | social.pantheon 26 | commons 27 | 28 | 29 | org.axonframework 30 | axon-spring-boot-starter 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-webflux 35 | 36 | 37 | -------------------------------------------------------------------------------- /api/webfinger/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | api 7 | social.pantheon 8 | 0.0.6-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | social.pantheon.api 13 | webfinger 14 | 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-maven-plugin 20 | 21 | 22 | org.apache.maven.plugins 23 | maven-compiler-plugin 24 | 25 | 9 26 | 9 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /api/webfinger/src/main/java/social/pantheon/api/webfinger/WebFingerServer.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.api.webfinger; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class WebFingerServer { 8 | 9 | public static void main(String[] args){ 10 | SpringApplication.run(WebFingerServer.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /api/webfinger/src/main/java/social/pantheon/api/webfinger/controllers/WebFingerController.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.api.webfinger.controllers; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RequestParam; 7 | import org.springframework.web.bind.annotation.RestController; 8 | import reactor.core.publisher.Mono; 9 | import social.pantheon.api.webfinger.converters.ActorToJRD; 10 | import social.pantheon.api.webfinger.model.JsonResourceDescriptor; 11 | import social.pantheon.model.dto.ActorDTO; 12 | import social.pantheon.model.queries.GetActorById; 13 | import social.pantheon.model.value.ActorId; 14 | import social.pantheon.services.QueryService; 15 | 16 | import java.util.List; 17 | import java.util.concurrent.CompletableFuture; 18 | 19 | @RestController 20 | @RequiredArgsConstructor 21 | public class WebFingerController { 22 | 23 | @Value("${pantheon.domain}") 24 | String domain; 25 | 26 | private final QueryService queryService; 27 | private final ActorToJRD actorToJRD; 28 | 29 | @GetMapping(".well-known/webfinger") 30 | public Mono getResource(@RequestParam String resource, @RequestParam(required = false) List rel){ 31 | if (resource.startsWith("acct:")) resource = resource.substring("acct:".length()); 32 | ActorId id = ActorId.of(resource); 33 | 34 | if (!id.getDomain().equals(domain)) return Mono.empty(); 35 | 36 | return queryService.mono(new GetActorById(id), ActorDTO.class).map(actorToJRD); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /api/webfinger/src/main/java/social/pantheon/api/webfinger/converters/ActorToJRD.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.api.webfinger.converters; 2 | 3 | import org.springframework.stereotype.Component; 4 | import social.pantheon.api.webfinger.model.JsonResourceDescriptor; 5 | import social.pantheon.api.webfinger.model.LinkRelation; 6 | import social.pantheon.model.dto.ActorDTO; 7 | 8 | import java.util.List; 9 | import java.util.function.Function; 10 | 11 | @Component 12 | public class ActorToJRD implements Function { 13 | 14 | @Override 15 | public JsonResourceDescriptor apply(ActorDTO actor) { 16 | JsonResourceDescriptor jrd = new JsonResourceDescriptor("acct:" + actor.getId()); 17 | 18 | jrd.setAliases(List.of( 19 | "https://" + actor.getId().getLocalUrl() 20 | )); 21 | 22 | jrd.setLinks(List.of( 23 | new LinkRelation("http://webfinger.net/rel/profile-page", "text/html", "https://" + actor.getId().getLocalUrl()), 24 | new LinkRelation("self", "application/activity+json", "https://" + actor.getId().getLocalUrl()) 25 | )); 26 | 27 | return jrd; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /api/webfinger/src/main/java/social/pantheon/api/webfinger/model/JsonResourceDescriptor.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.api.webfinger.model; 2 | 3 | import com.google.common.collect.Lists; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.RequiredArgsConstructor; 7 | 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | @Data 13 | @AllArgsConstructor 14 | @RequiredArgsConstructor 15 | public class JsonResourceDescriptor { 16 | 17 | private final String subject; 18 | private List aliases = Lists.newArrayList(); 19 | private Map properties = new HashMap<>(); 20 | private List links = Lists.newArrayList(); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /api/webfinger/src/main/java/social/pantheon/api/webfinger/model/LinkRelation.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.api.webfinger.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.RequiredArgsConstructor; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | @Data 11 | @AllArgsConstructor 12 | @RequiredArgsConstructor 13 | public class LinkRelation { 14 | 15 | private final String rel; 16 | private final String type; 17 | private final String href; 18 | private Map titles = new HashMap<>(); 19 | private Map properties = new HashMap<>(); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /api/webfinger/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=webfinger -------------------------------------------------------------------------------- /cloud/axon/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | cloud 7 | social.pantheon 8 | 0.0.6-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | social.pantheon.cloud 13 | axon 14 | ${axon.server.version} 15 | 16 | 17 | 18 | com.github.AxonIQ.axon-server-se 19 | axonserver 20 | axonserver-se-${axon.server.version} 21 | 22 | 23 | 24 | 25 | 26 | jitpack.io 27 | https://jitpack.io 28 | 29 | 30 | 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-maven-plugin 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /cloud/axon/src/main/java/social/pantheon/cloud/axon/AxonServer.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.cloud.axon; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.scheduling.annotation.EnableAsync; 6 | import org.springframework.scheduling.annotation.EnableScheduling; 7 | 8 | @SpringBootApplication(scanBasePackageClasses = io.axoniq.axonserver.AxonServer.class) 9 | @EnableAsync 10 | @EnableScheduling 11 | public class AxonServer extends io.axoniq.axonserver.AxonServer { 12 | 13 | public static void main(String[] args){ 14 | SpringApplication.run(AxonServer.class, args); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /cloud/axon/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=axon 2 | server.port=8024 -------------------------------------------------------------------------------- /cloud/config/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | social.pantheon 9 | cloud 10 | 0.0.6-SNAPSHOT 11 | 12 | 13 | social.pantheon.cloud 14 | config 15 | 16 | 17 | 18 | org.springframework.cloud 19 | spring-cloud-config-server 20 | 21 | 22 | org.springframework.cloud 23 | spring-cloud-starter-netflix-eureka-client 24 | 25 | 26 | 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-maven-plugin 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /cloud/config/src/main/java/social/pantheon/cloud/config/ConfigServer.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.cloud.config; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.config.server.EnableConfigServer; 6 | 7 | @SpringBootApplication 8 | @EnableConfigServer 9 | public class ConfigServer { 10 | 11 | public static void main(String[] args){ 12 | SpringApplication.run(ConfigServer.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /cloud/config/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port: 8888 -------------------------------------------------------------------------------- /cloud/config/src/main/resources/bootstrap.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: config 4 | profiles: 5 | active: composite 6 | 7 | cloud: 8 | config: 9 | server: 10 | bootstrap: true 11 | composite: 12 | - 13 | type: native 14 | searchLocations: file:./configuration -------------------------------------------------------------------------------- /cloud/discovery/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | social.pantheon 9 | cloud 10 | 0.0.6-SNAPSHOT 11 | 12 | 13 | social.pantheon.cloud 14 | discovery 15 | 16 | 17 | 18 | org.springframework.cloud 19 | spring-cloud-starter-config 20 | 21 | 22 | org.springframework.cloud 23 | spring-cloud-starter-netflix-eureka-server 24 | 25 | 26 | de.codecentric 27 | spring-boot-admin-starter-client 28 | 29 | 30 | javax.xml.bind 31 | jaxb-api 32 | 33 | 34 | com.sun.xml.bind 35 | jaxb-core 36 | 37 | 38 | com.sun.xml.bind 39 | jaxb-impl 40 | 41 | 42 | javax.activation 43 | activation 44 | 45 | 46 | 47 | 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-maven-plugin 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /cloud/discovery/src/main/java/social/pantheon/cloud/discovery/DiscoveryServer.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.cloud.discovery; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 6 | 7 | @SpringBootApplication 8 | @EnableEurekaServer 9 | public class DiscoveryServer { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(DiscoveryServer.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /cloud/discovery/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8761 2 | 3 | spring.application.name=discovery 4 | eureka.client.register-with-eureka=false 5 | eureka.client.fetch-registry=false 6 | 7 | logging.level.com.netflix.eureka=OFF 8 | logging.level.com.netflix.discovery=OFF 9 | 10 | spring.boot.admin.client.url=http://host.docker.internal:8762 11 | spring.boot.admin.client.instance.name=DISCOVERY 12 | spring.boot.admin.client.instance.service-base-url=http://host.docker.internal:8761 -------------------------------------------------------------------------------- /cloud/gateway/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | cloud 7 | social.pantheon 8 | 0.0.6-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | social.pantheon.cloud 13 | gateway 14 | 15 | 16 | 17 | org.springframework.cloud 18 | spring-cloud-starter-gateway 19 | 20 | 21 | 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-maven-plugin 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /cloud/gateway/src/main/java/social/pantheon/cloud/gateway/GatewayServer.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.cloud.gateway; 2 | 3 | import brave.sampler.Sampler; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.cloud.gateway.route.RouteLocator; 7 | import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.http.HttpMethod; 10 | 11 | import static java.util.regex.Pattern.quote; 12 | 13 | @SpringBootApplication 14 | public class GatewayServer { 15 | 16 | private static final String AP_CONTENT_TYPE = "^.*(" + quote("application/activity+json") + "|" + quote("application/ld+json") + ").*$"; 17 | 18 | public static void main(String[] args) { 19 | SpringApplication.run(GatewayServer.class, args); 20 | } 21 | 22 | @Bean 23 | public Sampler getSampler(){ 24 | return Sampler.ALWAYS_SAMPLE; 25 | } 26 | 27 | @Bean 28 | public RouteLocator routeLocator(RouteLocatorBuilder builder){ 29 | return builder.routes() 30 | .route(p -> p.path("/.well-known/webfinger").uri("lb://webfinger")) 31 | .route(p -> p.path("/.well-known/nodeinfo").uri("lb://nodeinfo")) 32 | .route(p -> p.path("/graphql").uri("lb://graphql")) 33 | .route(p -> p.header("content-type", AP_CONTENT_TYPE).or().header("accept", AP_CONTENT_TYPE).uri("lb://activitypub")) 34 | .build(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /cloud/gateway/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=gateway 2 | server.port=80 3 | management.server.port=8080 -------------------------------------------------------------------------------- /cloud/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | pantheon 7 | social.pantheon 8 | 0.0.6-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | cloud 13 | pom 14 | 15 | 16 | axon 17 | config 18 | status 19 | discovery 20 | gateway 21 | 22 | 23 | -------------------------------------------------------------------------------- /cloud/status/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | social.pantheon 9 | cloud 10 | 0.0.6-SNAPSHOT 11 | 12 | 13 | social.pantheon.cloud 14 | status 15 | 16 | 17 | 18 | de.codecentric 19 | spring-boot-admin-starter-server 20 | 21 | 22 | 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-maven-plugin 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /cloud/status/src/main/java/social/pantheon/cloud/monitor/StatusServer.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.cloud.monitor; 2 | 3 | import de.codecentric.boot.admin.server.config.EnableAdminServer; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 6 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | @Configuration 10 | @EnableAutoConfiguration 11 | @EnableDiscoveryClient 12 | @EnableAdminServer 13 | public class StatusServer { 14 | 15 | public static void main(String[] args) { 16 | SpringApplication.run(StatusServer.class, args); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /cloud/status/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=status 2 | server.port=8762 3 | spring.boot.admin.ui.brand=Pantheon Console 4 | spring.boot.admin.ui.title=Pantheon Console -------------------------------------------------------------------------------- /commons/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | pantheon 7 | social.pantheon 8 | 0.0.6-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | commons 13 | 14 | 15 | 16 | 17 | org.apache.maven.plugins 18 | maven-compiler-plugin 19 | 20 | 11 21 | 11 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | org.axonframework 30 | axon-modelling 31 | 32 | 33 | javax.validation 34 | validation-api 35 | 36 | 37 | commons-codec 38 | commons-codec 39 | 40 | 41 | io.projectreactor 42 | reactor-core 43 | 44 | 45 | org.springframework.cloud 46 | spring-cloud-starter-zipkin 47 | 48 | 49 | -------------------------------------------------------------------------------- /commons/src/main/java/social/pantheon/CommonsAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package social.pantheon; 2 | 3 | import brave.sampler.Sampler; 4 | import org.axonframework.queryhandling.QueryGateway; 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import social.pantheon.model.value.ActorId; 9 | import social.pantheon.services.QueryService; 10 | 11 | @Configuration 12 | public class CommonsAutoConfiguration { 13 | 14 | @Bean 15 | @ConditionalOnMissingBean 16 | public ActorId.Provider actorIdProvider(){ 17 | return new ActorId.Provider(); 18 | } 19 | 20 | @Bean 21 | @ConditionalOnMissingBean 22 | public QueryService queryService(QueryGateway queryGateway){ 23 | return new QueryService(queryGateway); 24 | } 25 | 26 | @Bean 27 | @ConditionalOnMissingBean 28 | public Sampler getSampler(){ 29 | return Sampler.ALWAYS_SAMPLE; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /commons/src/main/java/social/pantheon/jsonld/Context.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.jsonld; 2 | 3 | import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; 4 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 5 | 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | 9 | @JacksonAnnotationsInside 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @JsonSerialize(using = JsonLDSerializer.class) 12 | public @interface Context { 13 | String value(); 14 | } 15 | -------------------------------------------------------------------------------- /commons/src/main/java/social/pantheon/jsonld/JsonLDSerializer.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.jsonld; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.databind.BeanDescription; 5 | import com.fasterxml.jackson.databind.JavaType; 6 | import com.fasterxml.jackson.databind.JsonSerializer; 7 | import com.fasterxml.jackson.databind.SerializerProvider; 8 | import com.fasterxml.jackson.databind.ser.BeanSerializerFactory; 9 | import com.fasterxml.jackson.databind.ser.std.StdSerializer; 10 | import lombok.extern.log4j.Log4j2; 11 | 12 | import java.io.IOException; 13 | 14 | @Log4j2 15 | public class JsonLDSerializer extends StdSerializer { 16 | 17 | protected JsonLDSerializer(){ this(null); } 18 | protected JsonLDSerializer(Class t){ super(t); } 19 | 20 | @Override 21 | public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { 22 | jsonGenerator.writeStartObject(); 23 | jsonGenerator.writeStringField("@context", o.getClass().getAnnotation(Context.class).value()); 24 | 25 | serializeFields(o, jsonGenerator, serializerProvider); 26 | 27 | jsonGenerator.writeEndObject(); 28 | } 29 | 30 | private void serializeFields(Object bean, JsonGenerator gen, SerializerProvider provider) 31 | throws IOException { 32 | JavaType javaType = provider.constructType(bean.getClass()); 33 | BeanDescription beanDesc = provider.getConfig().introspect(javaType); 34 | JsonSerializer serializer = BeanSerializerFactory.instance.findBeanSerializer(provider, 35 | javaType, 36 | beanDesc); 37 | serializer.unwrappingSerializer(null).serialize(bean, gen, provider); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /commons/src/main/java/social/pantheon/model/commands/CreateActorCommand.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.model.commands; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.axonframework.modelling.command.TargetAggregateIdentifier; 7 | import social.pantheon.model.value.ActorId; 8 | 9 | import javax.validation.constraints.NotNull; 10 | 11 | 12 | @Data 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | public class CreateActorCommand { 16 | @TargetAggregateIdentifier 17 | @NotNull ActorId id; 18 | @NotNull String keyId; 19 | } -------------------------------------------------------------------------------- /commons/src/main/java/social/pantheon/model/commands/FollowActorCommand.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.model.commands; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.axonframework.modelling.command.TargetAggregateIdentifier; 7 | import social.pantheon.model.value.ActorId; 8 | 9 | import javax.validation.constraints.NotNull; 10 | 11 | @Data 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | public class FollowActorCommand { 15 | @TargetAggregateIdentifier 16 | @NotNull ActorId source; 17 | @NotNull ActorId target; 18 | } 19 | -------------------------------------------------------------------------------- /commons/src/main/java/social/pantheon/model/commands/UnfollowActorCommand.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.model.commands; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.axonframework.modelling.command.TargetAggregateIdentifier; 7 | import social.pantheon.model.value.ActorId; 8 | 9 | import javax.validation.constraints.NotNull; 10 | 11 | @Data 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | public class UnfollowActorCommand { 15 | @TargetAggregateIdentifier 16 | @NotNull ActorId source; 17 | @NotNull ActorId target; 18 | } 19 | -------------------------------------------------------------------------------- /commons/src/main/java/social/pantheon/model/dto/ActorDTO.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.model.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import social.pantheon.model.value.ActorId; 7 | 8 | @Data 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | public class ActorDTO { 12 | ActorId id; 13 | Integer followingCount; 14 | Integer followersCount; 15 | Integer likeCount; 16 | Integer postCount; 17 | } 18 | -------------------------------------------------------------------------------- /commons/src/main/java/social/pantheon/model/dto/RelationshipDTO.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.model.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.time.Instant; 8 | 9 | @Data 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class RelationshipDTO { 13 | ActorDTO source; 14 | ActorDTO target; 15 | Instant createdAt; 16 | } 17 | -------------------------------------------------------------------------------- /commons/src/main/java/social/pantheon/model/events/ActorCreatedEvent.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.model.events; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import social.pantheon.model.value.ActorId; 7 | 8 | @Data 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | public class ActorCreatedEvent { 12 | ActorId id; 13 | String keyId; 14 | } -------------------------------------------------------------------------------- /commons/src/main/java/social/pantheon/model/events/ActorFollowedEvent.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.model.events; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import social.pantheon.model.value.ActorId; 7 | 8 | @Data 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | public class ActorFollowedEvent { 12 | ActorId source; 13 | ActorId target; 14 | } 15 | -------------------------------------------------------------------------------- /commons/src/main/java/social/pantheon/model/events/ActorUnfollowedEvent.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.model.events; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import social.pantheon.model.value.ActorId; 7 | 8 | @Data 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | public class ActorUnfollowedEvent { 12 | ActorId source; 13 | ActorId target; 14 | } 15 | -------------------------------------------------------------------------------- /commons/src/main/java/social/pantheon/model/queries/FetchExternalPublicKeyById.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.model.queries; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class FetchExternalPublicKeyById { 11 | String keyId; 12 | } 13 | -------------------------------------------------------------------------------- /commons/src/main/java/social/pantheon/model/queries/GetActorById.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.model.queries; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.stereotype.Component; 8 | import social.pantheon.model.value.ActorId; 9 | 10 | import java.util.function.Function; 11 | 12 | @Data 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | public class GetActorById { 16 | ActorId id; 17 | } 18 | -------------------------------------------------------------------------------- /commons/src/main/java/social/pantheon/model/queries/GetFollowersForActor.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.model.queries; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import social.pantheon.model.value.ActorId; 7 | 8 | @Data 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | public class GetFollowersForActor { 12 | ActorId id; 13 | } 14 | -------------------------------------------------------------------------------- /commons/src/main/java/social/pantheon/model/queries/GetFollowingForActor.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.model.queries; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import social.pantheon.model.value.ActorId; 7 | 8 | @Data 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | public class GetFollowingForActor { 12 | ActorId id; 13 | } 14 | -------------------------------------------------------------------------------- /commons/src/main/java/social/pantheon/model/queries/GetPublicKeyById.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.model.queries; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class GetPublicKeyById { 11 | String keyId; 12 | } 13 | -------------------------------------------------------------------------------- /commons/src/main/java/social/pantheon/model/queries/GetSignatureForInput.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.model.queries; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class GetSignatureForInput { 11 | String keyId; 12 | String message; 13 | } 14 | -------------------------------------------------------------------------------- /commons/src/main/java/social/pantheon/model/queries/VerifySignatureForInput.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.model.queries; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class VerifySignatureForInput { 11 | String keyId; 12 | String message; 13 | String signature; 14 | } 15 | -------------------------------------------------------------------------------- /commons/src/main/java/social/pantheon/model/value/ActorId.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.model.value; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.stereotype.Component; 8 | import social.pantheon.model.queries.GetActorById; 9 | 10 | import java.io.Serializable; 11 | import java.util.function.Function; 12 | 13 | @Data 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | public class ActorId implements Serializable { 17 | String domain; 18 | String username; 19 | 20 | public static ActorId of(String id){ 21 | String[] parts = id.split("@"); 22 | return new ActorId(parts[1], parts[0]); 23 | } 24 | 25 | public String toString(){ 26 | return username + "@" + domain; 27 | } 28 | public String getLocalUrl(){ return domain + "/@" + username; } 29 | 30 | public static class Provider { 31 | @Value("${pantheon.domain}") String domain; 32 | 33 | public ActorId get(String username){ 34 | return new ActorId(domain, username); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /commons/src/main/java/social/pantheon/model/value/PublicKey.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.model.value; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import lombok.ToString; 7 | import org.apache.commons.codec.binary.Base64; 8 | import social.pantheon.jsonld.Context; 9 | 10 | import java.math.BigInteger; 11 | import java.security.KeyFactory; 12 | import java.security.NoSuchAlgorithmException; 13 | import java.security.interfaces.RSAPublicKey; 14 | import java.security.spec.AlgorithmParameterSpec; 15 | import java.security.spec.InvalidKeySpecException; 16 | import java.security.spec.X509EncodedKeySpec; 17 | 18 | @Data 19 | @ToString 20 | @NoArgsConstructor 21 | @Context("https://w3id.org/security/v1") 22 | public class PublicKey implements RSAPublicKey { 23 | 24 | @JsonIgnore 25 | @ToString.Exclude 26 | RSAPublicKey delegate; 27 | 28 | String id; 29 | String owner; 30 | String publicKeyPem; 31 | 32 | public PublicKey(String id, String owner, String publicKeyPem) { 33 | this.id = id; 34 | this.owner = owner; 35 | setPublicKeyPem(publicKeyPem); 36 | } 37 | 38 | public void setPublicKeyPem(String publicKeyPem){ 39 | this.publicKeyPem = publicKeyPem.replaceAll("\\n", ""); 40 | 41 | try { 42 | publicKeyPem = publicKeyPem.replaceAll("\\n|-{5}[A-Z ]*-{5}", ""); 43 | byte[] encoded = Base64.decodeBase64(publicKeyPem); 44 | KeyFactory kf = KeyFactory.getInstance("RSA"); 45 | delegate = (RSAPublicKey) kf.generatePublic(new X509EncodedKeySpec(encoded)); 46 | } catch (InvalidKeySpecException ex){ 47 | throw new IllegalArgumentException(ex); 48 | } catch (NoSuchAlgorithmException e) { 49 | e.printStackTrace(); 50 | } 51 | } 52 | 53 | @Override 54 | @JsonIgnore 55 | public BigInteger getPublicExponent() { 56 | return delegate.getPublicExponent(); 57 | } 58 | 59 | @Override 60 | @JsonIgnore 61 | public String getAlgorithm() { 62 | return delegate.getAlgorithm(); 63 | } 64 | 65 | @Override 66 | @JsonIgnore 67 | public String getFormat() { 68 | return delegate.getFormat(); 69 | } 70 | 71 | @Override 72 | @JsonIgnore 73 | public byte[] getEncoded() { 74 | return delegate.getEncoded(); 75 | } 76 | 77 | @Override 78 | @JsonIgnore 79 | public BigInteger getModulus() { 80 | return delegate.getModulus(); 81 | } 82 | 83 | @Override 84 | @JsonIgnore 85 | public AlgorithmParameterSpec getParams(){ 86 | return delegate.getParams(); 87 | } 88 | } -------------------------------------------------------------------------------- /commons/src/main/java/social/pantheon/services/QueryService.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.services; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.log4j.Log4j2; 5 | import org.axonframework.messaging.responsetypes.MultipleInstancesResponseType; 6 | import org.axonframework.queryhandling.QueryGateway; 7 | import org.springframework.beans.factory.ObjectProvider; 8 | import org.springframework.stereotype.Component; 9 | import reactor.core.publisher.Flux; 10 | import reactor.core.publisher.Mono; 11 | 12 | import java.util.List; 13 | import java.util.function.Function; 14 | 15 | import static reactor.core.publisher.Mono.fromFuture; 16 | 17 | @Log4j2 18 | @RequiredArgsConstructor 19 | public class QueryService { 20 | private final QueryGateway queryGateway; 21 | 22 | public Mono mono(Object query, Class type){ 23 | return fromFuture(queryGateway.query(query, type)); 24 | } 25 | 26 | public Flux flux(Object query, Class type){ 27 | Mono> mono = fromFuture(queryGateway.query(query, new MultipleInstancesResponseType<>(type))); 28 | return mono.flatMapIterable(Function.identity()); 29 | } 30 | 31 | public Mono mono(Object query, Class type, ObjectProvider objectProvider){ 32 | return mono(query, type).map(ifNotNull(objectProvider::getObject)); 33 | } 34 | 35 | public Flux flux(Object query, Class type, ObjectProvider objectProvider){ 36 | return flux(query, type).map(ifNotNull(objectProvider::getObject)); 37 | } 38 | 39 | private Function ifNotNull(Function func){ 40 | return dto -> dto == null ? null : func.apply(dto); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /commons/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=social.pantheon.CommonsAutoConfiguration -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | vault: 4 | image: vault 5 | ports: 6 | - "8200:8200" 7 | cap_add: 8 | - IPC_LOCK 9 | volumes: 10 | - "vault:/vault/data" 11 | - "./configuration/vault.json:/vault/config.json" 12 | command: 13 | - "server" 14 | - "-config=/vault/config.json" 15 | volumes: 16 | vault: -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | Pantheon 8 | A federated platform for building communities 9 | 10 | 11 | org.springframework.boot 12 | spring-boot-starter-parent 13 | 2.1.3.RELEASE 14 | 15 | 16 | 17 | social.pantheon 18 | pantheon 19 | 0.0.6-SNAPSHOT 20 | pom 21 | 22 | 23 | api 24 | cloud 25 | aggregates 26 | views 27 | commons 28 | 29 | 30 | 31 | 1.8 32 | Greenwich.SR2 33 | 2.1.6 34 | 1.1.1 35 | 2.2.11 36 | 4.2 37 | 4.2.1 38 | 0.0.4 39 | 40 | 41 | 42 | 43 | org.projectlombok 44 | lombok 45 | true 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-starter 50 | 51 | 52 | org.springframework.cloud 53 | spring-cloud-starter-config 54 | 55 | 56 | org.springframework.cloud 57 | spring-cloud-starter-netflix-eureka-client 58 | 59 | 60 | org.springframework.boot 61 | spring-boot-starter-actuator 62 | 63 | 64 | org.springframework.cloud 65 | spring-cloud-starter-zipkin 66 | 67 | 68 | 69 | 70 | 71 | 72 | social.pantheon 73 | commons 74 | ${project.version} 75 | 76 | 77 | io.leangen.graphql 78 | graphql-spqr-spring-boot-starter 79 | ${graphql-spqr.version} 80 | 81 | 82 | com.google.guava 83 | guava 84 | 28.1-jre 85 | 86 | 87 | org.axonframework 88 | axon-spring-boot-starter 89 | ${axon.client.version} 90 | 91 | 92 | org.axonframework 93 | axon-modelling 94 | ${axon.client.version} 95 | 96 | 97 | org.springframework.cloud 98 | spring-cloud-dependencies 99 | ${spring-cloud.version} 100 | pom 101 | import 102 | 103 | 104 | de.codecentric 105 | spring-boot-admin-dependencies 106 | ${spring-boot-admin.version} 107 | pom 108 | import 109 | 110 | 111 | javax.xml.bind 112 | jaxb-api 113 | ${jaxb.version} 114 | 115 | 116 | com.sun.xml.bind 117 | jaxb-core 118 | ${jaxb.version} 119 | 120 | 121 | com.sun.xml.bind 122 | jaxb-impl 123 | ${jaxb.version} 124 | 125 | 126 | javax.activation 127 | activation 128 | ${javax.activation.version} 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | org.springframework.boot 138 | spring-boot-maven-plugin 139 | 140 | 141 | 142 | build-info 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /views/actorgraph/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | views 7 | social.pantheon 8 | 0.0.6-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | actorgraph 13 | 14 | 15 | 16 | org.neo4j 17 | neo4j 18 | 3.5.3 19 | 20 | 21 | 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-maven-plugin 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /views/actorgraph/src/main/java/social/pantheon/views/actorgraph/ActorGraphServer.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.views.actorgraph; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 | 7 | @SpringBootApplication 8 | @EnableDiscoveryClient 9 | public class ActorGraphServer { 10 | 11 | public static void main(String[] args){ 12 | SpringApplication.run(ActorGraphServer.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /views/actorgraph/src/main/java/social/pantheon/views/actorgraph/Neo4jConfiguration.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.views.actorgraph; 2 | 3 | import lombok.extern.log4j.Log4j2; 4 | import org.neo4j.graphdb.GraphDatabaseService; 5 | import org.neo4j.graphdb.factory.GraphDatabaseFactory; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.nio.file.Files; 13 | import java.nio.file.Path; 14 | import java.util.Comparator; 15 | 16 | @Log4j2 17 | @Configuration 18 | public class Neo4jConfiguration { 19 | 20 | @Value("${neo4j.clear_on_start:false}") 21 | boolean clearOnStart; 22 | 23 | @Bean 24 | GraphDatabaseService graphDatabaseService() throws IOException { 25 | if (clearOnStart){ 26 | log.info("Clearing " + new File("data").getAbsolutePath()); 27 | 28 | Files.walk(new File("data").toPath()) 29 | .sorted(Comparator.reverseOrder()) 30 | .map(Path::toFile) 31 | .forEach(File::delete); 32 | } 33 | 34 | return new GraphDatabaseFactory().newEmbeddedDatabase(new File("data")); 35 | } 36 | } -------------------------------------------------------------------------------- /views/actorgraph/src/main/java/social/pantheon/views/actorgraph/Neo4jTokenStore.java: -------------------------------------------------------------------------------- 1 | //package social.pantheon.views.actorgraph; 2 | // 3 | //import org.axonframework.eventhandling.TrackingToken; 4 | //import org.axonframework.eventhandling.tokenstore.TokenStore; 5 | //import org.axonframework.eventhandling.tokenstore.UnableToClaimTokenException; 6 | //import org.springframework.stereotype.Component; 7 | // 8 | //@Component 9 | //public class Neo4jTokenStore implements TokenStore { 10 | // 11 | // 12 | // @Override 13 | // public void storeToken(TrackingToken trackingToken, String s, int i) throws UnableToClaimTokenException { 14 | // 15 | // } 16 | // 17 | // @Override 18 | // public TrackingToken fetchToken(String s, int i) throws UnableToClaimTokenException { 19 | // return null; 20 | // } 21 | // 22 | // @Override 23 | // public void releaseClaim(String s, int i) { 24 | // 25 | // } 26 | // 27 | // @Override 28 | // public int[] fetchSegments(String s) { 29 | // return new int[0]; 30 | // } 31 | //} 32 | -------------------------------------------------------------------------------- /views/actorgraph/src/main/java/social/pantheon/views/actorgraph/serializers/ActorSerializer.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.views.actorgraph.serializers; 2 | 3 | import org.neo4j.graphdb.Label; 4 | import org.neo4j.graphdb.Node; 5 | import org.neo4j.graphdb.RelationshipType; 6 | import org.springframework.stereotype.Component; 7 | import social.pantheon.model.dto.ActorDTO; 8 | import social.pantheon.model.value.ActorId; 9 | 10 | import static org.neo4j.graphdb.Direction.INCOMING; 11 | import static org.neo4j.graphdb.Direction.OUTGOING; 12 | 13 | @Component 14 | public class ActorSerializer implements NodeSerializer { 15 | 16 | private static final Label ACTOR = Label.label("ActorDTO"); 17 | private static final String ID = "id"; 18 | 19 | private static final RelationshipType FOLLOWS = RelationshipType.withName("Follows"); 20 | private static final RelationshipType LIKES = RelationshipType.withName("Likes"); 21 | 22 | @Override 23 | public ActorDTO serialize(Node node) { 24 | if (node == null) return null; 25 | ActorDTO actor = new ActorDTO(); 26 | 27 | actor.setId(ActorId.of((String) node.getProperty(ID))); 28 | actor.setFollowingCount(node.getDegree(FOLLOWS, OUTGOING)); 29 | actor.setFollowersCount(node.getDegree(FOLLOWS, INCOMING)); 30 | actor.setLikeCount(node.getDegree(LIKES, OUTGOING)); 31 | actor.setPostCount(node.getDegree(OUTGOING) - actor.getFollowingCount() - actor.getLikeCount()); 32 | 33 | return actor; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /views/actorgraph/src/main/java/social/pantheon/views/actorgraph/serializers/NodeSerializer.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.views.actorgraph.serializers; 2 | 3 | import org.neo4j.graphdb.Node; 4 | 5 | public interface NodeSerializer { 6 | 7 | T serialize(Node node); 8 | } 9 | -------------------------------------------------------------------------------- /views/actorgraph/src/main/java/social/pantheon/views/actorgraph/services/ActorService.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.views.actorgraph.services; 2 | 3 | import lombok.Cleanup; 4 | import lombok.RequiredArgsConstructor; 5 | import lombok.extern.log4j.Log4j2; 6 | import org.axonframework.eventhandling.EventHandler; 7 | import org.axonframework.eventhandling.Timestamp; 8 | import org.axonframework.queryhandling.QueryHandler; 9 | import org.neo4j.graphdb.*; 10 | import org.springframework.stereotype.Service; 11 | import social.pantheon.model.dto.ActorDTO; 12 | import social.pantheon.model.dto.RelationshipDTO; 13 | import social.pantheon.model.events.ActorCreatedEvent; 14 | import social.pantheon.model.events.ActorFollowedEvent; 15 | import social.pantheon.model.events.ActorUnfollowedEvent; 16 | import social.pantheon.model.queries.GetActorById; 17 | import social.pantheon.model.queries.GetFollowersForActor; 18 | import social.pantheon.model.queries.GetFollowingForActor; 19 | import social.pantheon.model.value.ActorId; 20 | import social.pantheon.views.actorgraph.serializers.ActorSerializer; 21 | 22 | import java.time.Instant; 23 | import java.util.ArrayList; 24 | import java.util.Comparator; 25 | import java.util.List; 26 | 27 | import static org.neo4j.graphdb.Direction.INCOMING; 28 | import static org.neo4j.graphdb.Direction.OUTGOING; 29 | 30 | @Log4j2 31 | @Service 32 | @RequiredArgsConstructor 33 | public class ActorService { 34 | 35 | private static final Label ACTOR = Label.label("ActorDTO"); 36 | private static final String ID = "id"; 37 | private static final String CREATED_AT = "createdAt"; 38 | 39 | private static final RelationshipType FOLLOWS = RelationshipType.withName("Follows"); 40 | private static final RelationshipType LIKES = RelationshipType.withName("Likes"); 41 | 42 | private final GraphDatabaseService db; 43 | private final ActorSerializer actorSerializer; 44 | 45 | private Node getActor(ActorId id){ 46 | return db.findNode(ACTOR, ID, id.toString()); 47 | } 48 | 49 | @QueryHandler 50 | public ActorDTO on(GetActorById query){ 51 | @Cleanup Transaction tx = db.beginTx(); 52 | 53 | Node node = getActor(query.getId()); 54 | 55 | tx.success(); 56 | return actorSerializer.serialize(node); 57 | } 58 | 59 | @QueryHandler 60 | public List on(GetFollowersForActor query){ 61 | List followers = new ArrayList<>(); 62 | 63 | try (Transaction tx = db.beginTx()){ 64 | Node node = getActor(query.getId()); 65 | ActorDTO target = actorSerializer.serialize(node); 66 | if (node == null) return null; 67 | for (Relationship relationship : node.getRelationships(INCOMING, FOLLOWS)){ 68 | Instant time = Instant.ofEpochMilli((Long) relationship.getProperty(CREATED_AT)); 69 | ActorDTO source = actorSerializer.serialize(relationship.getStartNode()); 70 | followers.add(new RelationshipDTO(source, target, time)); 71 | } 72 | tx.success(); 73 | } 74 | 75 | followers.sort(Comparator.comparing(r -> r.getCreatedAt())); 76 | 77 | return followers; 78 | } 79 | 80 | @QueryHandler 81 | public List on(GetFollowingForActor query){ 82 | List following = new ArrayList<>(); 83 | 84 | try (Transaction tx = db.beginTx()){ 85 | Node node = getActor(query.getId()); 86 | ActorDTO source = actorSerializer.serialize(node); 87 | if (node == null) return null; 88 | for (Relationship relationship : node.getRelationships(OUTGOING, FOLLOWS)){ 89 | Instant time = Instant.ofEpochMilli((Long) relationship.getProperty(CREATED_AT)); 90 | ActorDTO target = actorSerializer.serialize(relationship.getEndNode()); 91 | following.add(new RelationshipDTO(source, target, time)); 92 | } 93 | tx.success(); 94 | } 95 | 96 | following.sort(Comparator.comparing(r -> ((RelationshipDTO) r).getCreatedAt()).reversed()); 97 | return following; 98 | } 99 | 100 | @EventHandler 101 | public void on(ActorCreatedEvent event){ 102 | log.info(event); 103 | try (Transaction tx = db.beginTx()) { 104 | Node actor = db.findNode(ACTOR, ID, event.getId().toString()); 105 | if (actor == null) { 106 | actor = db.createNode(ACTOR); 107 | actor.setProperty(ID, event.getId().toString()); 108 | } else throw new IllegalStateException(); 109 | tx.success(); 110 | } 111 | } 112 | 113 | @EventHandler 114 | public void on(ActorFollowedEvent event, @Timestamp Instant timestamp){ 115 | log.info(event); 116 | try (Transaction tx = db.beginTx()) { 117 | Node source = db.findNode(ACTOR, ID, event.getSource().toString()); 118 | Node target = db.findNode(ACTOR, ID, event.getTarget().toString()); 119 | 120 | Relationship follows = source.createRelationshipTo(target, FOLLOWS); 121 | follows.setProperty(CREATED_AT, timestamp.toEpochMilli()); 122 | tx.success(); 123 | } 124 | } 125 | 126 | @EventHandler 127 | public void on(ActorUnfollowedEvent event){ 128 | log.info(event); 129 | try (Transaction tx = db.beginTx()) { 130 | Node source = db.findNode(ACTOR, ID, event.getSource().toString()); 131 | Node target = db.findNode(ACTOR, ID, event.getTarget().toString()); 132 | 133 | if (source.getDegree(FOLLOWS, OUTGOING) < target.getDegree(FOLLOWS, INCOMING)){ 134 | for (Relationship r : source.getRelationships(FOLLOWS, OUTGOING)){ 135 | if (r.getEndNode().equals(target)){ 136 | r.delete(); 137 | break; 138 | } 139 | } 140 | } else { 141 | for (Relationship r : target.getRelationships(FOLLOWS, INCOMING)){ 142 | if (r.getStartNode().equals(source)){ 143 | r.delete(); 144 | break; 145 | } 146 | } 147 | } 148 | tx.success(); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /views/actorgraph/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=actorgraph -------------------------------------------------------------------------------- /views/keyholder/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | views 7 | social.pantheon 8 | 0.0.6-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | social.pantheon.views 13 | keyholder 14 | 15 | 16 | 17 | org.springframework.cloud 18 | spring-cloud-starter-vault-config 19 | 20 | 21 | com.fasterxml.jackson.dataformat 22 | jackson-dataformat-xml 23 | 24 | 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-maven-plugin 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /views/keyholder/src/main/java/social/pantheon/views/keyholder/KeyHolderServer.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.views.keyholder; 2 | 3 | import lombok.extern.log4j.Log4j2; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @Log4j2 8 | @SpringBootApplication 9 | public class KeyHolderServer { 10 | 11 | public static void main(String[] args){ 12 | SpringApplication.run(KeyHolderServer.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /views/keyholder/src/main/java/social/pantheon/views/keyholder/VaultConfiguration.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.views.keyholder; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.vault.core.VaultKeyValueOperations; 6 | import org.springframework.vault.core.VaultKeyValueOperationsSupport; 7 | import org.springframework.vault.core.VaultTemplate; 8 | import org.springframework.vault.core.VaultTransitOperations; 9 | 10 | @Configuration 11 | public class VaultConfiguration { 12 | 13 | @Bean 14 | public VaultTransitOperations vaultTransitOperations(VaultTemplate vaultTemplate){ 15 | return vaultTemplate.opsForTransit("transit"); 16 | } 17 | 18 | @Bean 19 | public VaultKeyValueOperations vaultKeyValueOperations(VaultTemplate vaultTemplate){ 20 | return vaultTemplate.opsForKeyValue("kv", VaultKeyValueOperationsSupport.KeyValueBackend.KV_2); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /views/keyholder/src/main/java/social/pantheon/views/keyholder/services/KeyService.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.views.keyholder.services; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.log4j.Log4j2; 5 | import org.axonframework.eventhandling.EventHandler; 6 | import org.axonframework.queryhandling.QueryHandler; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.stereotype.Service; 9 | import org.springframework.vault.core.VaultKeyValueOperations; 10 | import org.springframework.vault.core.VaultTransitOperations; 11 | import org.springframework.vault.support.VaultResponseSupport; 12 | import org.springframework.vault.support.VaultTransitKey; 13 | import org.springframework.vault.support.VaultTransitKeyCreationRequest; 14 | import social.pantheon.model.events.ActorCreatedEvent; 15 | import social.pantheon.model.queries.FetchExternalPublicKeyById; 16 | import social.pantheon.model.queries.GetPublicKeyById; 17 | import social.pantheon.model.value.PublicKey; 18 | import social.pantheon.services.QueryService; 19 | 20 | import java.util.LinkedHashMap; 21 | import java.util.concurrent.ExecutionException; 22 | 23 | @Log4j2 24 | @Service 25 | @RequiredArgsConstructor 26 | public class KeyService { 27 | 28 | private final VaultTransitOperations transit; 29 | private final VaultKeyValueOperations kv; 30 | private final QueryService queryService; 31 | 32 | @Value("${pantheon.domain}") 33 | String localDomain; 34 | 35 | @EventHandler 36 | public void on(ActorCreatedEvent event){ 37 | log.debug("Handling " + event); 38 | if (!event.getId().getDomain().equals(localDomain)) return; 39 | log.info("Creating keypair for " + event.getId()); 40 | transit.createKey(event.getId().getUsername(), VaultTransitKeyCreationRequest.ofKeyType("rsa-2048")); 41 | } 42 | 43 | @QueryHandler 44 | public PublicKey handle(GetPublicKeyById query){ 45 | log.debug("Handling " + query); 46 | String keyId = query.getKeyId().replaceAll("^https?:\\/\\/", ""); 47 | 48 | if (keyId.startsWith(localDomain)) return getLocalKey(keyId); 49 | else return getExternalKey(keyId); 50 | } 51 | 52 | private PublicKey getLocalKey(String keyId){ 53 | log.debug("Getting PublicKey From Vault Transit: " + keyId); 54 | String username = getUsernameFromKeyId(keyId); 55 | 56 | VaultTransitKey key = transit.getKey(username); 57 | String pem = (String) ((LinkedHashMap) key.getKeys().get(String.valueOf(key.getLatestVersion()))).get("public_key"); 58 | 59 | return new PublicKey(keyId, localDomain + "/@" + username, pem); 60 | } 61 | 62 | private PublicKey getExternalKey(String keyId){ 63 | VaultResponseSupport result = kv.get(keyId, PublicKey.class); 64 | if (result != null){ 65 | log.debug("Getting PublicKey From Vault KV: " + keyId); 66 | return result.getRequiredData(); 67 | } 68 | 69 | log.debug("Getting PublicKey From ActivityPub: " + keyId); 70 | PublicKey pk = queryService.mono(new FetchExternalPublicKeyById(keyId), PublicKey.class).block(); 71 | kv.put(pk.getId(), pk); 72 | return pk; 73 | } 74 | 75 | String getUsernameFromKeyId(String keyId) { 76 | return keyId.substring(localDomain.length() + 2, keyId.contains("#") ? keyId.indexOf("#") : keyId.length()); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /views/keyholder/src/main/java/social/pantheon/views/keyholder/services/SignatureService.java: -------------------------------------------------------------------------------- 1 | package social.pantheon.views.keyholder.services; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.log4j.Log4j2; 5 | import org.apache.commons.codec.binary.Base64; 6 | import org.axonframework.queryhandling.QueryHandler; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.util.Base64Utils; 9 | import org.springframework.vault.VaultException; 10 | import org.springframework.vault.core.VaultOperations; 11 | import social.pantheon.model.queries.GetPublicKeyById; 12 | import social.pantheon.model.queries.GetSignatureForInput; 13 | import social.pantheon.model.queries.VerifySignatureForInput; 14 | import social.pantheon.model.value.PublicKey; 15 | 16 | import java.security.InvalidKeyException; 17 | import java.security.NoSuchAlgorithmException; 18 | import java.security.Signature; 19 | import java.security.SignatureException; 20 | import java.util.LinkedHashMap; 21 | import java.util.Map; 22 | 23 | @Log4j2 24 | @Service 25 | @RequiredArgsConstructor 26 | public class SignatureService { 27 | 28 | private final VaultOperations vaultOperations; 29 | private final KeyService keyService; 30 | 31 | @QueryHandler 32 | public String handle(GetSignatureForInput query) throws VaultException { 33 | log.debug("Handling " + query); 34 | 35 | Map request = new LinkedHashMap<>(); 36 | request.put("input", Base64Utils.encodeToString(query.getMessage().getBytes())); 37 | request.put("signature_algorithm", "pkcs1v15"); 38 | 39 | return ((String) vaultOperations 40 | .write(String.format("%s/sign/%s", "transit", keyService.getUsernameFromKeyId(query.getKeyId())), request) 41 | .getRequiredData().get("signature")).substring("vault:v1:".length()); 42 | } 43 | 44 | @QueryHandler 45 | public boolean handle(VerifySignatureForInput query) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { 46 | log.debug("Handling " + query); 47 | PublicKey publicKey = keyService.handle(new GetPublicKeyById(query.getKeyId())); 48 | 49 | Signature sign = Signature.getInstance("SHA256withRSA"); 50 | sign.initVerify(publicKey); 51 | sign.update(query.getMessage().getBytes()); 52 | return sign.verify(Base64.decodeBase64(query.getSignature().getBytes())); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /views/keyholder/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=keyholder 2 | spring.cloud.vault.scheme=http 3 | spring.cloud.vault.authentication=approle 4 | spring.cloud.vault.app-role.role-id=${ROLE_ID} 5 | spring.cloud.vault.app-role.secret-id=${SECRET_ID} 6 | spring.cloud.vault.generic.enabled=false 7 | spring.cloud.vault.kv=false -------------------------------------------------------------------------------- /views/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | pantheon 7 | social.pantheon 8 | 0.0.6-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | views 13 | pom 14 | 15 | 16 | actorgraph 17 | keyholder 18 | 19 | 20 | 21 | 22 | social.pantheon 23 | commons 24 | 25 | 26 | org.axonframework 27 | axon-spring-boot-starter 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-web 32 | 33 | 34 | 35 | --------------------------------------------------------------------------------