From 816db24929732fa3967667ec76d95cbfb01068d1 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Sat, 11 Nov 2023 23:15:01 +0300 Subject: Make sure HTTP requests actually use caching * OkHttp dispatcher is not used for synchronous requests * @Cacheable only works for bean method calls, refactor beans to use it correctly --- src/main/java/com/juick/ActivityPubManager.java | 58 +++++++++++++++++++ .../java/com/juick/config/HttpClientConfig.java | 2 - src/main/java/com/juick/config/SecurityConfig.java | 5 +- .../java/com/juick/service/ActivityPubService.java | 65 ---------------------- .../HTTPSignatureAuthenticationFilter.java | 10 ++-- 5 files changed, 66 insertions(+), 74 deletions(-) (limited to 'src') diff --git a/src/main/java/com/juick/ActivityPubManager.java b/src/main/java/com/juick/ActivityPubManager.java index 566cc1fd..461f4731 100644 --- a/src/main/java/com/juick/ActivityPubManager.java +++ b/src/main/java/com/juick/ActivityPubManager.java @@ -19,6 +19,7 @@ package com.juick; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.juick.model.AnonymousUser; import com.juick.model.Message; import com.juick.model.Reaction; import com.juick.model.User; @@ -46,10 +47,14 @@ import io.pebbletemplates.pebble.template.PebbleTemplate; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.core.convert.ConversionService; import org.springframework.http.MediaType; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; +import org.tomitribe.auth.signatures.MissingRequiredHeaderException; +import org.tomitribe.auth.signatures.Signature; +import org.tomitribe.auth.signatures.Verifier; import javax.annotation.Nonnull; import javax.inject.Inject; @@ -57,7 +62,9 @@ import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.net.URI; +import java.security.Key; import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; import java.time.Instant; import java.util.*; import java.util.stream.Collectors; @@ -80,6 +87,10 @@ public class ActivityPubManager implements ActivityListener, NotificationListene ConversionService conversionService; @Inject ObjectMapper jsonMapper; + @Inject + private ApplicationEventPublisher applicationEventPublisher; + @Inject + private KeystoreManager keystoreManager; @Override public void processFollowEvent(@Nonnull FollowEvent followEvent) { @@ -420,4 +431,51 @@ public class ActivityPubManager implements ActivityListener, NotificationListene throw new HttpNotFoundException(); } } + + public User verifyActor(String method, String path, Map headers) { + String signatureString = headers.get("signature"); + if (StringUtils.isNotEmpty(signatureString)) { + Signature signature = Signature.fromString(signatureString); + var keyId = UriComponentsBuilder.fromUriString(signature.getKeyId()).fragment(null).build().toUri(); + var user = activityPubService.getUserByAccountUri(keyId.toASCIIString()); + Key key = null; + Actor actor = null; + if (!user.isAnonymous()) { + // local user + key = keystoreManager.getPublicKey(); + } else { + var context = activityPubService.get(keyId); + if (context.isPresent()) { + actor = (Actor) context.get(); + key = KeystoreManager.publicKeyOf(actor); + } + } + if (key != null) { + Verifier verifier = new Verifier(key, signature); + try { + boolean result = verifier.verify(method.toLowerCase(), path, headers); + if (result) { + if (!user.isAnonymous()) { + return user; + } else { + if (actor != null) { + User person = new User(); + person.setUri(URI.create(actor.getId())); + if (actor.isSuspended()) { + logger.info("{} is suspended, deleting", actor.getId()); + applicationEventPublisher + .publishEvent(new DeleteUserEvent(this, actor.getId())); + } + return person; + } + } + } + } catch (NoSuchAlgorithmException | SignatureException | MissingRequiredHeaderException + | IOException e) { + logger.warn("Verification error for {}: {}", signature.getKeyId(), e.getMessage()); + } + } + } + return AnonymousUser.INSTANCE; + } } diff --git a/src/main/java/com/juick/config/HttpClientConfig.java b/src/main/java/com/juick/config/HttpClientConfig.java index 2540f4db..d838575d 100644 --- a/src/main/java/com/juick/config/HttpClientConfig.java +++ b/src/main/java/com/juick/config/HttpClientConfig.java @@ -28,7 +28,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.inject.Inject; -import java.util.concurrent.Executors; @Configuration public class HttpClientConfig { @@ -38,7 +37,6 @@ public class HttpClientConfig { @Bean public OkHttpClient httpClient() { return new OkHttpClient.Builder() - .dispatcher(new Dispatcher(Executors.newFixedThreadPool(1))) .addInterceptor(new HttpLoggingInterceptor(logger::debug) .setLevel(HttpLoggingInterceptor.Level.BASIC)) .addInterceptor(new ActivityPubRequestInterceptor()) diff --git a/src/main/java/com/juick/config/SecurityConfig.java b/src/main/java/com/juick/config/SecurityConfig.java index e02e32ed..c557ab4e 100644 --- a/src/main/java/com/juick/config/SecurityConfig.java +++ b/src/main/java/com/juick/config/SecurityConfig.java @@ -17,6 +17,7 @@ package com.juick.config; +import com.juick.ActivityPubManager; import com.juick.KeystoreManager; import com.juick.service.ActivityPubService; import com.juick.service.UserService; @@ -105,7 +106,7 @@ public class SecurityConfig { } @Inject - private ActivityPubService activityPubService; + private ActivityPubManager activityPubManager; @Bean HashParamAuthenticationFilter apiAuthenticationFilter() { @@ -190,7 +191,7 @@ public class SecurityConfig { SecurityFilterChain apiChain(HttpSecurity http) throws Exception { http.securityMatcher("/api/**", "/u/**", "/n/**") .addFilterBefore(apiAuthenticationFilter(), BasicAuthenticationFilter.class) - .addFilterBefore(new HTTPSignatureAuthenticationFilter(activityPubService, userService), + .addFilterBefore(new HTTPSignatureAuthenticationFilter(activityPubManager, userService), BasicAuthenticationFilter.class) .authorizeHttpRequests(requests -> requests .requestMatchers(HttpMethod.OPTIONS).permitAll() diff --git a/src/main/java/com/juick/service/ActivityPubService.java b/src/main/java/com/juick/service/ActivityPubService.java index c2d3f1e7..f89f3261 100644 --- a/src/main/java/com/juick/service/ActivityPubService.java +++ b/src/main/java/com/juick/service/ActivityPubService.java @@ -18,10 +18,8 @@ package com.juick.service; import com.fasterxml.jackson.databind.ObjectMapper; -import com.juick.KeystoreManager; import com.juick.model.AnonymousUser; import com.juick.model.User; -import com.juick.service.activities.DeleteUserEvent; import com.juick.util.DateFormattersHolder; import com.juick.www.api.activity.model.Context; import com.juick.www.api.activity.model.objects.Actor; @@ -29,12 +27,10 @@ import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; -import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.annotation.Cacheable; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.core.convert.ConversionService; import org.springframework.dao.DuplicateKeyException; import org.springframework.http.HttpHeaders; @@ -43,21 +39,15 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; import org.tomitribe.auth.signatures.Base64; -import org.tomitribe.auth.signatures.MissingRequiredHeaderException; -import org.tomitribe.auth.signatures.Signature; -import org.tomitribe.auth.signatures.Verifier; import javax.annotation.Nonnull; import javax.inject.Inject; import java.io.IOException; import java.net.URI; -import java.security.Key; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.security.SignatureException; import java.time.Instant; import java.util.List; -import java.util.Map; import java.util.Optional; @Repository @@ -76,10 +66,6 @@ public class ActivityPubService extends BaseJdbcService implements SocialService @Inject private SignatureService signatureService; @Inject - private ApplicationEventPublisher applicationEventPublisher; - @Inject - private KeystoreManager keystoreManager; - @Inject private User serviceUser; @Inject private ConversionService conversionService; @@ -182,55 +168,4 @@ public class ActivityPubService extends BaseJdbcService implements SocialService return response.code(); } } - - public User verifyActor(String method, String path, Map headers) { - String signatureString = headers.get("signature"); - if (StringUtils.isNotEmpty(signatureString)) { - try { - Signature signature = Signature.fromString(signatureString); - var keyId = UriComponentsBuilder.fromUriString(signature.getKeyId()).fragment(null).build().toUri(); - var user = getUserByAccountUri(keyId.toASCIIString()); - Key key = null; - Actor actor = null; - if (!user.isAnonymous()) { - // local user - key = keystoreManager.getPublicKey(); - } else { - var context = get(keyId); - if (context.isPresent()) { - actor = (Actor) context.get(); - key = KeystoreManager.publicKeyOf(actor); - } - } - if (key != null) { - Verifier verifier = new Verifier(key, signature); - try { - boolean result = verifier.verify(method.toLowerCase(), path, headers); - if (result) { - if (!user.isAnonymous()) { - return user; - } else { - if (actor != null) { - User person = new User(); - person.setUri(URI.create(actor.getId())); - if (actor.isSuspended()) { - logger.info("{} is suspended, deleting", actor.getId()); - applicationEventPublisher - .publishEvent(new DeleteUserEvent(this, actor.getId())); - } - return person; - } - } - } - } catch (NoSuchAlgorithmException | SignatureException | MissingRequiredHeaderException - | IOException e) { - logger.warn("Verification error for {}: {}", signature.getKeyId(), e.getMessage()); - } - } - } catch (Exception ex) { - - } - } - return AnonymousUser.INSTANCE; - } } diff --git a/src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java b/src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java index 55c87383..30cb1512 100644 --- a/src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java +++ b/src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java @@ -17,8 +17,8 @@ package com.juick.service.security; +import com.juick.ActivityPubManager; import com.juick.model.User; -import com.juick.service.ActivityPubService; import com.juick.service.UserService; import com.juick.service.security.entities.JuickUser; import jakarta.servlet.FilterChain; @@ -37,13 +37,13 @@ import java.util.stream.Collectors; public class HTTPSignatureAuthenticationFilter extends BaseAuthenticationFilter { - private final ActivityPubService signatureManager; + private final ActivityPubManager activityPubManager; private final UserService userService; public HTTPSignatureAuthenticationFilter( - final ActivityPubService activityPubService, + final ActivityPubManager activityPubManager, final UserService userService) { - this.signatureManager = activityPubService; + this.activityPubManager = activityPubManager; this.userService = userService; } @@ -54,7 +54,7 @@ public class HTTPSignatureAuthenticationFilter extends BaseAuthenticationFilter Map headers = Collections.list(request.getHeaderNames()) .stream() .collect(Collectors.toMap(String::toLowerCase, request::getHeader)); - var user = signatureManager.verifyActor(request.getMethod(), request.getRequestURI(), headers); + var user = activityPubManager.verifyActor(request.getMethod(), request.getRequestURI(), headers); String userUri = user.getUri().toString(); if (!user.isAnonymous() || userUri.length() > 0) { if (userUri.length() == 0) { -- cgit v1.2.3