aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Vitaly Takmazov2023-01-04 03:38:19 +0300
committerGravatar Vitaly Takmazov2023-01-04 05:46:16 +0300
commitc471503ede9aad91193ff6f93966196e6aff15d6 (patch)
tree8c70c8f58b140465be651cd019f26eadd476711f /src
parent086d9a7625bfc5a386f5b1028d364fb546c2fa9d (diff)
OAuth authentication for Mastodon and ActivityPub C2S
Diffstat (limited to 'src')
-rw-r--r--src/main/java/com/juick/config/SecurityConfig.java104
-rw-r--r--src/main/java/com/juick/service/security/annotation/Visitor.java29
-rw-r--r--src/main/java/com/juick/www/SiteAttributesHandler.java25
-rw-r--r--src/main/java/com/juick/www/ad/SapeService.java3
-rw-r--r--src/main/java/com/juick/www/api/Mastodon.java118
-rw-r--r--src/main/java/com/juick/www/api/Messages.java18
-rw-r--r--src/main/java/com/juick/www/api/Notifications.java88
-rw-r--r--src/main/java/com/juick/www/api/PM.java12
-rw-r--r--src/main/java/com/juick/www/api/Post.java21
-rw-r--r--src/main/java/com/juick/www/api/Service.java12
-rw-r--r--src/main/java/com/juick/www/api/Tags.java8
-rw-r--r--src/main/java/com/juick/www/api/Users.java27
-rw-r--r--src/main/java/com/juick/www/api/activity/Profile.java13
-rw-r--r--src/main/java/com/juick/www/controllers/Help.java4
-rw-r--r--src/main/java/com/juick/www/controllers/Settings.java8
-rw-r--r--src/main/java/com/juick/www/controllers/SignUp.java6
-rw-r--r--src/main/java/com/juick/www/controllers/Site.java32
-rw-r--r--src/main/java/com/juick/www/controllers/SocialLogin.java11
-rw-r--r--src/main/java/com/juick/www/rss/Feeds.java10
-rw-r--r--src/main/java/com/juick/www/rss/MessagesView.java62
-rw-r--r--src/main/resources/db/migration/V1.29__oauth_clients.sql15
-rw-r--r--src/test/java/com/juick/server/tests/ServerTests.java17
22 files changed, 383 insertions, 260 deletions
diff --git a/src/main/java/com/juick/config/SecurityConfig.java b/src/main/java/com/juick/config/SecurityConfig.java
index d2030a62..d60abe00 100644
--- a/src/main/java/com/juick/config/SecurityConfig.java
+++ b/src/main/java/com/juick/config/SecurityConfig.java
@@ -25,20 +25,37 @@ import com.juick.service.security.HTTPSignatureAuthenticationFilter;
import com.juick.service.security.HashParamAuthenticationFilter;
import com.juick.service.security.JuickUserDetailsService;
import com.juick.service.security.entities.JuickUser;
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
+import com.nimbusds.jose.jwk.source.JWKSource;
+import com.nimbusds.jose.proc.SecurityContext;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
+import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
+import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
+import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.SecurityFilterChain;
-import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
-import org.springframework.security.web.authentication.RememberMeServices;
-import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
+import org.springframework.security.web.authentication.*;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
@@ -48,8 +65,12 @@ import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import javax.inject.Inject;
+import java.io.IOException;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
import java.util.Arrays;
import java.util.Collections;
+import java.util.UUID;
/**
* Created by aalexeev on 11/21/16.
@@ -61,7 +82,8 @@ public class SecurityConfig {
private UserService userService;
@Inject
private KeystoreManager keystoreManager;
-
+ @Inject
+ private JdbcTemplate jdbcTemplate;
private static final String COOKIE_NAME = "juick-remember-me";
@Bean
UserDetailsService userDetailsService() {
@@ -77,6 +99,7 @@ public class SecurityConfig {
configuration.setAllowedHeaders(Collections.singletonList("*"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ source.registerCorsConfiguration("/oauth/**", configuration);
source.registerCorsConfiguration("/api/**", configuration);
source.registerCorsConfiguration("/u/**", configuration);
source.registerCorsConfiguration("/n/**", configuration);
@@ -124,14 +147,60 @@ public class SecurityConfig {
services.setUseSecureCookie(false); // TODO set true if https is supports
return services;
}
+ @Bean
+ @Order(Ordered.HIGHEST_PRECEDENCE)
+ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
+ throws Exception {
+ OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
+ http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
+ .authorizationServerSettings(AuthorizationServerSettings.builder()
+ .authorizationEndpoint("/oauth/authorize")
+ .tokenEndpoint("/oauth/token")
+ .build())
+ .oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
+ http
+ // Redirect to the login page when not authenticated from the
+ // authorization endpoint
+ .exceptionHandling((exceptions) -> exceptions
+ .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
+ )
+ // Accept access tokens for User Info and/or Client Registration
+ .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
+
+ return http.formLogin(Customizer.withDefaults()).build();
+ }
+ @Bean
+ public RegisteredClientRepository registeredClientRepository() {
+ return new JdbcRegisteredClientRepository(jdbcTemplate);
+ }
+
+ @Bean
+ public JWKSource<SecurityContext> jwkSource() {
+ RSAPublicKey publicKey = (RSAPublicKey) keystoreManager.getPublicKey();
+ RSAPrivateKey privateKey = (RSAPrivateKey) keystoreManager.getPrivateKey();
+ RSAKey rsaKey = new RSAKey.Builder(publicKey)
+ .privateKey(privateKey)
+ .keyID(UUID.randomUUID().toString())
+ .build();
+ JWKSet jwkSet = new JWKSet(rsaKey);
+ return new ImmutableJWKSet<>(jwkSet);
+ }
+ @Bean
+ public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
+ return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
+ }
+ @Bean
+ public AuthorizationServerSettings authorizationServerSettings() {
+ return AuthorizationServerSettings.builder().build();
+ }
@Bean
+ @Order(2)
SecurityFilterChain apiChain(HttpSecurity http) throws Exception {
http.securityMatcher("/api/**")
.addFilterBefore(apiAuthenticationFilter(), BasicAuthenticationFilter.class)
.addFilterBefore(new HTTPSignatureAuthenticationFilter(signatureManager, userService),
BasicAuthenticationFilter.class)
- .addFilterBefore(bearerTokenAuthenticationFilter(), BasicAuthenticationFilter.class)
.authorizeHttpRequests(requests -> requests
.requestMatchers(HttpMethod.OPTIONS).permitAll()
.requestMatchers("/api/", "/api/messages", "/api/avatar",
@@ -142,15 +211,16 @@ public class SecurityConfig {
"/api/_vklogin", "/api/_tglogin",
"/api/_google", "/api/_applelogin", "/api/signup",
"/api/inbox", "/api/events", "/api/u/",
- "/api/info/**",
- "/api/nodeinfo/2.0")
+ "/api/info/**", "/api/v1/apps", "/api/v1/instance",
+ "/api/nodeinfo/2.0", "/oauth/**")
.permitAll()
- .anyRequest().hasRole("USER"))
+ .anyRequest().hasAnyAuthority("SCOPE_write", "ROLE_USER"))
.anonymous(anonymous -> anonymous.principal(JuickUser.ANONYMOUS_USER)
.authorities(JuickUser.ANONYMOUS_AUTHORITY))
.httpBasic(httpBasic -> httpBasic
.authenticationEntryPoint(apiAuthenticationEntryPoint()))
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
+ .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.sessionManagement(sessionManagement -> sessionManagement
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling(exceptionHandling -> exceptionHandling
@@ -159,14 +229,6 @@ public class SecurityConfig {
.headers().defaultsDisabled().cacheControl();
return http.build();
}
-
- @Bean
- AuthenticationSuccessHandler successHandler() {
- SimpleUrlAuthenticationSuccessHandler handler = new SimpleUrlAuthenticationSuccessHandler();
- handler.setUseReferer(true);
- return handler;
- }
-
@Bean
SecurityFilterChain h2ConsoFilterChain(HttpSecurity http) throws Exception {
http.securityMatcher("/h2-console/**")
@@ -182,7 +244,12 @@ public class SecurityConfig {
.headers().defaultsDisabled().cacheControl();
return http.build();
}
-
+ @Bean
+ AuthenticationSuccessHandler successHandler() {
+ var handler = new SavedRequestAwareAuthenticationSuccessHandler();
+ handler.setUseReferer(true);
+ return handler;
+ }
@Bean
SecurityFilterChain wwwChain(HttpSecurity http) throws Exception {
http.addFilterBefore(wwwAuthenticationFilter(), BasicAuthenticationFilter.class)
@@ -197,9 +264,6 @@ public class SecurityConfig {
.authorities(JuickUser.ANONYMOUS_AUTHORITY))
.cors(cors -> cors
.configurationSource(corsConfigurationSource()))
- .sessionManagement(
- sessionManagement -> sessionManagement
- .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.logout(logout -> logout
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.invalidateHttpSession(true)
diff --git a/src/main/java/com/juick/service/security/annotation/Visitor.java b/src/main/java/com/juick/service/security/annotation/Visitor.java
deleted file mode 100644
index 2bad7e4b..00000000
--- a/src/main/java/com/juick/service/security/annotation/Visitor.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2008-2020, Juick
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.juick.service.security.annotation;
-
-import org.springframework.security.core.annotation.AuthenticationPrincipal;
-
-import java.lang.annotation.*;
-
-@Target({ ElementType.PARAMETER, ElementType.TYPE })
-@Retention(RetentionPolicy.RUNTIME)
-@Documented
-@AuthenticationPrincipal(errorOnInvalidType = true, expression = "user")
-public @interface Visitor {
-}
diff --git a/src/main/java/com/juick/www/SiteAttributesHandler.java b/src/main/java/com/juick/www/SiteAttributesHandler.java
index e06a2070..4ead9d1e 100644
--- a/src/main/java/com/juick/www/SiteAttributesHandler.java
+++ b/src/main/java/com/juick/www/SiteAttributesHandler.java
@@ -17,6 +17,14 @@
package com.juick.www;
+import com.juick.model.AnonymousUser;
+import com.juick.model.User;
+import com.juick.service.UserService;
+import com.juick.service.security.entities.JuickUser;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
@@ -24,8 +32,12 @@ import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import jakarta.servlet.http.HttpServletResponse;
+import javax.inject.Inject;
+
@ControllerAdvice
public class SiteAttributesHandler {
+ @Inject
+ private UserService userService;
@ModelAttribute
public void setVaryResponseHeader(HttpServletResponse response) {
response.setHeader("Vary", "Accept-Language");
@@ -34,4 +46,17 @@ public class SiteAttributesHandler {
public void setReturnPathAttribute(Model model) {
model.addAttribute("retpath", ServletUriComponentsBuilder.fromCurrentRequestUri().toUriString());
}
+ @ModelAttribute
+ public User visitor(Model model, @AuthenticationPrincipal Object principal) {
+ if (principal != null) {
+ if (principal instanceof JuickUser) {
+ return ((JuickUser) principal).getUser();
+ }
+ if (principal instanceof Jwt) {
+ var uname = (String) ((Jwt) principal).getClaims().get("sub");
+ return userService.getUserByName(uname);
+ }
+ }
+ return AnonymousUser.INSTANCE;
+ }
}
diff --git a/src/main/java/com/juick/www/ad/SapeService.java b/src/main/java/com/juick/www/ad/SapeService.java
index 3c35f320..7b398806 100644
--- a/src/main/java/com/juick/www/ad/SapeService.java
+++ b/src/main/java/com/juick/www/ad/SapeService.java
@@ -18,7 +18,6 @@
package com.juick.www.ad;
import com.juick.model.User;
-import com.juick.service.security.annotation.Visitor;
import com.juick.www.controllers.Site;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -47,7 +46,7 @@ public class SapeService {
@ModelAttribute
public void addSapeLinks(
- @Visitor User visitor,
+ @ModelAttribute User visitor,
@CookieValue(name = "sape_cookie", required = false, defaultValue = StringUtils.EMPTY) String sapeCookie,
@RequestParam(required = false, defaultValue = "0") int before,
@RequestParam(name = "show", required = false) String paramShow,
diff --git a/src/main/java/com/juick/www/api/Mastodon.java b/src/main/java/com/juick/www/api/Mastodon.java
new file mode 100644
index 00000000..69f0f4f6
--- /dev/null
+++ b/src/main/java/com/juick/www/api/Mastodon.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2008-2023, Juick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.juick.www.api;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.juick.model.User;
+import com.juick.service.UserService;
+import com.juick.www.WebApp;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.MediaType;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
+import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
+import org.springframework.web.bind.annotation.*;
+
+import javax.inject.Inject;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.UUID;
+
+@RestController
+public class Mastodon {
+ @Value("${web_domain:localhost}")
+ private String domain;
+ @Inject
+ WebApp webApp;
+ @Inject
+ UserService userService;
+ @Inject
+ RegisteredClientRepository registeredClientRepository;
+
+ public record ApplicationRequest(@JsonProperty("client_name") String clientName,
+ @JsonProperty("redirect_uris") String redirectUris,
+ @JsonProperty("scopes") String scopes) {
+ }
+
+ public record ApplicationResponse(String id,
+ @JsonProperty("client_name") String name,
+ @JsonProperty("redirect_uri") String redirectUri,
+ @JsonProperty("client_id") String clientId,
+ @JsonProperty("client_secret") String clientSecret) {
+ }
+
+ public record CredentialAccount(String id, String username, String acct,
+ @JsonProperty("display_name") String displayName,
+ @JsonProperty("followers_count") Integer followersCount,
+ @JsonProperty("following_count") Integer followingCount,
+ String avatar) {
+
+ }
+ private Collection<String> parseScopes(String s) {
+ return s != null ? Arrays.asList(s.split(" ")) : Collections.emptyList();
+ }
+
+ @PostMapping(value = "/api/v1/apps", consumes = { MediaType.APPLICATION_JSON_VALUE })
+ public ApplicationResponse apps(@RequestBody ApplicationRequest application) {
+ return apps(application.clientName(), application.redirectUris(), application.scopes());
+ }
+
+ @PostMapping(value = "/api/v1/apps", consumes = { MediaType.APPLICATION_FORM_URLENCODED_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE })
+ public ApplicationResponse apps(@RequestParam("client_name") String clientName,
+ @RequestParam("redirect_uris") String redirectUris,
+ @RequestParam("scopes") String scopes) {
+ var secret = UUID.randomUUID().toString();
+ RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
+ .clientId(UUID.randomUUID().toString())
+ .clientSecret("{noop}" + secret)
+ .clientName(clientName)
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
+ .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
+ .redirectUri(redirectUris)
+ .scopes((coll) -> coll.addAll(parseScopes(scopes)))
+ .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build();
+ registeredClientRepository.save(registeredClient);
+ return new ApplicationResponse(
+ registeredClient.getId(),
+ registeredClient.getClientName(),
+ String.join(",", registeredClient.getRedirectUris()),
+ registeredClient.getClientId(),
+ secret
+ );
+ }
+ public record Instance(String domain) {}
+ @GetMapping("/api/v1/instance")
+ public Instance getInstance() {
+ return new Instance(domain);
+ }
+ @GetMapping("/api/v1/accounts/verify_credentials")
+ public CredentialAccount account(@ModelAttribute User visitor) {
+ return new CredentialAccount(
+ String.valueOf(visitor.getUid()),
+ visitor.getName(),
+ visitor.getName(),
+ visitor.getFullName(),
+ userService.getUserReaders(visitor.getUid()).size(),
+ userService.getUserFriends(visitor.getUid()).size(),
+ webApp.getAvatarUrl(visitor)
+ );
+ }
+}
diff --git a/src/main/java/com/juick/www/api/Messages.java b/src/main/java/com/juick/www/api/Messages.java
index 2993e805..c23976f4 100644
--- a/src/main/java/com/juick/www/api/Messages.java
+++ b/src/main/java/com/juick/www/api/Messages.java
@@ -30,14 +30,11 @@ import com.juick.service.MessagesService;
import com.juick.service.TagService;
import com.juick.service.UserService;
import com.juick.service.component.SystemEvent;
-import com.juick.service.security.annotation.Visitor;
import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
-import org.springframework.security.access.annotation.Secured;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
@@ -70,9 +67,8 @@ public class Messages {
// TODO: serialize image urls
- @GetMapping("/api/home")
- @Secured("ROLE_USER")
- public List<Message> getHome(@Visitor User visitor,
+ @GetMapping({"/api/home"})
+ public List<Message> getHome(@ModelAttribute User visitor,
@RequestParam(defaultValue = "0") int before_mid) {
int vuid = visitor.getUid();
List<Integer> mids = messagesService.getMyFeed(vuid, before_mid, true);
@@ -82,7 +78,7 @@ public class Messages {
}
@GetMapping("/api/messages")
- public List<Message> getMessages(@Visitor User visitor,
+ public List<Message> getMessages(@ModelAttribute User visitor,
@RequestParam(required = false) String uname,
@RequestParam(name = "before_mid", defaultValue = "0") Integer before,
@RequestParam(required = false, defaultValue = "0") Integer daysback,
@@ -140,7 +136,7 @@ public class Messages {
}
@DeleteMapping("/api/messages")
- public CommandResult deleteMessage(@Visitor User visitor, @RequestParam int mid,
+ public CommandResult deleteMessage(@ModelAttribute User visitor, @RequestParam int mid,
@RequestParam(required = false, defaultValue = "0") int rid) {
if (rid > 0) {
if (messagesService.deleteReply(visitor.getUid(), mid, rid)) {
@@ -154,7 +150,7 @@ public class Messages {
}
@GetMapping("/api/messages/discussions")
- public List<Message> getDiscussions(@Visitor User visitor,
+ public List<Message> getDiscussions(@ModelAttribute User visitor,
@RequestParam(required = false, defaultValue = "0") Long to) {
List<Message> msgs = messagesService.getMessages(visitor, messagesService.getDiscussions(visitor.getUid(), to));
msgs.forEach(m -> m.getUser().setAvatar(webApp.getAvatarUrl(m.getUser())));
@@ -162,7 +158,7 @@ public class Messages {
}
@GetMapping("/api/thread")
- public List<Message> getThread(@Visitor User visitor, @RequestParam(defaultValue = "0") int mid,
+ public List<Message> getThread(@ModelAttribute User visitor, @RequestParam(defaultValue = "0") int mid,
@RequestParam(defaultValue = "true") boolean showReplies) {
Optional<Message> message = messagesService.getMessage(mid);
if (message.isPresent()) {
@@ -192,7 +188,7 @@ public class Messages {
}
@GetMapping(value = "/api/thread/mark_read/{mid}-{rid}.gif", produces = MediaType.IMAGE_GIF_VALUE)
- public byte[] markThreadRead(@Visitor User visitor, @PathVariable int mid, @PathVariable int rid)
+ public byte[] markThreadRead(@ModelAttribute User visitor, @PathVariable int mid, @PathVariable int rid)
throws IOException {
if (!visitor.isAnonymous()) {
messagesService.setLastReadComment(visitor, mid, rid);
diff --git a/src/main/java/com/juick/www/api/Notifications.java b/src/main/java/com/juick/www/api/Notifications.java
index 09dad9e2..32ba3dc1 100644
--- a/src/main/java/com/juick/www/api/Notifications.java
+++ b/src/main/java/com/juick/www/api/Notifications.java
@@ -17,36 +17,19 @@
package com.juick.www.api;
-import java.util.Collections;
-import java.util.List;
-import java.util.stream.Collectors;
-
-import javax.inject.Inject;
-
-import com.juick.model.AnonymousUser;
-import com.juick.model.ExternalToken;
-import com.juick.model.Message;
-import com.juick.model.Status;
-import com.juick.model.User;
-import com.juick.service.MessagesService;
-import com.juick.service.PushQueriesService;
-import com.juick.service.SubscriptionService;
-import com.juick.service.TelegramService;
-import com.juick.service.UserService;
-import com.juick.service.security.annotation.Visitor;
+import com.juick.model.*;
+import com.juick.service.*;
import com.juick.util.HttpBadRequestException;
import com.juick.util.HttpForbiddenException;
-
+import io.swagger.v3.oas.annotations.Hidden;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
-import io.swagger.v3.oas.annotations.Hidden;
+import javax.inject.Inject;
+import java.util.Collections;
+import java.util.List;
/**
* Created by vitalyster on 24.10.2016.
@@ -88,7 +71,7 @@ public class Notifications {
@Hidden
@RequestMapping(value = "/api/notifications", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public List<User> doGet(
- @Visitor User visitor,
+ @ModelAttribute(binding = false) User visitor,
@RequestParam(required = false, defaultValue = "0") int uid,
@RequestParam(required = false, defaultValue = "0") int mid,
@RequestParam(required = false, defaultValue = "0") int rid) {
@@ -127,24 +110,17 @@ public class Notifications {
@Hidden
@RequestMapping(value = "/api/notifications", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE)
public Status doDelete(
- @Visitor User visitor,
+ @ModelAttribute User visitor,
@RequestBody List<ExternalToken> list) {
if (!visitor.equals(serviceUser)) {
throw new HttpForbiddenException();
}
list.forEach(t -> {
switch (t.type()) {
- case "gcm":
- pushQueriesService.deleteGCMToken(t.token());
- break;
- case "apns":
- pushQueriesService.deleteAPNSToken(t.token());
- break;
- case "mpns":
- pushQueriesService.deleteMPNSToken(t.token());
- break;
- default:
- throw new HttpBadRequestException();
+ case "gcm" -> pushQueriesService.deleteGCMToken(t.token());
+ case "apns" -> pushQueriesService.deleteAPNSToken(t.token());
+ case "mpns" -> pushQueriesService.deleteMPNSToken(t.token());
+ default -> throw new HttpBadRequestException();
}
});
@@ -153,24 +129,17 @@ public class Notifications {
@Hidden
@RequestMapping(value = "/api/notifications/delete", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public Status doDeleteTokens(
- @Visitor User visitor,
+ @ModelAttribute User visitor,
@RequestBody List<ExternalToken> list) {
if (!visitor.equals(serviceUser)) {
throw new HttpForbiddenException();
}
list.forEach(t -> {
switch (t.type()) {
- case "gcm":
- pushQueriesService.deleteGCMToken(t.token());
- break;
- case "apns":
- pushQueriesService.deleteAPNSToken(t.token());
- break;
- case "mpns":
- pushQueriesService.deleteMPNSToken(t.token());
- break;
- default:
- throw new HttpBadRequestException();
+ case "gcm" -> pushQueriesService.deleteGCMToken(t.token());
+ case "apns" -> pushQueriesService.deleteAPNSToken(t.token());
+ case "mpns" -> pushQueriesService.deleteMPNSToken(t.token());
+ default -> throw new HttpBadRequestException();
}
});
@@ -180,21 +149,14 @@ public class Notifications {
@Hidden
@RequestMapping(value = "/api/notifications", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE)
public Status doPut(
- @Visitor User visitor,
+ @ModelAttribute User visitor,
@RequestBody List<ExternalToken> list) {
list.forEach(t -> {
switch (t.type()) {
- case "gcm":
- pushQueriesService.addGCMToken(visitor.getUid(), t.token());
- break;
- case "apns":
- pushQueriesService.addAPNSToken(visitor.getUid(), t.token());
- break;
- case "mpns":
- pushQueriesService.addMPNSToken(visitor.getUid(), t.token());
- break;
- default:
- throw new HttpBadRequestException();
+ case "gcm" -> pushQueriesService.addGCMToken(visitor.getUid(), t.token());
+ case "apns" -> pushQueriesService.addAPNSToken(visitor.getUid(), t.token());
+ case "mpns" -> pushQueriesService.addMPNSToken(visitor.getUid(), t.token());
+ default -> throw new HttpBadRequestException();
}
});
return Status.OK;
@@ -203,7 +165,7 @@ public class Notifications {
@Deprecated
@RequestMapping(value = "/api/android/register", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public Status doAndroidRegister(
- @Visitor User visitor,
+ @ModelAttribute User visitor,
@RequestParam(name = "regid") String regId) {
pushQueriesService.addGCMToken(visitor.getUid(), regId);
return Status.OK;
@@ -212,7 +174,7 @@ public class Notifications {
@Deprecated
@RequestMapping(value = "/api/winphone/register", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public Status doWinphoneRegister(
- @Visitor User visitor,
+ @ModelAttribute User visitor,
@RequestParam(name = "url") String regId) {
pushQueriesService.addMPNSToken(visitor.getUid(), regId);
return Status.OK;
diff --git a/src/main/java/com/juick/www/api/PM.java b/src/main/java/com/juick/www/api/PM.java
index 96387dce..c4acd4b3 100644
--- a/src/main/java/com/juick/www/api/PM.java
+++ b/src/main/java/com/juick/www/api/PM.java
@@ -29,13 +29,9 @@ import com.juick.www.WebApp;
import com.juick.service.ChatService;
import com.juick.service.UserService;
import com.juick.service.component.SystemEvent;
-import com.juick.service.security.annotation.Visitor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.MediaType;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
import javax.inject.Inject;
import java.util.Collections;
@@ -57,7 +53,7 @@ public class PM {
@RequestMapping(value = "/api/pm", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public List<Message> doGetPM(
- @Visitor User visitor,
+ @ModelAttribute User visitor,
@RequestParam(required = false) String uname) {
int uid = 0;
if (uname != null && uname.matches("^[a-zA-Z0-9\\-]{2,16}$")) {
@@ -75,7 +71,7 @@ public class PM {
@RequestMapping(value = "/api/pm", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public Message doPostPM(
- @Visitor User visitor,
+ @ModelAttribute User visitor,
@RequestParam String uname,
@RequestParam String body) {
User userTo = AnonymousUser.INSTANCE;
@@ -107,7 +103,7 @@ public class PM {
}
@RequestMapping(value = "/api/groups_pms", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public PrivateChats doGetGroupsPMs(
- @Visitor User visitor,
+ @ModelAttribute User visitor,
@RequestParam(defaultValue = "5") int cnt) {
// TODO: ignore cnt param for now but make sure paging param will not be cnt
diff --git a/src/main/java/com/juick/www/api/Post.java b/src/main/java/com/juick/www/api/Post.java
index c840a590..2a92178d 100644
--- a/src/main/java/com/juick/www/api/Post.java
+++ b/src/main/java/com/juick/www/api/Post.java
@@ -37,7 +37,6 @@ import com.juick.service.MessagesService;
import com.juick.service.StorageService;
import com.juick.service.UserService;
import com.juick.service.activities.UpdateEvent;
-import com.juick.service.security.annotation.Visitor;
import com.juick.util.HttpBadRequestException;
import com.juick.util.HttpForbiddenException;
import com.juick.util.HttpNotFoundException;
@@ -49,13 +48,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.ResponseStatus;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
/**
@@ -81,7 +74,7 @@ public class Post {
@RequestMapping(value = "/api/post", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(value = HttpStatus.OK)
public CommandResult doPostMessage(
- @Visitor User visitor,
+ @ModelAttribute User visitor,
@RequestParam(required = false, defaultValue = StringUtils.EMPTY) String body,
@RequestParam(required = false) String img,
@RequestParam(required = false) MultipartFile attach) throws Exception {
@@ -112,7 +105,7 @@ public class Post {
@RequestMapping(value = "/api/comment", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public CommandResult doPostComment(
- @Visitor User visitor,
+ @ModelAttribute User visitor,
@RequestParam(defaultValue = "0") int mid,
@RequestParam(defaultValue = "0") int rid,
@RequestParam(required = false, defaultValue = StringUtils.EMPTY) final String body,
@@ -164,7 +157,7 @@ public class Post {
@PostMapping("/api/like")
@ResponseStatus(value = HttpStatus.OK)
- public Status doPostRecommendation(@Visitor User visitor, @RequestParam Integer mid) throws Exception {
+ public Status doPostRecommendation(@ModelAttribute User visitor, @RequestParam Integer mid) throws Exception {
Optional<Message> message = messagesService.getMessage(mid);
if (message.isEmpty()) {
throw new HttpNotFoundException();
@@ -180,7 +173,7 @@ public class Post {
@PostMapping("/api/subscribe")
@ResponseStatus(value = HttpStatus.OK)
- public Status doPostSubscribe(@Visitor User visitor,
+ public Status doPostSubscribe(@ModelAttribute User visitor,
@RequestParam Integer mid) throws Exception {
Optional<Message> message = messagesService.getMessage(mid);
if (message.isEmpty()) {
@@ -204,7 +197,7 @@ public class Post {
@PostMapping("/api/react")
@ResponseStatus(value = HttpStatus.OK)
public Status doPostReact(
- @Visitor User visitor,
+ @ModelAttribute User visitor,
@RequestParam Integer mid, @RequestParam @NotNull int reactionId,
@RequestParam(required = false, defaultValue = "1") int count) {
@@ -226,7 +219,7 @@ public class Post {
}
@PostMapping("/api/update")
- public CommandResult updateMessage(@Visitor User visitor,
+ public CommandResult updateMessage(@ModelAttribute User visitor,
@RequestParam Integer mid,
@RequestParam(required = false, defaultValue = "0") Integer rid,
@RequestParam String body) {
diff --git a/src/main/java/com/juick/www/api/Service.java b/src/main/java/com/juick/www/api/Service.java
index 3bb760ff..f4599a56 100644
--- a/src/main/java/com/juick/www/api/Service.java
+++ b/src/main/java/com/juick/www/api/Service.java
@@ -30,7 +30,6 @@ import com.juick.service.MessagesService;
import com.juick.service.StorageService;
import com.juick.service.UserService;
import com.juick.service.component.AccountVerificationEvent;
-import com.juick.service.security.annotation.Visitor;
import io.swagger.v3.oas.annotations.Hidden;
import jakarta.mail.Session;
import jakarta.mail.internet.AddressException;
@@ -49,10 +48,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.ExceptionHandler;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
@@ -92,7 +88,7 @@ public class Service {
@Hidden
@PostMapping("/api/mail")
@ResponseStatus(value = HttpStatus.OK)
- public void processMail(@Visitor User current, InputStream data) throws Exception {
+ public void processMail(@ModelAttribute User current, InputStream data) throws Exception {
if (current.equals(serviceUser)) {
MimeMessage msg = new MimeMessage(session, data);
String[] returnPaths = msg.getHeader("Return-Path");
@@ -207,7 +203,7 @@ public class Service {
@Hidden
@PostMapping("/api/mail/unsubscribe")
@ResponseStatus(value = HttpStatus.OK)
- public void processMailUnsubscribe(@Visitor User current, InputStream data) throws Exception {
+ public void processMailUnsubscribe(@ModelAttribute User current, InputStream data) throws Exception {
if (current.equals(serviceUser)) {
MimeMessage msg = new MimeMessage(session, data);
String from = msg.getFrom() == null || msg.getFrom().length > 1
@@ -231,7 +227,7 @@ public class Service {
}
@GetMapping("/api/events")
- public SseEmitter handle(@Visitor User visitor) {
+ public SseEmitter handle(@ModelAttribute User visitor) {
logger.info("{} connected", visitor.getName());
if (!visitor.isAnonymous()) {
userService.updateLastSeen(visitor);
diff --git a/src/main/java/com/juick/www/api/Tags.java b/src/main/java/com/juick/www/api/Tags.java
index 7d934f38..2b6405ac 100644
--- a/src/main/java/com/juick/www/api/Tags.java
+++ b/src/main/java/com/juick/www/api/Tags.java
@@ -20,12 +20,8 @@ package com.juick.www.api;
import com.juick.model.User;
import com.juick.model.TagStats;
import com.juick.service.TagService;
-import com.juick.service.security.annotation.Visitor;
import org.springframework.http.MediaType;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
import javax.inject.Inject;
import java.util.List;
@@ -40,7 +36,7 @@ public class Tags {
@RequestMapping(value = "/api/tags", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public List<TagStats> tags(
- @Visitor User visitor,
+ @ModelAttribute User visitor,
@RequestParam(required = false, defaultValue = "0") int user_id
) {
if (user_id > 0) {
diff --git a/src/main/java/com/juick/www/api/Users.java b/src/main/java/com/juick/www/api/Users.java
index dd620380..f7c24d8d 100644
--- a/src/main/java/com/juick/www/api/Users.java
+++ b/src/main/java/com/juick/www/api/Users.java
@@ -36,7 +36,6 @@ import com.juick.service.TelegramService;
import com.juick.service.UserService;
import com.juick.service.activities.UpdateUserEvent;
import com.juick.service.component.MailVerificationEvent;
-import com.juick.service.security.annotation.Visitor;
import com.juick.util.HttpBadRequestException;
import com.juick.util.HttpNotFoundException;
import com.juick.util.HttpUtils;
@@ -48,13 +47,7 @@ import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.MediaType;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
/**
@@ -80,13 +73,13 @@ public class Users {
private ApplicationEventPublisher applicationEventPublisher;
@RequestMapping(value = "/api/auth", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
- public String getAuthToken(@Visitor User visitor) {
+ public String getAuthToken(@ModelAttribute User visitor) {
return userService.getHashByUID(visitor.getUid());
}
@RequestMapping(value = "/api/users", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public List<User> doGetUsers(
- @Visitor User visitor,
+ @ModelAttribute User visitor,
@RequestParam(value = "uname", required = false) List<String> unames) {
List<User> users = new ArrayList<>();
@@ -108,7 +101,7 @@ public class Users {
}
@GetMapping("/api/me")
- public SecureUser getMe(@Visitor User visitor) {
+ public SecureUser getMe(@ModelAttribute User visitor) {
SecureUser me = new SecureUser();
me.setUid(visitor.getUid());
me.setName(visitor.getName());
@@ -127,7 +120,7 @@ public class Users {
return (SecureUser)userService.getUserInfo(me);
}
@PostMapping("/api/me")
- public void updateMe(@Visitor User visitor,
+ public void updateMe(@ModelAttribute User visitor,
@RequestParam(required = false) String password,
@RequestParam(value = "jid-del", required = false) String jidForDeletion,
@RequestParam(value = "email-add", required = false) String newEmail,
@@ -171,12 +164,12 @@ public class Users {
}
}
@PostMapping("/api/me/subscribe")
- public void subscribeMe(@Visitor User visitor, String email) {
+ public void subscribeMe(@ModelAttribute User visitor, String email) {
// TODO: check status
emailService.setNotificationsEmail(visitor.getUid(), email);
}
@PostMapping("/api/me/upload")
- public void updateInfo(@Visitor User visitor,
+ public void updateInfo(@ModelAttribute User visitor,
@RequestParam MultipartFile avatar) throws IOException {
String avatarTmpPath = HttpUtils.receiveMultiPartFile(avatar, storageService.getTemporaryDirectory()).getHost();
if (StringUtils.isNotEmpty(avatarTmpPath)) {
@@ -187,7 +180,7 @@ public class Users {
@RequestMapping(value = "/api/users/read", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public List<User> doGetUserRead(
- @Visitor User visitor,
+ @ModelAttribute User visitor,
@RequestParam String uname) {
int uid = 0;
if (uname == null) {
@@ -211,7 +204,7 @@ public class Users {
@RequestMapping(value = "/api/users/readers", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public List<User> doGetUserReaders(
- @Visitor User visitor,
+ @ModelAttribute User visitor,
@RequestParam String uname) {
int uid = 0;
if (uname == null) {
@@ -234,7 +227,7 @@ public class Users {
}
@GetMapping("/api/info/{uname}")
- public User getUserInfo(@Visitor User visitor, @PathVariable String uname) {
+ public User getUserInfo(@ModelAttribute User visitor, @PathVariable String uname) {
User user = userService.getUserByName(uname);
if (!user.isBanned()) {
user.setRead(doGetUserRead(visitor, uname));
diff --git a/src/main/java/com/juick/www/api/activity/Profile.java b/src/main/java/com/juick/www/api/activity/Profile.java
index 404e0734..7a3fdf29 100644
--- a/src/main/java/com/juick/www/api/activity/Profile.java
+++ b/src/main/java/com/juick/www/api/activity/Profile.java
@@ -48,7 +48,6 @@ import com.juick.service.activities.DirectMessageEvent;
import com.juick.service.activities.FollowEvent;
import com.juick.service.activities.UndoAnnounceEvent;
import com.juick.service.activities.UndoFollowEvent;
-import com.juick.service.security.annotation.Visitor;
import com.overzealous.remark.Remark;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
@@ -61,11 +60,7 @@ import org.springframework.core.convert.ConversionService;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import org.springframework.web.util.UriComponentsBuilder;
@@ -136,8 +131,8 @@ public class Profile {
@GetMapping(value = "/u/{userName}/blog", produces = { Context.LD_JSON_MEDIA_TYPE,
Context.ACTIVITYSTREAMS_PROFILE_MEDIA_TYPE })
- public OrderedCollectionPage getOutboxPage(@Visitor User visitor, @PathVariable String userName,
- @RequestParam(required = false, defaultValue = "0") int before) {
+ public OrderedCollectionPage getOutboxPage(@ModelAttribute User visitor, @PathVariable String userName,
+ @RequestParam(required = false, defaultValue = "0") int before) {
User user = userService.getUserByName(userName);
if (!user.isAnonymous() && !user.isBanned()) {
UriComponentsBuilder uri = UriComponentsBuilder.fromUriString(baseUri);
@@ -273,7 +268,7 @@ public class Profile {
@CacheEvict(cacheNames = "profiles", key = "{ #visitor.uri }")
@PostMapping(value = "/api/inbox", consumes = { Context.LD_JSON_MEDIA_TYPE,
Context.ACTIVITYSTREAMS_PROFILE_MEDIA_TYPE })
- public ResponseEntity<CommandResult> processInbox(@Visitor User visitor, InputStream inboxData) throws Exception {
+ public ResponseEntity<CommandResult> processInbox(@ModelAttribute User visitor, InputStream inboxData) throws Exception {
String inbox = IOUtils.toString(inboxData, StandardCharsets.UTF_8);
Activity activity = jsonMapper.readValue(inbox, Activity.class);
if ((StringUtils.isNotEmpty(visitor.getUri().toString())
diff --git a/src/main/java/com/juick/www/controllers/Help.java b/src/main/java/com/juick/www/controllers/Help.java
index ae1dafbe..ae7ba9d1 100644
--- a/src/main/java/com/juick/www/controllers/Help.java
+++ b/src/main/java/com/juick/www/controllers/Help.java
@@ -20,13 +20,13 @@ package com.juick.www.controllers;
import com.juick.model.User;
import com.juick.util.HttpNotFoundException;
import com.juick.service.HelpService;
-import com.juick.service.security.annotation.Visitor;
import com.juick.www.WebApp;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import javax.inject.Inject;
@@ -49,7 +49,7 @@ public class Help {
@GetMapping({"/help/", "/help", "/help/{langOrPage}", "/help/{lang}/{page}"})
public String showHelp(
- @Visitor User visitor,
+ @ModelAttribute User visitor,
Locale locale,
@PathVariable(required = false, name = "lang") String lang,
@PathVariable(required = false, name = "page") String page,
diff --git a/src/main/java/com/juick/www/controllers/Settings.java b/src/main/java/com/juick/www/controllers/Settings.java
index b990bf41..4d7deece 100644
--- a/src/main/java/com/juick/www/controllers/Settings.java
+++ b/src/main/java/com/juick/www/controllers/Settings.java
@@ -35,7 +35,6 @@ import com.juick.service.TagService;
import com.juick.service.TelegramService;
import com.juick.service.UserService;
import com.juick.service.activities.UpdateUserEvent;
-import com.juick.service.security.annotation.Visitor;
import com.juick.util.HttpBadRequestException;
import com.juick.util.HttpUtils;
import com.juick.www.WebApp;
@@ -58,6 +57,7 @@ import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
@@ -89,7 +89,7 @@ public class Settings {
@GetMapping("/settings")
protected String doGet(
- @Visitor User visitor,
+ @ModelAttribute User visitor,
Locale locale,
@RequestParam(required = false, defaultValue = "main") String page,
@RequestParam(required = false) String code, ModelMap model) throws IOException {
@@ -126,7 +126,7 @@ public class Settings {
@PostMapping("/settings")
protected String doPost(
- @Visitor User visitor,
+ @ModelAttribute User visitor,
HttpServletRequest request, HttpServletResponse response,
@RequestParam(required = false) MultipartFile avatar,
ModelMap model)
@@ -272,7 +272,7 @@ public class Settings {
}
@PostMapping("/settings/unsubscribe")
public String unsubscribeOneClick(
- @Visitor User user,
+ @ModelAttribute User user,
@RequestParam(name = "List-Unsubscribe") String unsubscribe,
ModelMap model) {
if (!user.isAnonymous()) {
diff --git a/src/main/java/com/juick/www/controllers/SignUp.java b/src/main/java/com/juick/www/controllers/SignUp.java
index 9fc04dd5..8318dabd 100644
--- a/src/main/java/com/juick/www/controllers/SignUp.java
+++ b/src/main/java/com/juick/www/controllers/SignUp.java
@@ -23,13 +23,13 @@ import com.juick.util.UsernameTakenException;
import com.juick.www.WebApp;
import com.juick.service.EmailService;
import com.juick.service.UserService;
-import com.juick.service.security.annotation.Visitor;
import com.juick.service.security.entities.JuickUser;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@@ -52,7 +52,7 @@ public class SignUp {
@GetMapping("/signup")
protected String doGet(
- @Visitor User visitor,
+ @ModelAttribute User visitor,
@RequestParam String type, @RequestParam String hash, ModelMap model) {
if (hash.length() > 36 || !type.matches("^[a-zA-Z0-9\\-]+$")
|| !hash.matches("^[a-zA-Z0-9\\-]+$")) {
@@ -91,7 +91,7 @@ public class SignUp {
@PostMapping("/signup")
protected String doPost(
- @Visitor User visitor,
+ @ModelAttribute User visitor,
@RequestParam String type,
@RequestParam String hash,
@RequestParam String action,
diff --git a/src/main/java/com/juick/www/controllers/Site.java b/src/main/java/com/juick/www/controllers/Site.java
index f45fe8f2..e8acc650 100644
--- a/src/main/java/com/juick/www/controllers/Site.java
+++ b/src/main/java/com/juick/www/controllers/Site.java
@@ -30,7 +30,6 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import com.juick.service.*;
-import com.juick.service.security.annotation.Visitor;
import com.juick.util.MessageUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
@@ -43,12 +42,7 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.WebAttributes;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
-import org.springframework.web.bind.annotation.CookieValue;
-import org.springframework.web.bind.annotation.ExceptionHandler;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestHeader;
-import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.*;
import javax.inject.Inject;
import java.net.URLEncoder;
@@ -99,7 +93,7 @@ public class Site {
}
@GetMapping("/login")
- public String getloginForm(@Visitor User visitor,
+ public String getloginForm(@ModelAttribute User visitor,
@RequestParam(name = "retpath", required = false, defaultValue = "/") String retPath,
HttpSession session,
ModelMap model) {
@@ -125,7 +119,7 @@ public class Site {
}
@GetMapping("/")
- protected String doGet(@Visitor User visitor, Locale locale, @RequestParam(required = false) String tag,
+ protected String doGet(@ModelAttribute User visitor, Locale locale, @RequestParam(required = false) String tag,
@RequestParam(name = "show", required = false) String paramShow,
@RequestParam(name = "search", required = false) String paramSearch,
@RequestParam(name = "before", required = false, defaultValue = "0") Integer paramBefore,
@@ -217,7 +211,7 @@ public class Site {
}
@GetMapping(path = "/{uname}/", headers = "Connection!=Upgrade")
- protected String doGetBlog(@Visitor User visitor, @RequestParam(required = false, name = "show") String paramShow,
+ protected String doGetBlog(@ModelAttribute User visitor, @RequestParam(required = false, name = "show") String paramShow,
@RequestParam(required = false, name = "tag") String paramTagStr,
@RequestParam(required = false, name = "search") String paramSearch,
@RequestParam(required = false, name = "page", defaultValue = "0") Integer page, @PathVariable String uname,
@@ -324,7 +318,7 @@ public class Site {
}
@GetMapping("/{uname}/tags")
- protected String doGetTags(@Visitor User visitor, @PathVariable String uname, ModelMap model) {
+ protected String doGetTags(@ModelAttribute User visitor, @PathVariable String uname, ModelMap model) {
User user = userService.getUserByName(uname);
if (visitor.isBanned()) {
throw new HttpNotFoundException();
@@ -344,7 +338,7 @@ public class Site {
}
@GetMapping("/{uname}/friends")
- protected String doGetFriends(@Visitor User visitor, @PathVariable String uname, ModelMap model) {
+ protected String doGetFriends(@ModelAttribute User visitor, @PathVariable String uname, ModelMap model) {
User user = userService.getUserByName(uname);
if (visitor.isBanned()) {
throw new HttpNotFoundException();
@@ -360,7 +354,7 @@ public class Site {
}
@GetMapping("/{uname}/readers")
- protected String doGetReaders(@Visitor User visitor, @PathVariable String uname, ModelMap model) {
+ protected String doGetReaders(@ModelAttribute User visitor, @PathVariable String uname, ModelMap model) {
User user = userService.getUserByName(uname);
visitor.setAvatar(webApp.getAvatarWebPath(visitor));
model.addAttribute("title", "Читатели " + user.getName());
@@ -373,7 +367,7 @@ public class Site {
}
@GetMapping("/{uname}/bl")
- protected String doGetBL(@Visitor User visitor, @PathVariable String uname, ModelMap model) {
+ protected String doGetBL(@ModelAttribute User visitor, @PathVariable String uname, ModelMap model) {
User user = userService.getUserByName(uname);
if (visitor.getUid() != user.getUid()) {
throw new HttpForbiddenException();
@@ -389,7 +383,7 @@ public class Site {
}
@GetMapping("/tag/{tagName}")
- protected String tagAction(@Visitor User visitor, HttpServletRequest request, @PathVariable String tagName,
+ protected String tagAction(@ModelAttribute User visitor, HttpServletRequest request, @PathVariable String tagName,
@RequestParam(required = false, defaultValue = "0") int before, ModelMap model) {
visitor.setAvatar(webApp.getAvatarWebPath(visitor));
String paramTagStr = StringEscapeUtils.unescapeHtml4(tagName);
@@ -452,7 +446,7 @@ public class Site {
}
@GetMapping("/pm/inbox")
- protected String doGetInbox(@Visitor User visitor, ModelMap model) {
+ protected String doGetInbox(@ModelAttribute User visitor, ModelMap model) {
visitor.setAvatar(webApp.getAvatarWebPath(visitor));
String title = "PM: Inbox";
List<Message> msgs = chatService.getInbox(visitor.getUid());
@@ -466,7 +460,7 @@ public class Site {
}
@GetMapping("/pm/sent")
- protected String doGetSent(@Visitor User visitor, @RequestParam(required = false) String uname, ModelMap model) {
+ protected String doGetSent(@ModelAttribute User visitor, @RequestParam(required = false) String uname, ModelMap model) {
visitor.setAvatar(webApp.getAvatarWebPath(visitor));
String title = "PM: Sent";
List<Message> msgs = chatService.getOutbox(visitor.getUid());
@@ -485,7 +479,7 @@ public class Site {
@GetMapping(value = "/{uname}/{mid}", produces = { MediaType.TEXT_HTML_VALUE, Context.ACTIVITY_MEDIA_TYPE,
Context.LD_JSON_MEDIA_TYPE })
- protected String threadAction(@Visitor User visitor, ModelMap model, @PathVariable String uname,
+ protected String threadAction(@ModelAttribute User visitor, ModelMap model, @PathVariable String uname,
@PathVariable int mid,
@RequestHeader(name = HttpHeaders.ACCEPT, required = false) String acceptHeader,
@CookieValue(name = "sape_cookie", required = false, defaultValue = StringUtils.EMPTY) String sapeCookie) {
@@ -589,7 +583,7 @@ public class Site {
}
@GetMapping("/post")
- protected String postAction(@Visitor User visitor, @RequestParam(required = false) String body, ModelMap model) {
+ protected String postAction(@ModelAttribute User visitor, @RequestParam(required = false) String body, ModelMap model) {
fillUserModel(model, visitor, visitor);
visitor.setAvatar(webApp.getAvatarWebPath(visitor));
model.addAttribute("title", "Написать");
diff --git a/src/main/java/com/juick/www/controllers/SocialLogin.java b/src/main/java/com/juick/www/controllers/SocialLogin.java
index c9611543..24bf97f6 100644
--- a/src/main/java/com/juick/www/controllers/SocialLogin.java
+++ b/src/main/java/com/juick/www/controllers/SocialLogin.java
@@ -27,7 +27,6 @@ import com.juick.model.ext.vk.UsersResponse;
import com.juick.service.EmailService;
import com.juick.service.TelegramService;
import com.juick.service.UserService;
-import com.juick.service.security.annotation.Visitor;
import com.juick.util.HttpBadRequestException;
import jakarta.annotation.PostConstruct;
@@ -46,11 +45,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.CookieValue;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestHeader;
-import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.*;
import org.springframework.web.util.UriComponentsBuilder;
import javax.inject.Inject;
@@ -191,8 +186,8 @@ public class SocialLogin {
}
@GetMapping("/_twitter")
- protected void doTwitterLogin(@Visitor com.juick.model.User user, HttpServletRequest request,
- HttpServletResponse response) throws IOException, ExecutionException, InterruptedException {
+ protected void doTwitterLogin(@ModelAttribute com.juick.model.User user, HttpServletRequest request,
+ HttpServletResponse response) throws IOException, ExecutionException, InterruptedException {
String hash = StringUtils.EMPTY, request_token = StringUtils.EMPTY, request_token_secret = StringUtils.EMPTY;
String verifier = request.getParameter("oauth_verifier");
Cookie[] cookies = request.getCookies();
diff --git a/src/main/java/com/juick/www/rss/Feeds.java b/src/main/java/com/juick/www/rss/Feeds.java
index 8111f2df..41b006a6 100644
--- a/src/main/java/com/juick/www/rss/Feeds.java
+++ b/src/main/java/com/juick/www/rss/Feeds.java
@@ -21,16 +21,12 @@ import com.juick.model.User;
import com.juick.util.HttpNotFoundException;
import com.juick.service.MessagesService;
import com.juick.service.UserService;
-import com.juick.service.security.annotation.Visitor;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.ExceptionHandler;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import javax.inject.Inject;
@@ -53,7 +49,7 @@ public class Feeds {
}
@GetMapping("/rss/{userName}/{feedType}")
- public ModelAndView getBlog(@Visitor User visitor, @PathVariable String userName, @PathVariable FeedType feedType) {
+ public ModelAndView getBlog(@ModelAttribute User visitor, @PathVariable String userName, @PathVariable FeedType feedType) {
User user = userService.getUserByName(userName);
if (!user.isAnonymous() && !user.isBanned()) {
List<Integer> mids = feedType == FeedType.blog ? messagesService.getUserBlog(user.getUid(), 0, 0) : messagesService.getUserBlogWithRecommendations(user, visitor, 0, 0);
@@ -69,7 +65,7 @@ public class Feeds {
@GetMapping("/rss/")
public ModelAndView getLast(
- @Visitor User visitor,
+ @ModelAttribute User visitor,
@RequestParam(value = "hours", required = false, defaultValue = "0") Integer hours) {
List<Integer> mids = messagesService.getLastMessages(hours);
ModelAndView modelAndView = new ModelAndView();
diff --git a/src/main/java/com/juick/www/rss/MessagesView.java b/src/main/java/com/juick/www/rss/MessagesView.java
index 71d63754..d6edeb28 100644
--- a/src/main/java/com/juick/www/rss/MessagesView.java
+++ b/src/main/java/com/juick/www/rss/MessagesView.java
@@ -93,37 +93,39 @@ public class MessagesView extends AbstractRssFeedView {
String feedType = (String) model.get("feedType");
if (userObj != null) {
User user = (User) userObj;
- feed.setDescription(String.format("The latest messages by @%s at Juick", user.getName()));
- String title = String.format("%s - Juick", user.getName());
- feed.setTitle(title);
- String link = String.format("https://juick.com/%s/", user.getName());
- feed.setLink(link);
- try {
- Attachment avatar = storageService.getAvatarMetadata(user);
- Image rssImage = new Image();
- rssImage.setUrl(webApp.getAvatarUrl(user));
- rssImage.setTitle(title);
- rssImage.setLink(link);
- rssImage.setHeight(avatar.getHeight());
- rssImage.setWidth(avatar.getWidth());
- feed.setImage(rssImage);
- } catch (IOException e) {
- logger.warn("Feed avatar not found for {}", user.getName());
+ if (!user.isAnonymous()) {
+ feed.setDescription(String.format("The latest messages by @%s at Juick", user.getName()));
+ String title = String.format("%s - Juick", user.getName());
+ feed.setTitle(title);
+ String link = String.format("https://juick.com/%s/", user.getName());
+ feed.setLink(link);
+ try {
+ Attachment avatar = storageService.getAvatarMetadata(user);
+ Image rssImage = new Image();
+ rssImage.setUrl(webApp.getAvatarUrl(user));
+ rssImage.setTitle(title);
+ rssImage.setLink(link);
+ rssImage.setHeight(avatar.getHeight());
+ rssImage.setWidth(avatar.getWidth());
+ feed.setImage(rssImage);
+ } catch (IOException e) {
+ logger.warn("Feed avatar not found for {}", user.getName());
+ }
+
+ String href = String.format("https://rss.juick.com/%s/%s", user.getName(), feedType);
+ AtomLinkModule atomLinkModule = new AtomLinkModuleImpl();
+ Link atomLink = new Link();
+ atomLink.setHref(href);
+ atomLink.setType("application/rss+xml");
+ atomLink.setRel("self");
+ atomLinkModule.setLinks(Collections.singletonList(atomLink));
+
+ feed.getModules().add(atomLinkModule);
+ } else {
+ feed.setDescription("The latest messages at Juick");
+ feed.setLink("https://juick.com/");
+ feed.setTitle("Juick");
}
-
- String href = String.format("https://rss.juick.com/%s/%s", user.getName(), feedType);
- AtomLinkModule atomLinkModule = new AtomLinkModuleImpl();
- Link atomLink = new Link();
- atomLink.setHref(href);
- atomLink.setType("application/rss+xml");
- atomLink.setRel("self");
- atomLinkModule.setLinks(Collections.singletonList(atomLink));
-
- feed.getModules().add(atomLinkModule);
- } else {
- feed.setDescription("The latest messages at Juick");
- feed.setLink("https://juick.com/");
- feed.setTitle("Juick");
}
MediaModule mediaModule = new MediaModuleImpl();
diff --git a/src/main/resources/db/migration/V1.29__oauth_clients.sql b/src/main/resources/db/migration/V1.29__oauth_clients.sql
new file mode 100644
index 00000000..17bec0b3
--- /dev/null
+++ b/src/main/resources/db/migration/V1.29__oauth_clients.sql
@@ -0,0 +1,15 @@
+CREATE TABLE oauth2_registered_client (
+ id varchar(100) NOT NULL,
+ client_id varchar(100) NOT NULL,
+ client_id_issued_at timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ client_secret varchar(200) DEFAULT NULL,
+ client_secret_expires_at timestamp DEFAULT NULL,
+ client_name varchar(200) NOT NULL,
+ client_authentication_methods varchar(1000) NOT NULL,
+ authorization_grant_types varchar(1000) NOT NULL,
+ redirect_uris varchar(1000) DEFAULT NULL,
+ scopes varchar(1000) NOT NULL,
+ client_settings varchar(2000) NOT NULL,
+ token_settings varchar(2000) NOT NULL,
+ PRIMARY KEY (id)
+); \ No newline at end of file
diff --git a/src/test/java/com/juick/server/tests/ServerTests.java b/src/test/java/com/juick/server/tests/ServerTests.java
index 957da377..5eeeab3d 100644
--- a/src/test/java/com/juick/server/tests/ServerTests.java
+++ b/src/test/java/com/juick/server/tests/ServerTests.java
@@ -90,11 +90,13 @@ import org.springframework.core.io.Resource;
import org.springframework.http.*;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.mock.web.MockHttpSession;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
@@ -2718,6 +2720,19 @@ public class ServerTests {
}
@Test
+ public void givenAccessSecuredResource_whenAuthenticated_thenRedirectedBack() throws Exception {
+ MockHttpServletRequestBuilder securedResourceAccess = get("/settings");
+ MvcResult unauthenticatedResult = mockMvc.perform(securedResourceAccess).andExpect(status().is3xxRedirection())
+ .andReturn();
+ MockHttpSession session = (MockHttpSession) unauthenticatedResult.getRequest().getSession();
+ String loginUrl = unauthenticatedResult.getResponse().getRedirectedUrl();
+ mockMvc.perform(post(loginUrl).param("username", ugnichName).param("password", ugnichPassword)
+ .session(session).with(csrf())).andExpect(status().is3xxRedirection())
+ .andExpect(redirectedUrlPattern("**/settings?continue")).andReturn();
+ mockMvc.perform(securedResourceAccess.session(session)).andExpect(status().isOk());
+ }
+/*
+ @Test
public void tokenAuth() throws Exception {
var token = keystoreManager.generateToken(ugnich);
mockMvc.perform(get("/api/me")
@@ -2732,4 +2747,6 @@ public class ServerTests {
.header("Authorization", "Bearer " + token))
.andExpect(status().isUnauthorized());
}
+
+ */
}