From ae5870f1fa9bbf045f1881664bb8a3a098fd2610 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Tue, 11 Dec 2018 16:14:14 +0300 Subject: avatar upload api --- src/main/java/com/juick/server/api/Users.java | 26 +++++++++++++++------- .../com/juick/server/www/controllers/Settings.java | 2 -- 2 files changed, 18 insertions(+), 10 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/api/Users.java b/src/main/java/com/juick/server/api/Users.java index 6f9ab290..f8fdba0c 100644 --- a/src/main/java/com/juick/server/api/Users.java +++ b/src/main/java/com/juick/server/api/Users.java @@ -20,19 +20,17 @@ package com.juick.server.api; import com.juick.User; import com.juick.model.ApplicationStatus; import com.juick.model.UserInfo; -import com.juick.server.util.HttpForbiddenException; -import com.juick.server.util.HttpNotFoundException; +import com.juick.server.util.*; import com.juick.server.www.WebApp; -import com.juick.service.CrosspostService; -import com.juick.service.EmailService; -import com.juick.service.MessagesService; -import com.juick.service.UserService; -import com.juick.server.util.UserUtils; -import com.juick.server.util.WebUtils; +import com.juick.service.*; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import javax.inject.Inject; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -52,6 +50,10 @@ public class Users { private EmailService emailService; @Inject private WebApp webApp; + @Inject + private ImagesService imagesService; + @Value("${upload_tmp_dir:#{systemEnvironment['TEMP'] ?: '/tmp'}}") + private String tmpDir; @RequestMapping(value = "/api/auth", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public String getAuthToken() { @@ -96,6 +98,14 @@ public class Users { me.setAvatar(webApp.getAvatarUrl(visitor)); return me; } + @PostMapping("/api/me/upload") + public void updateInfo(@RequestParam MultipartFile avatar) throws IOException { + User visitor = UserUtils.getCurrentUser(); + String avatarTmpPath = HttpUtils.receiveMultiPartFile(avatar, tmpDir).getHost(); + if (StringUtils.isNotEmpty(avatarTmpPath)) { + imagesService.saveAvatar(avatarTmpPath, visitor.getUid()); + } + } @RequestMapping(value = "/api/users/read", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public List doGetUserRead( diff --git a/src/main/java/com/juick/server/www/controllers/Settings.java b/src/main/java/com/juick/server/www/controllers/Settings.java index 57984aef..fc84b410 100644 --- a/src/main/java/com/juick/server/www/controllers/Settings.java +++ b/src/main/java/com/juick/server/www/controllers/Settings.java @@ -60,8 +60,6 @@ import java.util.stream.IntStream; public class Settings { private static final Logger logger = LoggerFactory.getLogger(Settings.class); - @Value("${img_path:#{systemEnvironment['TEMP'] ?: '/tmp'}}") - private String imgDir; @Value("${upload_tmp_dir:#{systemEnvironment['TEMP'] ?: '/tmp'}}") private String tmpDir; @Inject -- cgit v1.2.3 From 1dc570a8aaaa8657eb4faa7283e30a5fde93d321 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Wed, 12 Dec 2018 09:19:53 +0300 Subject: Vary: Accept-Language --- src/main/java/com/juick/server/www/VaryHandler.java | 14 ++++++++++++++ src/test/java/com/juick/server/tests/ServerTests.java | 12 ++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 src/main/java/com/juick/server/www/VaryHandler.java (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/www/VaryHandler.java b/src/main/java/com/juick/server/www/VaryHandler.java new file mode 100644 index 00000000..5a1b86a6 --- /dev/null +++ b/src/main/java/com/juick/server/www/VaryHandler.java @@ -0,0 +1,14 @@ +package com.juick.server.www; + +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ModelAttribute; + +import javax.servlet.http.HttpServletResponse; + +@ControllerAdvice +public class VaryHandler { + @ModelAttribute + public void setVaryResponseHeader(HttpServletResponse response) { + response.setHeader("Vary", "Accept-Language"); + } +} diff --git a/src/test/java/com/juick/server/tests/ServerTests.java b/src/test/java/com/juick/server/tests/ServerTests.java index b732cb98..b914aed0 100644 --- a/src/test/java/com/juick/server/tests/ServerTests.java +++ b/src/test/java/com/juick/server/tests/ServerTests.java @@ -1877,4 +1877,16 @@ public class ServerTests { String newHash = DigestUtils.md5DigestAsHex(newAvatarData); assertThat(webApp.getAvatarUrl(userService.getUserByName(freefdName)), is(String.format("http://localhost:8080/i/a/%d-%s.png", freefd.getUid(), newHash))); } + @Test + public void varyMvcResponse() throws Exception { + mockMvc.perform(get("/")) + .andExpect(status().isOk()) + .andExpect(header().string("Vary", "Accept-Language")); + mockMvc.perform(get("/rss/ugnich/blog")) + .andExpect(status().isOk()) + .andExpect(header().string("Vary", "Accept-Language")); + mockMvc.perform(get("/api/messages")) + .andExpect(status().isOk()) + .andExpect(header().string("Vary", "Accept-Language")); + } } -- cgit v1.2.3 From 5cf17016b213232dede31eac45c9d311e461672b Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Wed, 12 Dec 2018 14:07:44 +0300 Subject: api: read and readers in /info endpoint --- java-telegram-bot-api | 1 - src/main/java/com/juick/server/api/Users.java | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) delete mode 160000 java-telegram-bot-api (limited to 'src/main/java/com/juick/server') diff --git a/java-telegram-bot-api b/java-telegram-bot-api deleted file mode 160000 index 672eabf1..00000000 --- a/java-telegram-bot-api +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 672eabf1d52ce73ac9242b545c9893e64ba82df2 diff --git a/src/main/java/com/juick/server/api/Users.java b/src/main/java/com/juick/server/api/Users.java index f8fdba0c..0e0fee85 100644 --- a/src/main/java/com/juick/server/api/Users.java +++ b/src/main/java/com/juick/server/api/Users.java @@ -165,6 +165,8 @@ public class Users { public UserInfo getUserInfo(@PathVariable String uname) { User user = userService.getUserByName(uname); if (!user.isBanned()) { + user.setRead(doGetUserRead(uname)); + user.setReaders(doGetUserReaders(uname)); user.setAvatar(webApp.getAvatarUrl(user)); return userService.getUserInfo(user); } -- cgit v1.2.3 From 9d493ac966db2e016f73efcdcfb65dfb10bf8114 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Wed, 12 Dec 2018 15:12:27 +0300 Subject: UserInfo -> User --- src/main/java/com/juick/User.java | 27 ++++++++++ src/main/java/com/juick/model/UserInfo.java | 60 ---------------------- src/main/java/com/juick/server/XMPPConnection.java | 4 +- src/main/java/com/juick/server/api/Users.java | 14 +---- .../juick/server/configuration/SecurityConfig.java | 2 +- .../server/www/controllers/AnythingFilter.java | 2 +- .../com/juick/server/www/controllers/Settings.java | 12 ++--- src/main/java/com/juick/service/UserService.java | 5 +- .../java/com/juick/service/UserServiceImpl.java | 37 ++++++------- .../java/com/juick/server/tests/ServerTests.java | 20 ++++++-- 10 files changed, 74 insertions(+), 109 deletions(-) delete mode 100644 src/main/java/com/juick/model/UserInfo.java (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/User.java b/src/main/java/com/juick/User.java index 7221e416..78d47139 100644 --- a/src/main/java/com/juick/User.java +++ b/src/main/java/com/juick/User.java @@ -53,6 +53,9 @@ public class User { private URI uri; private Instant seen; private boolean verified; + private String country; + private String url; + private String description; public User() { tokens = new ArrayList<>(); @@ -232,4 +235,28 @@ public class User { public void setVerified(boolean verified) { this.verified = verified; } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } } diff --git a/src/main/java/com/juick/model/UserInfo.java b/src/main/java/com/juick/model/UserInfo.java deleted file mode 100644 index ca5d75e0..00000000 --- a/src/main/java/com/juick/model/UserInfo.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2008-2017, Juick - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package com.juick.model; - -/** - * Created by vt on 03/09/16. - */ -public class UserInfo { - private String fullName; - private String country; - private String url; - private String description; - - public String getFullName() { - return fullName; - } - - public void setFullName(String fullName) { - this.fullName = fullName; - } - - public String getCountry() { - return country; - } - - public void setCountry(String country) { - this.country = country; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } -} diff --git a/src/main/java/com/juick/server/XMPPConnection.java b/src/main/java/com/juick/server/XMPPConnection.java index f77b2354..dfbe92d5 100644 --- a/src/main/java/com/juick/server/XMPPConnection.java +++ b/src/main/java/com/juick/server/XMPPConnection.java @@ -22,7 +22,6 @@ import com.juick.formatters.PlainTextFormatter; import com.juick.server.www.WebApp; import com.juick.service.component.*; import com.juick.model.CommandResult; -import com.juick.model.UserInfo; import com.juick.server.xmpp.iq.MessageQuery; import com.juick.server.xmpp.s2s.BasicXmppSession; import com.juick.server.xmpp.s2s.StanzaListener; @@ -35,7 +34,6 @@ import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.ApplicationEventPublisher; import rocks.xmpp.addr.Jid; import rocks.xmpp.core.XmppException; import rocks.xmpp.core.session.XmppSession; @@ -164,7 +162,7 @@ public class XMPPConnection implements StanzaListener, NotificationListener { } User user = userService.getUserByName(iq.getTo().getLocal()); if (!user.isAnonymous()) { - UserInfo info = userService.getUserInfo(user); + User info = userService.getUserInfo(user); VCard userVCard = new VCard(); userVCard.setFormattedName(info.getFullName()); userVCard.setNickname(user.getName()); diff --git a/src/main/java/com/juick/server/api/Users.java b/src/main/java/com/juick/server/api/Users.java index 0e0fee85..5c6efe0c 100644 --- a/src/main/java/com/juick/server/api/Users.java +++ b/src/main/java/com/juick/server/api/Users.java @@ -19,7 +19,6 @@ package com.juick.server.api; import com.juick.User; import com.juick.model.ApplicationStatus; -import com.juick.model.UserInfo; import com.juick.server.util.*; import com.juick.server.www.WebApp; import com.juick.service.*; @@ -96,7 +95,7 @@ public class Users { me.setRead(userService.getUserFriends(visitor.getUid())); me.setReaders(userService.getUserReaders(visitor.getUid())); me.setAvatar(webApp.getAvatarUrl(visitor)); - return me; + return (SecureUser)userService.getUserInfo(me); } @PostMapping("/api/me/upload") public void updateInfo(@RequestParam MultipartFile avatar) throws IOException { @@ -111,9 +110,6 @@ public class Users { public List doGetUserRead( @RequestParam String uname) { User visitor = UserUtils.getCurrentUser(); - if (visitor.isAnonymous()) { - throw new HttpForbiddenException(); - } int uid = 0; if (uname == null) { uid = visitor.getUid(); @@ -138,9 +134,6 @@ public class Users { public List doGetUserReaders( @RequestParam String uname) { User visitor = UserUtils.getCurrentUser(); - if (visitor.isAnonymous()) { - throw new HttpForbiddenException(); - } int uid = 0; if (uname == null) { uid = visitor.getUid(); @@ -162,7 +155,7 @@ public class Users { } @GetMapping("/api/info/{uname}") - public UserInfo getUserInfo(@PathVariable String uname) { + public User getUserInfo(@PathVariable String uname) { User user = userService.getUserByName(uname); if (!user.isBanned()) { user.setRead(doGetUserRead(uname)); @@ -177,9 +170,6 @@ public class Users { public String getHash() { return getAuthHash(); } - public UserInfo getUserInfo() { - return userService.getUserInfo(this); - } public List getJIDs() { return userService.getAllJIDs(this); } diff --git a/src/main/java/com/juick/server/configuration/SecurityConfig.java b/src/main/java/com/juick/server/configuration/SecurityConfig.java index f53cc531..16b61172 100644 --- a/src/main/java/com/juick/server/configuration/SecurityConfig.java +++ b/src/main/java/com/juick/server/configuration/SecurityConfig.java @@ -98,7 +98,7 @@ public class SecurityConfig { .authorizeRequests() .antMatchers(HttpMethod.OPTIONS).permitAll() .antMatchers("/api/", "/api/messages", "/api/messages/discussions", "/api/users", "/api/thread", "/api/tags", "/api/tlgmbtwbhk", "/api/fbwbhk", - "/api/skypebotendpoint", "/api/_fblogin", "/api/_vklogin", "/api/_tglogin", "/api/_google", "/api/signup", "/api/inbox", "/api/u/**", "/.well-known/webfinger", "/.well-known/x-nodeinfo2", "/rss/**", "/api/events").permitAll() + "/api/skypebotendpoint", "/api/_fblogin", "/api/_vklogin", "/api/_tglogin", "/api/_google", "/api/signup", "/api/inbox", "/api/u/**", "/.well-known/webfinger", "/.well-known/x-nodeinfo2", "/rss/**", "/api/events", "/api/info/**").permitAll() .anyRequest().hasRole("USER") .and() .anonymous().principal(JuickUser.ANONYMOUS_USER).authorities(JuickUser.ANONYMOUS_AUTHORITY) diff --git a/src/main/java/com/juick/server/www/controllers/AnythingFilter.java b/src/main/java/com/juick/server/www/controllers/AnythingFilter.java index cdbeafc0..57b298eb 100644 --- a/src/main/java/com/juick/server/www/controllers/AnythingFilter.java +++ b/src/main/java/com/juick/server/www/controllers/AnythingFilter.java @@ -60,7 +60,7 @@ public class AnythingFilter extends OncePerRequestFilter { } else { com.juick.User user = userService.getUserByName(anything); if (!user.isAnonymous()) { - ((HttpServletResponse) servletResponse).sendRedirect("/" + user.getName() + "/?before=" + before); + servletResponse.sendRedirect("/" + user.getName() + "/?before=" + before); } else { filterChain.doFilter(servletRequest, servletResponse); } diff --git a/src/main/java/com/juick/server/www/controllers/Settings.java b/src/main/java/com/juick/server/www/controllers/Settings.java index fc84b410..d5a21d09 100644 --- a/src/main/java/com/juick/server/www/controllers/Settings.java +++ b/src/main/java/com/juick/server/www/controllers/Settings.java @@ -18,7 +18,6 @@ package com.juick.server.www.controllers; import com.juick.User; import com.juick.model.NotifyOpts; -import com.juick.model.UserInfo; import com.juick.server.util.HttpBadRequestException; import com.juick.server.util.HttpUtils; import com.juick.server.util.UserUtils; @@ -156,16 +155,15 @@ public class Settings { } break; case "about": - UserInfo info = new UserInfo(); - info.setFullName(request.getParameter("fullname")); - info.setCountry(request.getParameter("country")); - info.setUrl(request.getParameter("url")); - info.setDescription(request.getParameter("descr")); + visitor.setFullName(request.getParameter("fullname")); + visitor.setCountry(request.getParameter("country")); + visitor.setUrl(request.getParameter("url")); + visitor.setDescription(request.getParameter("descr")); String avatarTmpPath = HttpUtils.receiveMultiPartFile(avatar, tmpDir).getHost(); if (StringUtils.isNotEmpty(avatarTmpPath)) { imagesService.saveAvatar(avatarTmpPath, visitor.getUid()); } - if (userService.updateUserInfo(visitor, info)) { + if (userService.updateUserInfo(visitor)) { result = String.format("

Your info is updated.

Back to blog.

", visitor.getName()); } break; diff --git a/src/main/java/com/juick/service/UserService.java b/src/main/java/com/juick/service/UserService.java index 832f978a..3a51dffb 100644 --- a/src/main/java/com/juick/service/UserService.java +++ b/src/main/java/com/juick/service/UserService.java @@ -20,7 +20,6 @@ package com.juick.service; import com.juick.Message; import com.juick.User; import com.juick.model.Auth; -import com.juick.model.UserInfo; import javax.annotation.Nonnull; import java.time.Instant; @@ -75,9 +74,9 @@ public interface UserService { int setUserOptionInt(int uid, String option, int value); - UserInfo getUserInfo(User user); + User getUserInfo(User user); - boolean updateUserInfo(User user, UserInfo info); + boolean updateUserInfo(User info); boolean getCanMedia(int uid); diff --git a/src/main/java/com/juick/service/UserServiceImpl.java b/src/main/java/com/juick/service/UserServiceImpl.java index 93904139..95a13f65 100644 --- a/src/main/java/com/juick/service/UserServiceImpl.java +++ b/src/main/java/com/juick/service/UserServiceImpl.java @@ -21,7 +21,6 @@ import com.juick.Message; import com.juick.User; import com.juick.model.AnonymousUser; import com.juick.model.Auth; -import com.juick.model.UserInfo; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; @@ -375,29 +374,31 @@ public class UserServiceImpl extends BaseJdbcService implements UserService { @Transactional(readOnly = true) @Override - public UserInfo getUserInfo(final User user) { - List list = getJdbcTemplate().query( - "SELECT fullname, country, url, descr FROM usersinfo WHERE user_id = ?", - ((rs, rowNum) -> { - UserInfo info = new UserInfo(); - info.setFullName(rs.getString(1)); - info.setCountry(rs.getString(2)); - info.setUrl(rs.getString(3)); - info.setDescription(rs.getString(4)); - return info; - }), - user.getUid()); - - return list.isEmpty() ? new UserInfo() : list.get(0); + public User getUserInfo(final User user) { + try { + getJdbcTemplate().queryForObject( + "SELECT fullname, country, url, descr FROM usersinfo WHERE user_id = ?", + ((rs, rowNum) -> { + user.setFullName(rs.getString(1)); + user.setCountry(rs.getString(2)); + user.setUrl(rs.getString(3)); + user.setDescription(rs.getString(4)); + return user; + }), + user.getUid()); + } catch (EmptyResultDataAccessException e) { + return user; + } + return user; } @Transactional @Override - public boolean updateUserInfo(final User user, final UserInfo info) { + public boolean updateUserInfo(final User info) { try { return getJdbcTemplate().update( "INSERT INTO usersinfo(user_id, fullname, country, url, descr) VALUES (?, ?, ?, ?, ?)", - user.getUid(), + info.getUid(), info.getFullName(), info.getCountry(), info.getUrl(), @@ -408,7 +409,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService { info.getCountry(), info.getUrl(), info.getDescription(), - user.getUid()) > 0; + info.getUid()) > 0; } } diff --git a/src/test/java/com/juick/server/tests/ServerTests.java b/src/test/java/com/juick/server/tests/ServerTests.java index 01aa298b..d1cdac8b 100644 --- a/src/test/java/com/juick/server/tests/ServerTests.java +++ b/src/test/java/com/juick/server/tests/ServerTests.java @@ -27,10 +27,7 @@ import com.gargoylesoftware.htmlunit.html.DomElement; import com.gargoylesoftware.htmlunit.html.HtmlPage; import com.jayway.jsonpath.JsonPath; import com.juick.*; -import com.juick.model.AnonymousUser; -import com.juick.model.CommandResult; -import com.juick.model.PrivateChats; -import com.juick.model.TagStats; +import com.juick.model.*; import com.juick.server.*; import com.juick.server.api.activity.model.Context; import com.juick.server.api.activity.model.activities.Create; @@ -1896,4 +1893,19 @@ public class ServerTests { .andExpect(status().isOk()) .andExpect(header().string("Vary", "Accept-Language")); } + @Test + public void apiInfo() throws Exception { + userService.createUser("tst", "tst"); + MvcResult result = mockMvc.perform(get("/api/info/tst")) + .andExpect(status().isOk()) + .andReturn(); + User tst = jsonMapper.readValue(result.getResponse().getContentAsString(), User.class); + assertThat(tst.getReaders(), is(nullValue())); + commandsManager.processCommand(ugnich, "S @tst", emptyUri); + result = mockMvc.perform(get("/api/info/tst")) + .andExpect(status().isOk()) + .andReturn(); + tst = jsonMapper.readValue(result.getResponse().getContentAsString(), User.class); + assertThat(tst.getReaders().size(), is(1)); + } } -- cgit v1.2.3 From 8268ccc461608d45bdf60e58ccf49256e8cc993c Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Mon, 17 Dec 2018 11:58:50 +0300 Subject: CORS for ActivityPub endpoints --- .../juick/server/configuration/SecurityConfig.java | 30 ++++++++++++---------- .../java/com/juick/server/tests/ServerTests.java | 5 ++++ 2 files changed, 21 insertions(+), 14 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/configuration/SecurityConfig.java b/src/main/java/com/juick/server/configuration/SecurityConfig.java index 16b61172..7145e9d5 100644 --- a/src/main/java/com/juick/server/configuration/SecurityConfig.java +++ b/src/main/java/com/juick/server/configuration/SecurityConfig.java @@ -69,6 +69,20 @@ public class SecurityConfig { public UserDetailsService userDetailsService() { return new JuickUserDetailsService(userService); } + @Bean + static CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + + configuration.setAllowedOrigins(Collections.singletonList("*")); + configuration.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "OPTIONS", "DELETE")); + configuration.setAllowedHeaders(Collections.singletonList("*")); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/api/**", configuration); + source.registerCorsConfiguration("/u/**", configuration); + source.registerCorsConfiguration("/n/**", configuration); + return source; + } @Configuration @Order(1) @@ -98,7 +112,7 @@ public class SecurityConfig { .authorizeRequests() .antMatchers(HttpMethod.OPTIONS).permitAll() .antMatchers("/api/", "/api/messages", "/api/messages/discussions", "/api/users", "/api/thread", "/api/tags", "/api/tlgmbtwbhk", "/api/fbwbhk", - "/api/skypebotendpoint", "/api/_fblogin", "/api/_vklogin", "/api/_tglogin", "/api/_google", "/api/signup", "/api/inbox", "/api/u/**", "/.well-known/webfinger", "/.well-known/x-nodeinfo2", "/rss/**", "/api/events", "/api/info/**").permitAll() + "/api/skypebotendpoint", "/api/_fblogin", "/api/_vklogin", "/api/_tglogin", "/api/_google", "/api/signup", "/api/inbox", "/api/events", "/api/info/**").permitAll() .anyRequest().hasRole("USER") .and() .anonymous().principal(JuickUser.ANONYMOUS_USER).authorities(JuickUser.ANONYMOUS_AUTHORITY) @@ -122,19 +136,6 @@ public class SecurityConfig { return new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED); } - @Bean - public CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration configuration = new CorsConfiguration(); - - configuration.setAllowedOrigins(Collections.singletonList("*")); - configuration.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "OPTIONS", "DELETE")); - configuration.setAllowedHeaders(Collections.singletonList("*")); - - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/api/**", configuration); - - return source; - } @Override public void configure(WebSecurity web) { web.debug(false); @@ -182,6 +183,7 @@ public class SecurityConfig { .anyRequest().permitAll() .and() .anonymous().principal(JuickUser.ANONYMOUS_USER).authorities(JuickUser.ANONYMOUS_AUTHORITY) + .and().cors().configurationSource(corsConfigurationSource()) .and().sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .invalidSessionUrl("/") diff --git a/src/test/java/com/juick/server/tests/ServerTests.java b/src/test/java/com/juick/server/tests/ServerTests.java index d1cdac8b..5902220f 100644 --- a/src/test/java/com/juick/server/tests/ServerTests.java +++ b/src/test/java/com/juick/server/tests/ServerTests.java @@ -488,6 +488,11 @@ public class ServerTests { .header("Origin", "http://api.example.net")) .andExpect(status().isOk()) .andExpect(header().string("Access-Control-Allow-Origin", "*")); + mockMvc.perform( + get("/u/ugnich") + .header("Origin", "http://api.example.net")) + .andExpect(status().isOk()) + .andExpect(header().string("Access-Control-Allow-Origin", "*")); } @Test -- cgit v1.2.3 From 710aa95363bbd893d44eca39d3f7d5a101cb04b7 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Thu, 20 Dec 2018 09:26:12 +0300 Subject: SignatureManager refactoring --- .../java/com/juick/server/SignatureManager.java | 26 ++++++++++++++-------- .../com/juick/server/api/activity/Profile.java | 5 +++-- .../security/HashParamAuthenticationFilter.java | 7 +++--- 3 files changed, 24 insertions(+), 14 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/SignatureManager.java b/src/main/java/com/juick/server/SignatureManager.java index b3b7a301..26e482ad 100644 --- a/src/main/java/com/juick/server/SignatureManager.java +++ b/src/main/java/com/juick/server/SignatureManager.java @@ -1,11 +1,14 @@ package com.juick.server; import com.fasterxml.jackson.databind.ObjectMapper; +import com.juick.User; import com.juick.server.api.activity.model.Context; import com.juick.server.api.activity.model.objects.Person; import com.juick.server.api.webfinger.model.Account; import com.juick.server.api.webfinger.model.Link; +import com.juick.service.UserService; import com.juick.util.DateFormattersHolder; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEventPublisher; @@ -41,7 +44,7 @@ public class SignatureManager { @Inject private ObjectMapper jsonMapper; @Inject - private ApplicationEventPublisher applicationEventPublisher; + private UserService userService; @Inject private RestTemplate apClient; @@ -70,23 +73,28 @@ public class SignatureManager { logger.info("accepted follower: {}", response.getStatusCodeValue()); } - public boolean verifySignature(String signatureString, URI actor, String method, String path, Map headers) { - Optional context = getContext(actor); + public User verifySignature(String method, String path, Map headers) throws IOException { + Signature signature = Signature.fromString(headers.get("signature")); + Optional context = getContext(URI.create(signature.getKeyId())); if (context.isPresent() && context.get() instanceof Person) { Person person = (Person) context.get(); Key key = KeystoreManager.publicKeyOf(person); - Verifier verifier = new Verifier(key, Signature.fromString(signatureString)); + + Verifier verifier = new Verifier(key, signature); try { boolean result = verifier.verify(method, path, headers); logger.info("signature is valid: {}", result); - return result; + User user = new User(); + user.setUri(URI.create(person.getId())); + if (key.equals(keystoreManager.getPublicKey())) { + return userService.getUserByName(person.getName()); + } + return user; } catch (NoSuchAlgorithmException | SignatureException | IOException e) { - logger.info("signature exception", e); - return false; + throw new IOException("Invalid signature"); } } - logger.info("person not found"); - return false; + throw new IOException("Person not found"); } public Optional getContext(URI contextUri) { Context context = apClient.getForEntity(contextUri, Context.class).getBody(); diff --git a/src/main/java/com/juick/server/api/activity/Profile.java b/src/main/java/com/juick/server/api/activity/Profile.java index 305b7c4a..2614cded 100644 --- a/src/main/java/com/juick/server/api/activity/Profile.java +++ b/src/main/java/com/juick/server/api/activity/Profile.java @@ -268,9 +268,10 @@ public class Profile { headers.put("content-type", contentType); headers.put("user-agent", userAgent); headers.put("accept-encoding", acceptEncoding); - boolean valid = signatureManager.verifySignature(signature, URI.create(activity.getActor()), "POST", + headers.put("signature", signature); + User signedUser = signatureManager.verifySignature( "POST", componentsBuilder.getPath(), headers); - if (valid) { + if ((StringUtils.isNotEmpty(signedUser.getUri().toString()) && signedUser.getUri().equals(URI.create(activity.getActor()))) || !signedUser.isAnonymous()) { if (activity instanceof Follow) { Follow followRequest = (Follow) activity; String actor = followRequest.getActor(); diff --git a/src/main/java/com/juick/service/security/HashParamAuthenticationFilter.java b/src/main/java/com/juick/service/security/HashParamAuthenticationFilter.java index 9215d09a..2fd5a2a7 100644 --- a/src/main/java/com/juick/service/security/HashParamAuthenticationFilter.java +++ b/src/main/java/com/juick/service/security/HashParamAuthenticationFilter.java @@ -30,6 +30,7 @@ import org.springframework.util.Assert; import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.util.WebUtils; +import javax.annotation.Nonnull; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.Cookie; @@ -59,9 +60,9 @@ public class HashParamAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal( - HttpServletRequest request, - HttpServletResponse response, - FilterChain filterChain) throws ServletException, IOException { + @Nonnull HttpServletRequest request, + @Nonnull HttpServletResponse response, + @Nonnull FilterChain filterChain) throws ServletException, IOException { String hash = getHashFromRequest(request); -- cgit v1.2.3 From a201bab42facd56290195703f6847e1a29033fb4 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Sun, 30 Dec 2018 23:05:16 +0300 Subject: Name XMPP threads --- src/main/java/com/juick/server/xmpp/s2s/ConnectionIn.java | 1 + src/main/java/com/juick/server/xmpp/s2s/ConnectionOut.java | 1 + 2 files changed, 2 insertions(+) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/xmpp/s2s/ConnectionIn.java b/src/main/java/com/juick/server/xmpp/s2s/ConnectionIn.java index 72c3ba8d..3929a69f 100644 --- a/src/main/java/com/juick/server/xmpp/s2s/ConnectionIn.java +++ b/src/main/java/com/juick/server/xmpp/s2s/ConnectionIn.java @@ -69,6 +69,7 @@ public class ConnectionIn extends Connection implements Runnable { } boolean xmppversionnew = parser.getAttributeValue(null, "version") != null; String from = parser.getAttributeValue(null, "from"); + Thread.currentThread().setName(String.format("XMPP S2S IN %s - %d", from, Thread.currentThread().getId())); if (Arrays.asList(xmpp.bannedHosts).contains(from)) { closeConnection(); diff --git a/src/main/java/com/juick/server/xmpp/s2s/ConnectionOut.java b/src/main/java/com/juick/server/xmpp/s2s/ConnectionOut.java index be485ab1..2b919da4 100644 --- a/src/main/java/com/juick/server/xmpp/s2s/ConnectionOut.java +++ b/src/main/java/com/juick/server/xmpp/s2s/ConnectionOut.java @@ -65,6 +65,7 @@ public class ConnectionOut extends Stream { this.dbKey = DialbackUtils.generateDialbackKey(generator.generate(15), to, from, streamID); } streamID = UUID.randomUUID().toString(); + Thread.currentThread().setName(String.format("XMPP S2S OUT %s - %d", to.toEscapedString(), Thread.currentThread().getId())); } public void sendOpenStream() throws IOException { -- cgit v1.2.3 From 3af1fa43a25ba844b999d0be6c1139e6a98046fb Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Mon, 31 Dec 2018 00:02:02 +0300 Subject: Use external XMPP server --- src/main/java/com/juick/server/XMPPConnection.java | 19 +- src/main/java/com/juick/server/XMPPServer.java | 429 --------------------- .../com/juick/server/configuration/XMPPConfig.java | 11 - .../java/com/juick/server/xmpp/XMPPStatusPage.java | 32 -- .../com/juick/server/xmpp/helpers/XMPPStatus.java | 48 --- .../com/juick/server/xmpp/router/Handshake.java | 39 -- .../java/com/juick/server/xmpp/router/Stream.java | 202 ---------- .../server/xmpp/router/StreamComponentServer.java | 57 --- .../com/juick/server/xmpp/router/StreamError.java | 57 --- .../juick/server/xmpp/router/StreamFeatures.java | 95 ----- .../juick/server/xmpp/router/StreamHandler.java | 13 - .../juick/server/xmpp/router/StreamNamespaces.java | 10 - .../com/juick/server/xmpp/router/XMPPError.java | 73 ---- .../com/juick/server/xmpp/router/XMPPRouter.java | 220 ----------- .../com/juick/server/xmpp/router/XmlUtils.java | 88 ----- .../java/com/juick/server/xmpp/s2s/CacheEntry.java | 40 -- .../java/com/juick/server/xmpp/s2s/Connection.java | 158 -------- .../com/juick/server/xmpp/s2s/ConnectionIn.java | 232 ----------- .../juick/server/xmpp/s2s/ConnectionListener.java | 16 - .../com/juick/server/xmpp/s2s/ConnectionOut.java | 190 --------- .../java/com/juick/server/xmpp/s2s/DNSQueries.java | 65 ---- .../juick/server/xmpp/s2s/util/DialbackUtils.java | 37 -- .../java/com/juick/server/tests/ServerTests.java | 81 ---- 23 files changed, 6 insertions(+), 2206 deletions(-) delete mode 100644 src/main/java/com/juick/server/XMPPServer.java delete mode 100644 src/main/java/com/juick/server/xmpp/XMPPStatusPage.java delete mode 100644 src/main/java/com/juick/server/xmpp/helpers/XMPPStatus.java delete mode 100644 src/main/java/com/juick/server/xmpp/router/Handshake.java delete mode 100644 src/main/java/com/juick/server/xmpp/router/Stream.java delete mode 100644 src/main/java/com/juick/server/xmpp/router/StreamComponentServer.java delete mode 100644 src/main/java/com/juick/server/xmpp/router/StreamError.java delete mode 100644 src/main/java/com/juick/server/xmpp/router/StreamFeatures.java delete mode 100644 src/main/java/com/juick/server/xmpp/router/StreamHandler.java delete mode 100644 src/main/java/com/juick/server/xmpp/router/StreamNamespaces.java delete mode 100644 src/main/java/com/juick/server/xmpp/router/XMPPError.java delete mode 100644 src/main/java/com/juick/server/xmpp/router/XMPPRouter.java delete mode 100644 src/main/java/com/juick/server/xmpp/router/XmlUtils.java delete mode 100644 src/main/java/com/juick/server/xmpp/s2s/CacheEntry.java delete mode 100644 src/main/java/com/juick/server/xmpp/s2s/Connection.java delete mode 100644 src/main/java/com/juick/server/xmpp/s2s/ConnectionIn.java delete mode 100644 src/main/java/com/juick/server/xmpp/s2s/ConnectionListener.java delete mode 100644 src/main/java/com/juick/server/xmpp/s2s/ConnectionOut.java delete mode 100644 src/main/java/com/juick/server/xmpp/s2s/DNSQueries.java delete mode 100644 src/main/java/com/juick/server/xmpp/s2s/util/DialbackUtils.java (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/XMPPConnection.java b/src/main/java/com/juick/server/XMPPConnection.java index dfbe92d5..c4816478 100644 --- a/src/main/java/com/juick/server/XMPPConnection.java +++ b/src/main/java/com/juick/server/XMPPConnection.java @@ -24,7 +24,6 @@ import com.juick.service.component.*; import com.juick.model.CommandResult; import com.juick.server.xmpp.iq.MessageQuery; import com.juick.server.xmpp.s2s.BasicXmppSession; -import com.juick.server.xmpp.s2s.StanzaListener; import com.juick.service.*; import com.juick.util.MessageUtils; import org.apache.commons.codec.digest.DigestUtils; @@ -81,14 +80,12 @@ import java.util.concurrent.ExecutorService; /** * @author ugnich */ -public class XMPPConnection implements StanzaListener, NotificationListener { +public class XMPPConnection implements NotificationListener { private static final Logger logger = LoggerFactory.getLogger("com.juick.server.xmpp"); private ExternalComponent router; @Inject - private XMPPServer xmpp; - @Inject private CommandsManager commandsManager; @Value("${xmppbot_jid:juick@localhost}") private Jid jid; @@ -96,6 +93,8 @@ public class XMPPConnection implements StanzaListener, NotificationListener { private String componentName; @Value("${component_port:5347}") private int componentPort; + @Value("${component_host:localhost}") + private String componentHost; @Value("${xmpp_password:secret}") private String password; @Value("${upload_tmp_dir:#{systemEnvironment['TEMP'] ?: '/tmp'}}") @@ -110,7 +109,7 @@ public class XMPPConnection implements StanzaListener, NotificationListener { @Inject private BasicXmppSession session; @Inject - private ExecutorService service; + private ExecutorService executorService; @Value("${service_user:juick}") private String serviceUsername; @Inject @@ -121,8 +120,7 @@ public class XMPPConnection implements StanzaListener, NotificationListener { @PostConstruct public void init() { logger.info("stream router start connecting to {}", componentPort); - xmpp.addStanzaListener(this); - router = ExternalComponent.create(componentName, password, session.getConfiguration(), "localhost", componentPort); + router = ExternalComponent.create(componentName, password, session.getConfiguration(), componentHost, componentPort); ServiceDiscoveryManager serviceDiscoveryManager = router.getManager(ServiceDiscoveryManager.class); serviceDiscoveryManager.addIdentity(Identity.clientBot().withName("Juick")); EntityCapabilitiesManager entityCapabilitiesManager = router.getManager(EntityCapabilitiesManager.class); @@ -259,7 +257,7 @@ public class XMPPConnection implements StanzaListener, NotificationListener { router.addInboundPresenceListener(event -> { incomingPresence(event.getPresence()); }); - service.submit(() -> { + executorService.submit(() -> { try { router.connect(); } catch (XmppException e) { @@ -644,11 +642,6 @@ public class XMPPConnection implements StanzaListener, NotificationListener { return null; } - @Override - public void stanzaReceived(Stanza xmlValue) { - router.send(xmlValue); - } - private void broadcastPresence(Presence.Type type) { Presence presence = new Presence(); presence.setFrom(jid); diff --git a/src/main/java/com/juick/server/XMPPServer.java b/src/main/java/com/juick/server/XMPPServer.java deleted file mode 100644 index 86ab6a78..00000000 --- a/src/main/java/com/juick/server/XMPPServer.java +++ /dev/null @@ -1,429 +0,0 @@ -/* - * Copyright (C) 2008-2017, Juick - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package com.juick.server; - -import com.juick.server.xmpp.router.StreamError; -import com.juick.server.xmpp.s2s.*; -import com.juick.service.UserService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.scheduling.annotation.Scheduled; -import org.xmlpull.v1.XmlPullParserException; -import rocks.xmpp.addr.Jid; -import rocks.xmpp.core.stanza.model.Stanza; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import javax.inject.Inject; -import javax.net.ssl.*; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Unmarshaller; -import java.io.IOException; -import java.io.StringReader; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketException; -import java.security.InvalidAlgorithmParameterException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.SecureRandom; -import java.security.cert.*; -import java.time.Duration; -import java.time.Instant; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * @author ugnich - */ -public class XMPPServer implements ConnectionListener { - private static final Logger logger = LoggerFactory.getLogger("com.juick.server.xmpp"); - - private static final int TIMEOUT_MINUTES = 15; - - @Inject - public ExecutorService service; - @Value("${hostname:localhost}") - private Jid jid; - @Value("${s2s_port:5269}") - private int s2sPort; - @Value("${broken_ssl_hosts:}") - public String[] brokenSSLhosts; - @Value("${banned_hosts:}") - public String[] bannedHosts; - - private final List inConnections = new CopyOnWriteArrayList<>(); - private final Map> outConnections = new ConcurrentHashMap<>(); - private final List outCache = new CopyOnWriteArrayList<>(); - private final List stanzaListeners = new CopyOnWriteArrayList<>(); - private final AtomicBoolean closeFlag = new AtomicBoolean(false); - - SSLContext sc; - CertificateFactory cf; - CertPathValidator cpv; - PKIXParameters params; - private TrustManager[] trustAllCerts = new TrustManager[]{ - new X509TrustManager() { - public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) { - } - - public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) { - } - - public java.security.cert.X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - } - }; - private boolean tlsConfigured = false; - - - private ServerSocket listener; - - @Inject - private BasicXmppSession session; - @Inject - private UserService userService; - @Inject - private KeystoreManager keystoreManager; - - @PostConstruct - public void init() throws KeyStoreException { - closeFlag.set(false); - try { - sc = SSLContext.getInstance("TLSv1.2"); - sc.init(keystoreManager.getKeymanagerFactory().getKeyManagers(), trustAllCerts, new SecureRandom()); - TrustManagerFactory trustManagerFactory = - TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - Set ca = new HashSet<>(); - trustManagerFactory.init((KeyStore)null); - Arrays.stream(trustManagerFactory.getTrustManagers()).forEach(t -> Arrays.stream(((X509TrustManager)t).getAcceptedIssuers()).forEach(cert -> ca.add(new TrustAnchor(cert, null)))); - params = new PKIXParameters(ca); - params.setRevocationEnabled(false); - cpv = CertPathValidator.getInstance("PKIX"); - cf = CertificateFactory.getInstance( "X.509" ); - tlsConfigured = true; - } catch (Exception e) { - logger.warn("tls unavailable"); - } - service.submit(() -> { - try { - listener = new ServerSocket(s2sPort); - logger.info("s2s listener ready"); - while (!listener.isClosed()) { - if (Thread.currentThread().isInterrupted()) break; - Socket socket = listener.accept(); - ConnectionIn client = new ConnectionIn(this, socket); - addConnectionIn(client); - service.submit(client); - } - } catch (SocketException e) { - // shutdown - } catch (IOException | XmlPullParserException e) { - logger.warn("xmpp exception", e); - } - }); - } - - public void addConnectionIn(ConnectionIn c) { - c.setListener(this); - inConnections.add(c); - } - - public void addConnectionOut(ConnectionOut c, Optional socket) { - c.setListener(this); - outConnections.put(c, socket); - } - - public void removeConnectionIn(ConnectionIn c) { - inConnections.remove(c); - } - - public void removeConnectionOut(ConnectionOut c) { - outConnections.remove(c); - } - - public String getFromCache(Jid to) { - final String[] cache = new String[1]; - outCache.stream().filter(c -> c.hostname != null && c.hostname.equals(to)).findFirst().ifPresent(c -> { - cache[0] = c.xml; - outCache.remove(c); - }); - return cache[0]; - } - - public Optional getConnectionOut(Jid hostname, boolean needReady) { - return outConnections.keySet().stream().filter(c -> c.to != null && - c.to.equals(hostname) && (!needReady || c.streamReady)).findFirst(); - } - - public Optional getConnectionIn(String streamID) { - return inConnections.stream().filter(c -> c.streamID != null && c.streamID.equals(streamID)).findFirst(); - } - - public void sendOut(Jid hostname, String xml) { - boolean haveAnyConn = false; - - ConnectionOut connOut = null; - for (ConnectionOut c : outConnections.keySet()) { - if (c.to != null && c.to.equals(hostname)) { - if (c.streamReady) { - connOut = c; - break; - } else { - haveAnyConn = true; - break; - } - } - } - if (connOut != null) { - connOut.send(xml); - return; - } - - boolean haveCache = false; - for (CacheEntry c : outCache) { - if (c.hostname != null && c.hostname.equals(hostname)) { - c.xml += xml; - c.updated = Instant.now(); - haveCache = true; - break; - } - } - if (!haveCache) { - outCache.add(new CacheEntry(hostname, xml)); - } - - if (!haveAnyConn && !closeFlag.get()) { - try { - createDialbackConnection(hostname.toEscapedString(), null, null); - } catch (Exception e) { - logger.warn("dialback error", e); - } - } - } - - void createDialbackConnection(String to, String checkSID, String dbKey) throws Exception { - ConnectionOut connectionOut = new ConnectionOut(getJid(), Jid.of(to), null, null, checkSID, dbKey); - addConnectionOut(connectionOut, Optional.empty()); - service.submit(() -> { - try { - Socket socket = new Socket(); - socket.connect(DNSQueries.getServerAddress(to)); - connectionOut.setInputStream(socket.getInputStream()); - connectionOut.setOutputStream(socket.getOutputStream()); - addConnectionOut(connectionOut, Optional.of(socket)); - connectionOut.connect(); - } catch (IOException e) { - logger.info("dialback to " + to + " exception", e); - } - }); - } - - public void startDialback(Jid from, String streamId, String dbKey) throws Exception { - Optional c = getConnectionOut(from, false); - if (c.isPresent()) { - c.get().sendDialbackVerify(streamId, dbKey); - } else { - createDialbackConnection(from.toEscapedString(), streamId, dbKey); - } - } - - public void addStanzaListener(StanzaListener listener) { - stanzaListeners.add(listener); - } - - public void onStanzaReceived(String xmlValue) { - logger.info("S2S: {}", xmlValue); - Stanza stanza = parse(xmlValue); - stanzaListeners.forEach(l -> l.stanzaReceived(stanza)); - } - - public BasicXmppSession getSession() { - return session; - } - - public List getInConnections() { - return inConnections; - } - - public Map> getOutConnections() { - return outConnections; - } - - @Override - public boolean isTlsAvailable() { - return tlsConfigured; - } - - @Override - public void starttls(ConnectionIn connection) { - logger.debug("stream {} securing", connection.streamID); - connection.sendStanza(""); - try { - connection.setSocket(sc.getSocketFactory().createSocket(connection.getSocket(), connection.getSocket().getInetAddress().getHostAddress(), - connection.getSocket().getPort(), false)); - SSLSocket sslSocket = (SSLSocket) connection.getSocket(); - sslSocket.addHandshakeCompletedListener(handshakeCompletedEvent -> { - try { - CertPath certPath = cf.generateCertPath(Arrays.asList(handshakeCompletedEvent.getPeerCertificates())); - cpv.validate(certPath, params); - connection.setTrusted(true); - logger.info("connection from {} is trusted", connection.from); - } catch (SSLPeerUnverifiedException | CertificateException | CertPathValidatorException | InvalidAlgorithmParameterException e) { - logger.info("connection from {} is NOT trusted, falling back to dialback", connection.from); - } - }); - sslSocket.setUseClientMode(false); - sslSocket.setNeedClientAuth(true); - sslSocket.startHandshake(); - connection.setSecured(true); - logger.debug("stream from {} secured", connection.streamID); - connection.restartParser(); - } catch (XmlPullParserException | IOException sex) { - logger.warn("stream {} ssl error {}", connection.streamID, sex); - connection.sendStanza(""); - removeConnectionIn(connection); - connection.closeConnection(); - } - } - - @Override - public void proceed(ConnectionOut connection) { - try { - Socket socket = outConnections.get(connection).get(); - socket = sc.getSocketFactory().createSocket(socket, socket.getInetAddress().getHostAddress(), - socket.getPort(), false); - SSLSocket sslSocket = (SSLSocket) socket; - sslSocket.addHandshakeCompletedListener(handshakeCompletedEvent -> { - try { - CertPath certPath = cf.generateCertPath(Arrays.asList(handshakeCompletedEvent.getPeerCertificates())); - cpv.validate(certPath, params); - connection.setTrusted(true); - logger.info("connection to {} is trusted", connection.to); - } catch (SSLPeerUnverifiedException | CertificateException | CertPathValidatorException | InvalidAlgorithmParameterException e) { - logger.info("connection to {} is NOT trusted, falling back to dialback", connection.to); - } - }); - sslSocket.setNeedClientAuth(true); - sslSocket.startHandshake(); - connection.setSecured(true); - logger.debug("stream to {} secured", connection.getStreamID()); - connection.setInputStream(socket.getInputStream()); - connection.setOutputStream(socket.getOutputStream()); - connection.restartStream(); - connection.sendOpenStream(); - } catch (NoSuchElementException | XmlPullParserException | IOException sex) { - logger.error("s2s ssl error: {} {}, error {}", connection.to, connection.getStreamID(), sex); - connection.send(""); - removeConnectionOut(connection); - connection.logoff(); - } - } - - @Override - public void verify(ConnectionOut connection, String from, String type, String sid) { - if (from != null && from.equals(connection.to.toEscapedString()) && sid != null && !sid.isEmpty() && type != null) { - getConnectionIn(sid).ifPresent(c -> c.sendDialbackResult(Jid.of(from), type)); - } - } - - @Override - public void dialbackError(ConnectionOut connection, StreamError error) { - logger.warn("Stream error from {}: {}", connection.getStreamID(), error.getCondition()); - removeConnectionOut(connection); - connection.logoff(); - } - - @Override - public void finished(ConnectionOut connection, boolean dirty) { - logger.warn("stream to {} {} finished, dirty={}", connection.to, connection.getStreamID(), dirty); - removeConnectionOut(connection); - connection.logoff(); - } - - @Override - public void exception(ConnectionOut connection, Exception ex) { - logger.error("s2s out exception: {} {}, exception {}", connection.to, connection.getStreamID(), ex); - removeConnectionOut(connection); - connection.logoff(); - } - - @Override - public void ready(ConnectionOut connection) { - logger.debug("stream to {} {} ready", connection.to, connection.getStreamID()); - String cache = getFromCache(connection.to); - if (cache != null) { - logger.debug("stream to {} {} sending cache", connection.to, connection.getStreamID()); - connection.send(cache); - } - } - - @Override - public boolean securing(ConnectionOut connection) { - return tlsConfigured && !Arrays.asList(brokenSSLhosts).contains(connection.to.toEscapedString()); - } - - public Stanza parse(String xml) { - try { - Unmarshaller unmarshaller = session.createUnmarshaller(); - return (Stanza)unmarshaller.unmarshal(new StringReader(xml)); - } catch (JAXBException e) { - logger.error("JAXB exception", e); - } - return null; - } - - public Jid getJid() { - return jid; - } - @Scheduled(fixedDelay = 10000) - public void cleanUp() { - Instant now = Instant.now(); - outConnections.keySet().stream().filter(c -> Duration.between(c.getUpdated(), now).toMinutes() > TIMEOUT_MINUTES) - .forEach(c -> { - logger.info("closing idle outgoing connection to {}", c.to); - c.logoff(); - outConnections.remove(c); - }); - - inConnections.stream().filter(c -> Duration.between(c.updated, now).toMinutes() > TIMEOUT_MINUTES) - .forEach(c -> { - logger.info("closing idle incoming connection from {}", c.from); - c.closeConnection(); - inConnections.remove(c); - }); - } - @PreDestroy - public void preDestroy() throws IOException { - closeFlag.set(true); - if (listener != null && !listener.isClosed()) { - listener.close(); - } - service.shutdown(); - logger.info("XMPP server destroyed"); - } - - public int getServerPort() { - return s2sPort; - } -} diff --git a/src/main/java/com/juick/server/configuration/XMPPConfig.java b/src/main/java/com/juick/server/configuration/XMPPConfig.java index 2feef286..ffacf4ef 100644 --- a/src/main/java/com/juick/server/configuration/XMPPConfig.java +++ b/src/main/java/com/juick/server/configuration/XMPPConfig.java @@ -1,10 +1,8 @@ package com.juick.server.configuration; import com.juick.server.XMPPConnection; -import com.juick.server.XMPPServer; import com.juick.server.xmpp.JidConverter; import com.juick.server.xmpp.iq.MessageQuery; -import com.juick.server.xmpp.router.XMPPRouter; import com.juick.server.xmpp.s2s.BasicXmppSession; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -40,15 +38,6 @@ public class XMPPConfig { return cs; } @Bean - public XMPPServer xmppServer() { - return new XMPPServer(); - } - @Bean - public XMPPRouter xmppRouter() { - return new XMPPRouter(); - } - @Bean - @DependsOn("xmppRouter") public XMPPConnection xmppConnection() { return new XMPPConnection(); } diff --git a/src/main/java/com/juick/server/xmpp/XMPPStatusPage.java b/src/main/java/com/juick/server/xmpp/XMPPStatusPage.java deleted file mode 100644 index 231696ec..00000000 --- a/src/main/java/com/juick/server/xmpp/XMPPStatusPage.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.juick.server.xmpp; - -import com.juick.server.XMPPServer; -import com.juick.server.xmpp.helpers.XMPPStatus; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -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.RestController; -import rocks.xmpp.addr.Jid; -import springfox.documentation.annotations.ApiIgnore; - -import javax.inject.Inject; -import java.util.stream.Collectors; - -@RestController -@ConditionalOnProperty("xmppbot_jid") -public class XMPPStatusPage { - @Inject - private XMPPServer xmpp; - @ApiIgnore - @RequestMapping(method = RequestMethod.GET, value = "/api/xmpp-status", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) - public XMPPStatus xmppStatus() { - XMPPStatus status = new XMPPStatus(); - if (xmpp != null) { - status.setInbound(xmpp.getInConnections().stream().map(c -> c.from).flatMap(j -> j.stream().map(Jid::getDomain)).collect(Collectors.toList())); - status.setOutbound(xmpp.getOutConnections().keySet().stream() - .map(c -> c.to).map(Jid::getDomain).collect(Collectors.toList())); - } - return status; - } -} diff --git a/src/main/java/com/juick/server/xmpp/helpers/XMPPStatus.java b/src/main/java/com/juick/server/xmpp/helpers/XMPPStatus.java deleted file mode 100644 index 99d89866..00000000 --- a/src/main/java/com/juick/server/xmpp/helpers/XMPPStatus.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2008-2017, Juick - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package com.juick.server.xmpp.helpers; - -import com.juick.server.xmpp.s2s.ConnectionIn; -import com.juick.server.xmpp.s2s.ConnectionOut; - -import java.util.List; -import java.util.Set; - -/** - * Created by vitalyster on 16.02.2017. - */ -public class XMPPStatus { - private List inbound; - private List outbound; - - public List getInbound() { - return inbound; - } - - public void setInbound(List inbound) { - this.inbound = inbound; - } - - public List getOutbound() { - return outbound; - } - - public void setOutbound(List outbound) { - this.outbound = outbound; - } -} diff --git a/src/main/java/com/juick/server/xmpp/router/Handshake.java b/src/main/java/com/juick/server/xmpp/router/Handshake.java deleted file mode 100644 index 0bc501dd..00000000 --- a/src/main/java/com/juick/server/xmpp/router/Handshake.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.juick.server.xmpp.router; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; - -/** - * Created by vitalyster on 30.01.2017. - */ -public class Handshake { - private String value; - - public static Handshake parse(XmlPullParser parser) throws IOException, XmlPullParserException { - parser.next(); - Handshake handshake = new Handshake(); - handshake.setValue(XmlUtils.getTagText(parser)); - return handshake; - } - - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - - @Override - public String toString() { - StringBuilder str = new StringBuilder("").append(getValue()).append(""); - } else { - str.append("/>"); - } - return str.toString(); - } -} diff --git a/src/main/java/com/juick/server/xmpp/router/Stream.java b/src/main/java/com/juick/server/xmpp/router/Stream.java deleted file mode 100644 index 2154edf6..00000000 --- a/src/main/java/com/juick/server/xmpp/router/Stream.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Juick - * Copyright (C) 2008-2011, Ugnich Anton - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package com.juick.server.xmpp.router; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlPullParserFactory; -import rocks.xmpp.addr.Jid; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.util.UUID; - -/** - * - * @author Ugnich Anton - */ -public abstract class Stream { - - public boolean isLoggedIn() { - return loggedIn; - } - - public void setLoggedIn(boolean loggedIn) { - this.loggedIn = loggedIn; - } - - public Jid from; - public Jid to; - private InputStream is; - private OutputStream os; - private XmlPullParserFactory factory; - protected XmlPullParser parser; - private OutputStreamWriter writer; - StreamHandler streamHandler; - private boolean loggedIn; - private Instant created; - private Instant updated; - String streamId; - private boolean secured; - - public Stream(final Jid from, final Jid to, final InputStream is, final OutputStream os) throws XmlPullParserException { - this.from = from; - this.to = to; - this.is = is; - this.os = os; - factory = XmlPullParserFactory.newInstance(); - created = updated = Instant.now(); - streamId = UUID.randomUUID().toString(); - } - - public void restartStream() throws XmlPullParserException { - parser = factory.newPullParser(); - parser.setInput(new InputStreamReader(is, StandardCharsets.UTF_8)); - parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - writer = new OutputStreamWriter(os, StandardCharsets.UTF_8); - } - - public void connect() { - try { - restartStream(); - handshake(); - parse(); - } catch (XmlPullParserException e) { - StreamError invalidXmlError = new StreamError("invalid-xml"); - send(invalidXmlError.toString()); - connectionFailed(new Exception(invalidXmlError.getCondition())); - } catch (IOException e) { - connectionFailed(e); - } - } - - public void setHandler(final StreamHandler streamHandler) { - this.streamHandler = streamHandler; - } - - public abstract void handshake() throws XmlPullParserException, IOException; - - public void logoff() { - setLoggedIn(false); - try { - writer.flush(); - writer.close(); - //TODO close parser - } catch (final Exception e) { - connectionFailed(e); - } - } - - public void send(final String str) { - try { - updated = Instant.now(); - writer.write(str); - writer.flush(); - } catch (final Exception e) { - connectionFailed(e); - } - } - - private void parse() throws IOException, XmlPullParserException { - while (parser.next() != XmlPullParser.END_DOCUMENT) { - if (parser.getEventType() == XmlPullParser.IGNORABLE_WHITESPACE) { - setUpdated(); - } - if (parser.getEventType() != XmlPullParser.START_TAG) { - continue; - } - setUpdated(); - final String tag = parser.getName(); - switch (tag) { - case "message": - case "presence": - case "iq": - streamHandler.stanzaReceived(XmlUtils.parseToString(parser, false)); - break; - case "error": - StreamError error = StreamError.parse(parser); - connectionFailed(new Exception(error.getCondition())); - return; - default: - XmlUtils.skip(parser); - break; - } - } - } - - /** - * This method is used to be called on a parser or a connection error. - * It tries to close the XML-Reader and XML-Writer one last time. - */ - private void connectionFailed(final Exception ex) { - if (isLoggedIn()) { - try { - writer.close(); - //TODO close parser - } catch (Exception e) { - } - } - if (streamHandler != null) { - streamHandler.fail(ex); - } - } - - public Instant getCreated() { - return created; - } - - public Instant getUpdated() { - return updated; - } - public String getStreamId() { - return streamId; - } - - public boolean isSecured() { - return secured; - } - - public void setSecured(boolean secured) { - this.secured = secured; - } - - public void setUpdated() { - this.updated = Instant.now(); - } - - public InputStream getInputStream() { - return is; - } - - public void setInputStream(InputStream is) { - this.is = is; - } - - public OutputStream getOutputStream() { - return os; - } - - public void setOutputStream(OutputStream os) { - this.os = os; - } -} diff --git a/src/main/java/com/juick/server/xmpp/router/StreamComponentServer.java b/src/main/java/com/juick/server/xmpp/router/StreamComponentServer.java deleted file mode 100644 index a58adfc5..00000000 --- a/src/main/java/com/juick/server/xmpp/router/StreamComponentServer.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.juick.server.xmpp.router; - -import org.apache.commons.codec.digest.DigestUtils; -import org.xmlpull.v1.XmlPullParserException; -import rocks.xmpp.addr.Jid; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.UUID; - -/** - * Created by vitalyster on 30.01.2017. - */ -public class StreamComponentServer extends Stream { - - private String streamId, secret; - - public String getStreamId() { - return streamId; - } - - - public StreamComponentServer(InputStream is, OutputStream os, String password) throws XmlPullParserException { - super(null, null, is, os); - secret = password; - streamId = UUID.randomUUID().toString(); - } - @Override - public void handshake() throws XmlPullParserException, IOException { - parser.next(); - if (!parser.getName().equals("stream") - || !parser.getNamespace(null).equals(StreamNamespaces.NS_COMPONENT_ACCEPT) - || !parser.getNamespace("stream").equals(StreamNamespaces.NS_STREAM)) { - throw new IOException("invalid stream"); - } - Jid domain = Jid.of(parser.getAttributeValue(null, "to")); - if (streamHandler.filter(null, domain)) { - send(new XMPPError(XMPPError.Type.cancel, "forbidden").toString()); - throw new IOException("invalid domain"); - } - from = domain; - to = domain; - send(String.format("", StreamNamespaces.NS_STREAM, StreamNamespaces.NS_COMPONENT_ACCEPT, from.asBareJid().toEscapedString(), streamId)); - Handshake handshake = Handshake.parse(parser); - boolean authenticated = handshake.getValue().equals(DigestUtils.sha1Hex(streamId + secret)); - setLoggedIn(authenticated); - if (!authenticated) { - send(new XMPPError(XMPPError.Type.cancel, "not-authorized").toString()); - streamHandler.fail(new IOException("stream:stream, failed authentication")); - return; - } - send(new Handshake().toString()); - streamHandler.ready(this); - } -} diff --git a/src/main/java/com/juick/server/xmpp/router/StreamError.java b/src/main/java/com/juick/server/xmpp/router/StreamError.java deleted file mode 100644 index f731f039..00000000 --- a/src/main/java/com/juick/server/xmpp/router/StreamError.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.juick.server.xmpp.router; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; - - -/** - * Created by vitalyster on 03.02.2017. - */ -public class StreamError { - - private String condition; - private String text; - - public StreamError() {} - - public StreamError(String condition) { - this.condition = condition; - } - - public static StreamError parse(XmlPullParser parser) throws IOException, XmlPullParserException { - StreamError streamError = new StreamError(); - final int initial = parser.getDepth(); - while (true) { - int eventType = parser.next(); - if (eventType == XmlPullParser.START_TAG && parser.getDepth() == initial + 1) { - final String tag = parser.getName(); - final String xmlns = parser.getNamespace(); - if (tag.equals("text") && xmlns.equals(StreamNamespaces.NS_XMPP_STREAMS)) { - streamError.text = XmlUtils.getTagText(parser); - } else if (xmlns.equals(StreamNamespaces.NS_XMPP_STREAMS)) { - streamError.condition = tag; - } else { - XmlUtils.skip(parser); - } - } else if (eventType == XmlPullParser.END_TAG && parser.getDepth() == initial) { - break; - } - } - return streamError; - } - - public String getCondition() { - return condition; - } - - @Override - public String toString() { - return String.format("<%s xmlns='%s'/>", condition, StreamNamespaces.NS_XMPP_STREAMS); - } - - public String getText() { - return text; - } -} diff --git a/src/main/java/com/juick/server/xmpp/router/StreamFeatures.java b/src/main/java/com/juick/server/xmpp/router/StreamFeatures.java deleted file mode 100644 index e8fc324f..00000000 --- a/src/main/java/com/juick/server/xmpp/router/StreamFeatures.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Juick - * Copyright (C) 2008-2013, Ugnich Anton - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package com.juick.server.xmpp.router; - -import java.io.IOException; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -/** - * - * @author Ugnich Anton - */ -public class StreamFeatures { - - public static final int NOTAVAILABLE = -1; - public static final int AVAILABLE = 0; - public static final int REQUIRED = 1; - public int STARTTLS = NOTAVAILABLE; - public int ZLIB = NOTAVAILABLE; - public int PLAIN = NOTAVAILABLE; - public int DIGEST_MD5 = NOTAVAILABLE; - public int REGISTER = NOTAVAILABLE; - public int EXTERNAL = NOTAVAILABLE; - - public static StreamFeatures parse(final XmlPullParser parser) throws XmlPullParserException, IOException { - StreamFeatures features = new StreamFeatures(); - final int initial = parser.getDepth(); - while (true) { - int eventType = parser.next(); - if (eventType == XmlPullParser.START_TAG && parser.getDepth() == initial + 1) { - final String tag = parser.getName(); - final String xmlns = parser.getNamespace(); - if (tag.equals("starttls") && xmlns != null && xmlns.equals("urn:ietf:params:xml:ns:xmpp-tls")) { - features.STARTTLS = AVAILABLE; - while (parser.next() == XmlPullParser.START_TAG) { - if (parser.getName().equals("required")) { - features.STARTTLS = REQUIRED; - } else { - XmlUtils.skip(parser); - } - } - } else if (tag.equals("compression") && xmlns != null && xmlns.equals("http://jabber.org/features/compress")) { - while (parser.next() == XmlPullParser.START_TAG) { - if (parser.getName().equals("method")) { - final String method = XmlUtils.getTagText(parser).toUpperCase(); - if (method.equals("ZLIB")) { - features.ZLIB = AVAILABLE; - } - } else { - XmlUtils.skip(parser); - } - } - } else if (tag.equals("mechanisms") && xmlns != null && xmlns.equals("urn:ietf:params:xml:ns:xmpp-sasl")) { - while (parser.next() == XmlPullParser.START_TAG) { - if (parser.getName().equals("mechanism")) { - final String mechanism = XmlUtils.getTagText(parser).toUpperCase(); - if (mechanism.equals("PLAIN")) { - features.PLAIN = AVAILABLE; - } else if (mechanism.equals("DIGEST-MD5")) { - features.DIGEST_MD5 = AVAILABLE; - } else if (mechanism.equals("EXTERNAL")) { - features.EXTERNAL = AVAILABLE; - } - } else { - XmlUtils.skip(parser); - } - } - } else if (tag.equals("register") && xmlns != null && xmlns.equals("http://jabber.org/features/iq-register")) { - features.REGISTER = AVAILABLE; - XmlUtils.skip(parser); - } else { - XmlUtils.skip(parser); - } - } else if (eventType == XmlPullParser.END_TAG && parser.getDepth() == initial) { - break; - } - } - return features; - } -} diff --git a/src/main/java/com/juick/server/xmpp/router/StreamHandler.java b/src/main/java/com/juick/server/xmpp/router/StreamHandler.java deleted file mode 100644 index 048c61ec..00000000 --- a/src/main/java/com/juick/server/xmpp/router/StreamHandler.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.juick.server.xmpp.router; - -import rocks.xmpp.addr.Jid; - -/** - * Created by vitalyster on 01.02.2017. - */ -public interface StreamHandler { - void ready(StreamComponentServer componentServer); - void fail(final Exception ex); - boolean filter(Jid from, Jid to); - void stanzaReceived(String stanza); -} diff --git a/src/main/java/com/juick/server/xmpp/router/StreamNamespaces.java b/src/main/java/com/juick/server/xmpp/router/StreamNamespaces.java deleted file mode 100644 index 1b9b1965..00000000 --- a/src/main/java/com/juick/server/xmpp/router/StreamNamespaces.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.juick.server.xmpp.router; - -public class StreamNamespaces { - public static final String NS_STREAM = "http://etherx.jabber.org/streams"; - public static final String NS_TLS = "urn:ietf:params:xml:ns:xmpp-tls"; - public static final String NS_DB = "jabber:server:dialback"; - public static final String NS_SERVER = "jabber:server"; - public static final String NS_COMPONENT_ACCEPT = "jabber:component:accept"; - public static final String NS_XMPP_STREAMS = "urn:ietf:params:xml:ns:xmpp-streams"; -} diff --git a/src/main/java/com/juick/server/xmpp/router/XMPPError.java b/src/main/java/com/juick/server/xmpp/router/XMPPError.java deleted file mode 100644 index 0cf9a3bc..00000000 --- a/src/main/java/com/juick/server/xmpp/router/XMPPError.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Juick - * Copyright (C) 2008-2013, ugnich - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package com.juick.server.xmpp.router; - -import org.apache.commons.text.StringEscapeUtils; - -/** - * - * @author ugnich - */ -public class XMPPError { - - public static final class Type { - - public static final String auth = "auth"; - public static final String cancel = "cancel"; - public static final String continue_ = "continue"; - public static final String modify = "modify"; - public static final String wait = "wait"; - } - private final static String TagName = "error"; - public String by = null; - private String type; - private String condition; - private String text = null; - - public XMPPError(String type, String condition) { - this.type = type; - this.condition = condition; - } - - @Override - public String toString() { - StringBuilder str = new StringBuilder("<").append(TagName).append(""); - if (by != null) { - str.append(" by=\"").append(StringEscapeUtils.escapeXml10(by)).append("\""); - } - if (type != null) { - str.append(" type=\"").append(StringEscapeUtils.escapeXml10(type)).append("\""); - } - - if (condition != null) { - str.append(">"); - str.append("<").append(StringEscapeUtils.escapeXml10(condition)).append(" xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\""); - if (text != null) { - str.append(">").append(StringEscapeUtils.escapeXml10(text)).append(""); - } else { - str.append("/>"); - } - str.append(""); - } else { - str.append("/>"); - } - - return str.toString(); - } -} diff --git a/src/main/java/com/juick/server/xmpp/router/XMPPRouter.java b/src/main/java/com/juick/server/xmpp/router/XMPPRouter.java deleted file mode 100644 index 6d67fa9c..00000000 --- a/src/main/java/com/juick/server/xmpp/router/XMPPRouter.java +++ /dev/null @@ -1,220 +0,0 @@ -package com.juick.server.xmpp.router; - -import com.juick.server.XMPPServer; -import com.juick.server.xmpp.s2s.BasicXmppSession; -import com.juick.server.xmpp.s2s.CacheEntry; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; -import org.xmlpull.v1.XmlPullParserException; -import rocks.xmpp.addr.Jid; -import rocks.xmpp.core.stanza.model.IQ; -import rocks.xmpp.core.stanza.model.Message; -import rocks.xmpp.core.stanza.model.Presence; -import rocks.xmpp.core.stanza.model.Stanza; -import rocks.xmpp.core.stanza.model.server.ServerIQ; -import rocks.xmpp.core.stanza.model.server.ServerMessage; -import rocks.xmpp.core.stanza.model.server.ServerPresence; -import rocks.xmpp.util.XmppUtils; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import javax.inject.Inject; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Unmarshaller; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamWriter; -import java.io.IOException; -import java.io.StringReader; -import java.io.StringWriter; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketException; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.ExecutorService; - -public class XMPPRouter implements StreamHandler { - private static final Logger logger = LoggerFactory.getLogger("com.juick.server.xmpp"); - - @Inject - private ExecutorService service; - - private final List connections = Collections.synchronizedList(new ArrayList<>()); - private final List outCache = new CopyOnWriteArrayList<>(); - - private ServerSocket listener; - - @Inject - private BasicXmppSession session; - - @Value("${router_port:5347}") - private int routerPort; - - @Inject - private XMPPServer xmppServer; - - @PostConstruct - public void init() { - logger.info("component router initialized"); - service.submit(() -> { - try { - listener = new ServerSocket(routerPort); - logger.info("component router listening on {}", routerPort); - while (!listener.isClosed()) { - if (Thread.currentThread().isInterrupted()) break; - Socket socket = listener.accept(); - service.submit(() -> { - try { - StreamComponentServer client = new StreamComponentServer(socket.getInputStream(), socket.getOutputStream(), "secret"); - addConnectionIn(client); - client.setHandler(this); - client.connect(); - } catch (IOException e) { - logger.error("component error", e); - } catch (XmlPullParserException e) { - e.printStackTrace(); - } - }); - } - } catch (SocketException e) { - // shutdown - } catch (IOException e) { - logger.warn("io exception", e); - } - }); - } - - @PreDestroy - public void close() throws Exception { - if (!listener.isClosed()) { - listener.close(); - } - synchronized (getConnections()) { - for (Iterator i = getConnections().iterator(); i.hasNext(); ) { - StreamComponentServer c = i.next(); - c.logoff(); - i.remove(); - } - } - service.shutdown(); - logger.info("XMPP router destroyed"); - } - - private void addConnectionIn(StreamComponentServer c) { - synchronized (getConnections()) { - getConnections().add(c); - } - } - - private void sendOut(Stanza s) { - try { - StringWriter stanzaWriter = new StringWriter(); - XMLStreamWriter xmppStreamWriter = XmppUtils.createXmppStreamWriter( - session.getConfiguration().getXmlOutputFactory().createXMLStreamWriter(stanzaWriter)); - session.createMarshaller().marshal(s, xmppStreamWriter); - xmppStreamWriter.flush(); - xmppStreamWriter.close(); - String xml = stanzaWriter.toString(); - logger.info("XMPPRouter (out): {}", xml); - sendOut(s.getTo().getDomain(), xml); - } catch (XMLStreamException | JAXBException e1) { - logger.info("jaxb exception", e1); - } - } - - private void sendOut(String hostname, String xml) { - boolean haveAnyConn = false; - - StreamComponentServer connOut = null; - synchronized (getConnections()) { - for (StreamComponentServer c : getConnections()) { - if (c.to != null && c.to.getDomain().equals(hostname)) { - if (c.isLoggedIn()) { - connOut = c; - break; - } else { - logger.info("bouncing stanza to {} component until it will be ready", hostname); - boolean haveCache = false; - for (CacheEntry entry : outCache) { - if (entry.hostname != null && entry.hostname.equals(hostname)) { - entry.xml += xml; - entry.updated = Instant.now(); - haveCache = true; - break; - } - } - if (!haveCache) { - outCache.add(new CacheEntry(Jid.of(hostname), xml)); - } - } - } - } - } - if (connOut != null) { - connOut.send(xml); - return; - } - xmppServer.sendOut(Jid.of(hostname), xml); - - } - - public List getConnections() { - return connections; - } - - private Stanza parse(String xml) { - try { - Unmarshaller unmarshaller = session.createUnmarshaller(); - return (Stanza)unmarshaller.unmarshal(new StringReader(xml)); - } catch (JAXBException e) { - logger.error("JAXB exception", e); - } - return null; - } - @Override - public void stanzaReceived(String stanza) { - Stanza input = parse(stanza); - if (input instanceof Message) { - sendOut(ServerMessage.from((Message)input)); - } else if (input instanceof IQ) { - sendOut(ServerIQ.from((IQ)input)); - } else { - sendOut(ServerPresence.from((Presence) input)); - } - } - - public String getFromCache(Jid to) { - final String[] cache = new String[1]; - outCache.stream().filter(c -> c.hostname != null && c.hostname.equals(to)).findFirst().ifPresent(c -> { - cache[0] = c.xml; - outCache.remove(c); - }); - return cache[0]; - } - - @Override - public void ready(StreamComponentServer componentServer) { - logger.info("component {} ready", componentServer.to); - String cache = getFromCache(componentServer.to); - if (cache != null) { - logger.debug("sending cache to {}", componentServer.to); - componentServer.send(cache); - } - } - - @Override - public void fail(Exception e) { - - } - - @Override - public boolean filter(Jid jid, Jid jid1) { - return false; - } -} \ No newline at end of file diff --git a/src/main/java/com/juick/server/xmpp/router/XmlUtils.java b/src/main/java/com/juick/server/xmpp/router/XmlUtils.java deleted file mode 100644 index 7579489f..00000000 --- a/src/main/java/com/juick/server/xmpp/router/XmlUtils.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Juick - * Copyright (C) 2008-2011, Ugnich Anton - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package com.juick.server.xmpp.router; - -import java.io.IOException; - -import org.apache.commons.text.StringEscapeUtils; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -/** - * - * @author Ugnich Anton - */ -public class XmlUtils { - - public static void skip(XmlPullParser parser) throws XmlPullParserException, IOException { - String tag = parser.getName(); - while (parser.getName() != null && !(parser.next() == XmlPullParser.END_TAG && parser.getName().equals(tag))) { - } - } - - public static String getTagText(XmlPullParser parser) throws XmlPullParserException, IOException { - String ret = ""; - String tag = parser.getName(); - - if (parser.next() == XmlPullParser.TEXT) { - ret = parser.getText(); - } - - while (!(parser.getEventType() == XmlPullParser.END_TAG && parser.getName().equals(tag))) { - parser.next(); - } - - return ret; - } - - public static String parseToString(XmlPullParser parser, boolean skipXMLNS) throws XmlPullParserException, IOException { - String tag = parser.getName(); - StringBuilder ret = new StringBuilder("<").append(tag); - - // skipXMLNS for xmlns="jabber:client" - - String ns = parser.getNamespace(); - if (!skipXMLNS && ns != null && !ns.isEmpty()) { - ret.append(" xmlns=\"").append(ns).append("\""); - } - - for (int i = 0; i < parser.getAttributeCount(); i++) { - String attr = parser.getAttributeName(i); - if ((!skipXMLNS || !attr.equals("xmlns")) && !attr.contains(":")) { - ret.append(" ").append(attr).append("=\"").append(StringEscapeUtils.escapeXml10(parser.getAttributeValue(i))).append("\""); - } - } - ret.append(">"); - - while (!(parser.next() == XmlPullParser.END_TAG && parser.getName().equals(tag))) { - int event = parser.getEventType(); - if (event == XmlPullParser.START_TAG) { - if (!parser.getName().contains(":")) { - ret.append(parseToString(parser, false)); - } else { - skip(parser); - } - } else if (event == XmlPullParser.TEXT) { - ret.append(StringEscapeUtils.escapeXml10(parser.getText())); - } - } - - ret.append(""); - return ret.toString(); - } -} diff --git a/src/main/java/com/juick/server/xmpp/s2s/CacheEntry.java b/src/main/java/com/juick/server/xmpp/s2s/CacheEntry.java deleted file mode 100644 index 33e875bd..00000000 --- a/src/main/java/com/juick/server/xmpp/s2s/CacheEntry.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2008-2017, Juick - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package com.juick.server.xmpp.s2s; - -import rocks.xmpp.addr.Jid; - -import java.time.Instant; - -/** - * - * @author ugnich - */ -public class CacheEntry { - - public Jid hostname; - public Instant created; - public Instant updated; - public String xml; - - public CacheEntry(Jid hostname, String xml) { - this.hostname = hostname; - this.created = this.updated =Instant.now(); - this.xml = xml; - } -} diff --git a/src/main/java/com/juick/server/xmpp/s2s/Connection.java b/src/main/java/com/juick/server/xmpp/s2s/Connection.java deleted file mode 100644 index 4fa8e741..00000000 --- a/src/main/java/com/juick/server/xmpp/s2s/Connection.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) 2008-2017, Juick - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package com.juick.server.xmpp.s2s; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.juick.server.XMPPServer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlPullParserFactory; - -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.net.Socket; -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.util.UUID; - -/** - * - * @author ugnich - */ -public class Connection { - - protected static final Logger logger = LoggerFactory.getLogger(Connection.class); - - public String streamID; - public Instant created; - public Instant updated; - public long bytesLocal = 0; - public long packetsLocal = 0; - XMPPServer xmpp; - private Socket socket; - public static final String NS_DB = "jabber:server:dialback"; - public static final String NS_TLS = "urn:ietf:params:xml:ns:xmpp-tls"; - public static final String NS_SASL = "urn:ietf:params:xml:ns:xmpp-sasl"; - public static final String NS_STREAM = "http://etherx.jabber.org/streams"; - XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); - XmlPullParser parser = factory.newPullParser(); - OutputStreamWriter writer; - private boolean secured = false; - private boolean authenticated = false; - private boolean trusted = false; - - - - public Connection(XMPPServer xmpp) throws XmlPullParserException { - this.xmpp = xmpp; - created = updated = Instant.now(); - } - - public void logParser() { - if (streamID == null) { - return; - } - String tag = "IN: <" + parser.getName(); - for (int i = 0; i < parser.getAttributeCount(); i++) { - tag += " " + parser.getAttributeName(i) + "=\"" + parser.getAttributeValue(i) + "\""; - } - tag += ">...\n"; - logger.trace(tag); - } - - public void sendStanza(String xml) { - if (streamID != null) { - logger.trace("OUT: {}\n", xml); - } - try { - writer.write(xml); - writer.flush(); - } catch (IOException e) { - logger.error("send stanza failed", e); - } - - updated = Instant.now(); - bytesLocal += xml.length(); - packetsLocal++; - } - - public void closeConnection() { - if (streamID != null) { - logger.debug("closing stream {}", streamID); - } - - try { - writer.write(""); - } catch (Exception e) { - } - - try { - writer.close(); - } catch (Exception e) { - } - - try { - socket.close(); - } catch (Exception e) { - } - } - - public boolean isSecured() { - return secured; - } - - public void setSecured(boolean secured) { - this.secured = secured; - } - - public void restartParser() throws XmlPullParserException, IOException { - streamID = UUID.randomUUID().toString(); - parser = factory.newPullParser(); - parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - parser.setInput(new InputStreamReader(socket.getInputStream())); - writer = new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8); - } - - @JsonIgnore - public Socket getSocket() { - return socket; - } - - public void setSocket(Socket socket) { - this.socket = socket; - } - - public boolean isAuthenticated() { - return authenticated; - } - - public void setAuthenticated(boolean authenticated) { - this.authenticated = authenticated; - } - - public boolean isTrusted() { - return trusted; - } - - public void setTrusted(boolean trusted) { - this.trusted = trusted; - } -} diff --git a/src/main/java/com/juick/server/xmpp/s2s/ConnectionIn.java b/src/main/java/com/juick/server/xmpp/s2s/ConnectionIn.java deleted file mode 100644 index 3929a69f..00000000 --- a/src/main/java/com/juick/server/xmpp/s2s/ConnectionIn.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright (C) 2008-2017, Juick - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package com.juick.server.xmpp.s2s; - -import com.juick.server.XMPPServer; -import com.juick.server.xmpp.router.StreamError; -import com.juick.server.xmpp.router.XmlUtils; -import org.apache.commons.lang3.StringUtils; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import rocks.xmpp.addr.Jid; - -import java.io.EOFException; -import java.io.IOException; -import java.net.Socket; -import java.net.SocketException; -import java.time.Instant; -import java.util.Arrays; -import java.util.List; -import java.util.UUID; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.stream.Collectors; - -/** - * @author ugnich - */ -public class ConnectionIn extends Connection implements Runnable { - - final public List from = new CopyOnWriteArrayList<>(); - public Instant received; - public long packetsRemote = 0; - ConnectionListener listener; - - public ConnectionIn(XMPPServer xmpp, Socket socket) throws XmlPullParserException, IOException { - super(xmpp); - this.setSocket(socket); - restartParser(); - } - - @Override - public void run() { - try { - parser.next(); // stream:stream - updateTsRemoteData(); - if (!parser.getName().equals("stream") - || !parser.getNamespace("stream").equals(NS_STREAM)) { -// || !parser.getAttributeValue(null, "version").equals("1.0") -// || !parser.getAttributeValue(null, "to").equals(Main.HOSTNAME)) { - throw new Exception(String.format("stream from %s invalid", getSocket().getRemoteSocketAddress())); - } - streamID = parser.getAttributeValue(null, "id"); - if (streamID == null) { - streamID = UUID.randomUUID().toString(); - } - boolean xmppversionnew = parser.getAttributeValue(null, "version") != null; - String from = parser.getAttributeValue(null, "from"); - Thread.currentThread().setName(String.format("XMPP S2S IN %s - %d", from, Thread.currentThread().getId())); - - if (Arrays.asList(xmpp.bannedHosts).contains(from)) { - closeConnection(); - return; - } - sendOpenStream(from, xmppversionnew); - - while (parser.next() != XmlPullParser.END_DOCUMENT) { - updateTsRemoteData(); - if (parser.getEventType() != XmlPullParser.START_TAG) { - continue; - } - logParser(); - - packetsRemote++; - - String tag = parser.getName(); - if (tag.equals("result") && parser.getNamespace().equals(NS_DB)) { - String dfrom = parser.getAttributeValue(null, "from"); - String to = parser.getAttributeValue(null, "to"); - logger.debug("stream from {} to {} {} asking for dialback", dfrom, to, streamID); - if (dfrom.endsWith(xmpp.getJid().toEscapedString()) && (dfrom.equals(xmpp.getJid().toEscapedString()) - || dfrom.endsWith("." + xmpp.getJid()))) { - logger.warn("stream from {} is invalid", dfrom); - break; - } - if (to != null && to.equals(xmpp.getJid().toEscapedString())) { - String dbKey = XmlUtils.getTagText(parser); - updateTsRemoteData(); - xmpp.startDialback(Jid.of(dfrom), streamID, dbKey); - } else { - logger.warn("stream from " + dfrom + " " + streamID + " invalid to " + to); - break; - } - } else if (tag.equals("verify") && parser.getNamespace().equals(NS_DB)) { - String vfrom = parser.getAttributeValue(null, "from"); - String vto = parser.getAttributeValue(null, "to"); - String vid = parser.getAttributeValue(null, "id"); - String vkey = XmlUtils.getTagText(parser); - updateTsRemoteData(); - final boolean[] valid = {false}; - if (vfrom != null && vto != null && vid != null && vkey != null) { - xmpp.getConnectionOut(Jid.of(vfrom), false).ifPresent(c -> { - String dialbackKey = c.dbKey; - valid[0] = vkey.equals(dialbackKey); - }); - } - if (valid[0]) { - sendStanza(""); - logger.debug("stream from {} {} dialback verify valid", vfrom, streamID); - setAuthenticated(true); - } else { - sendStanza(""); - logger.warn("stream from {} {} dialback verify invalid", vfrom, streamID); - } - } else if (tag.equals("presence") && checkFromTo(parser) && isAuthenticated()) { - String xml = XmlUtils.parseToString(parser, false); - logger.debug("stream {} presence: {}", streamID, xml); - xmpp.onStanzaReceived(xml); - } else if (tag.equals("message") && checkFromTo(parser)) { - updateTsRemoteData(); - String xml = XmlUtils.parseToString(parser, false); - logger.debug("stream {} message: {}", streamID, xml); - xmpp.onStanzaReceived(xml); - - } else if (tag.equals("iq") && checkFromTo(parser) && isAuthenticated()) { - updateTsRemoteData(); - String type = parser.getAttributeValue(null, "type"); - String xml = XmlUtils.parseToString(parser, false); - if (type == null || !type.equals("error")) { - logger.debug("stream {} iq: {}", streamID, xml); - xmpp.onStanzaReceived(xml); - } - } else if (!isSecured() && tag.equals("starttls") && !isAuthenticated()) { - listener.starttls(this); - } else if (isSecured() && tag.equals("stream") && parser.getNamespace().equals(NS_STREAM)) { - sendOpenStream(null, true); - } else if (isSecured() && tag.equals("auth") && parser.getNamespace().equals(NS_SASL) - && parser.getAttributeValue(null, "mechanism").equals("EXTERNAL") - && !isAuthenticated() && isTrusted()) { - sendStanza(""); - logger.info("stream {} authenticated externally", streamID); - this.from.add(Jid.of(from)); - setAuthenticated(true); - restartParser(); - } else if (tag.equals("error")) { - StreamError streamError = StreamError.parse(parser); - logger.debug("Stream error {} from {}: {}", streamError.getCondition(), streamID, streamError.getText()); - xmpp.removeConnectionIn(this); - closeConnection(); - } else { - String unhandledStanza = XmlUtils.parseToString(parser, true); - logger.warn("Unhandled stanza from {}: {}", streamID, unhandledStanza); - } - } - logger.warn("stream {} finished", streamID); - xmpp.removeConnectionIn(this); - closeConnection(); - } catch (EOFException | SocketException ex) { - logger.debug("stream {} closed (dirty)", streamID); - xmpp.removeConnectionIn(this); - closeConnection(); - } catch (Exception e) { - logger.debug("stream {} error {}", streamID, e); - xmpp.removeConnectionIn(this); - closeConnection(); - } - } - - void updateTsRemoteData() { - received = Instant.now(); - } - - void sendOpenStream(String from, boolean xmppversionnew) throws IOException { - String openStream = ""; - if (xmppversionnew) { - openStream += ""; - if (listener != null && listener.isTlsAvailable() && !Arrays.asList(xmpp.brokenSSLhosts).contains(from)) { - if (!isSecured()) { - openStream += ""; - } else if (!isAuthenticated() && isTrusted()) { - openStream += "" + - "EXTERNAL" + - ""; - } - } - openStream += ""; - } - sendStanza(openStream); - } - - public void sendDialbackResult(Jid sfrom, String type) { - sendStanza(""); - if (type.equals("valid")) { - from.add(sfrom); - logger.debug("stream from {} {} ready", sfrom, streamID); - setAuthenticated(true); - } - } - - boolean checkFromTo(XmlPullParser parser) throws Exception { - String cfrom = parser.getAttributeValue(null, "from"); - String cto = parser.getAttributeValue(null, "to"); - if (StringUtils.isNotEmpty(cfrom) && StringUtils.isNotEmpty(cto)) { - Jid jidfrom = Jid.of(cfrom); - for (Jid aFrom : from) { - if (aFrom.equals(Jid.of(jidfrom.getDomain()))) { - return true; - } - } - } - logger.warn("rejected from {}, to {}, stream {}", cfrom, cto, from.stream().collect(Collectors.joining(","))); - return false; - } - public void setListener(ConnectionListener listener) { - this.listener = listener; - } -} diff --git a/src/main/java/com/juick/server/xmpp/s2s/ConnectionListener.java b/src/main/java/com/juick/server/xmpp/s2s/ConnectionListener.java deleted file mode 100644 index 4c32b9ae..00000000 --- a/src/main/java/com/juick/server/xmpp/s2s/ConnectionListener.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.juick.server.xmpp.s2s; - - -import com.juick.server.xmpp.router.StreamError; - -public interface ConnectionListener { - boolean isTlsAvailable(); - void starttls(ConnectionIn connection); - void proceed(ConnectionOut connection); - void verify(ConnectionOut connection, String from, String type, String sid); - void dialbackError(ConnectionOut connection, StreamError error); - void finished(ConnectionOut connection, boolean dirty); - void exception(ConnectionOut connection, Exception ex); - void ready(ConnectionOut connection); - boolean securing(ConnectionOut connection); -} diff --git a/src/main/java/com/juick/server/xmpp/s2s/ConnectionOut.java b/src/main/java/com/juick/server/xmpp/s2s/ConnectionOut.java deleted file mode 100644 index 2b919da4..00000000 --- a/src/main/java/com/juick/server/xmpp/s2s/ConnectionOut.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2008-2017, Juick - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package com.juick.server.xmpp.s2s; - -import com.juick.server.xmpp.router.Stream; -import com.juick.server.xmpp.router.StreamError; -import com.juick.server.xmpp.router.StreamFeatures; -import com.juick.server.xmpp.router.XmlUtils; -import com.juick.server.xmpp.s2s.util.DialbackUtils; -import org.apache.commons.codec.Charsets; -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.text.RandomStringGenerator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.xmlpull.v1.XmlPullParser; -import rocks.xmpp.addr.Jid; - -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.SocketException; -import java.util.UUID; - -import static com.juick.server.xmpp.router.StreamNamespaces.NS_STREAM; -import static com.juick.server.xmpp.s2s.Connection.NS_SASL; - -/** - * @author ugnich - */ -public class ConnectionOut extends Stream { - protected static final Logger logger = LoggerFactory.getLogger(ConnectionOut.class); - public static final String NS_TLS = "urn:ietf:params:xml:ns:xmpp-tls"; - public static final String NS_DB = "jabber:server:dialback"; - private boolean secured = false; - private boolean trusted = false; - public boolean streamReady = false; - String checkSID = null; - String dbKey = null; - private String streamID; - ConnectionListener listener; - RandomStringGenerator generator = new RandomStringGenerator.Builder().withinRange('a', 'z').build(); - - public ConnectionOut(Jid from, Jid to, InputStream is, OutputStream os, String checkSID, String dbKey) throws Exception { - super(from, to, is, os); - this.to = to; - this.checkSID = checkSID; - this.dbKey = dbKey; - if (dbKey == null) { - this.dbKey = DialbackUtils.generateDialbackKey(generator.generate(15), to, from, streamID); - } - streamID = UUID.randomUUID().toString(); - Thread.currentThread().setName(String.format("XMPP S2S OUT %s - %d", to.toEscapedString(), Thread.currentThread().getId())); - } - - public void sendOpenStream() throws IOException { - send(""); - } - - void processDialback() throws Exception { - if (checkSID != null) { - sendDialbackVerify(checkSID, dbKey); - } - send("" + - dbKey + ""); - } - - @Override - public void handshake() { - try { - restartStream(); - - sendOpenStream(); - - parser.next(); // stream:stream - streamID = parser.getAttributeValue(null, "id"); - if (streamID == null || streamID.isEmpty()) { - throw new Exception("stream to " + to + " invalid first packet"); - } - - logger.debug("stream to {} {} open", to, streamID); - boolean xmppversionnew = parser.getAttributeValue(null, "version") != null; - if (!xmppversionnew) { - processDialback(); - } - - while (parser.next() != XmlPullParser.END_DOCUMENT) { - if (parser.getEventType() != XmlPullParser.START_TAG) { - continue; - } - - String tag = parser.getName(); - if (tag.equals("result") && parser.getNamespace().equals(NS_DB)) { - String type = parser.getAttributeValue(null, "type"); - if (type != null && type.equals("valid")) { - streamReady = true; - listener.ready(this); - } else { - logger.warn("stream to {} {} dialback fail", to, streamID); - } - XmlUtils.skip(parser); - } else if (tag.equals("verify") && parser.getNamespace().equals(NS_DB)) { - String from = parser.getAttributeValue(null, "from"); - String type = parser.getAttributeValue(null, "type"); - String sid = parser.getAttributeValue(null, "id"); - listener.verify(this, from, type, sid); - XmlUtils.skip(parser); - } else if (tag.equals("features") && parser.getNamespace().equals(NS_STREAM)) { - StreamFeatures features = StreamFeatures.parse(parser); - if (listener != null && !secured && features.STARTTLS >= 0 - && listener.securing(this)) { - logger.debug("stream to {} {} securing", to.toEscapedString(), streamID); - send(""); - } else if (secured && features.EXTERNAL >=0) { - String authid = Base64.encodeBase64String(from.toEscapedString().getBytes(Charsets.UTF_8)); - send(String.format("%s", authid)); - } else if (secured && streamReady) { - listener.ready(this); - } else { - processDialback(); - } - } else if (tag.equals("proceed") && parser.getNamespace().equals(NS_TLS)) { - listener.proceed(this); - } else if (tag.equals("success") && parser.getNamespace().equals(NS_SASL)) { - streamReady = true; - restartStream(); - sendOpenStream(); - } else if (secured && tag.equals("stream") && parser.getNamespace().equals(NS_STREAM)) { - streamID = parser.getAttributeValue(null, "id"); - } else if (tag.equals("error")) { - StreamError streamError = StreamError.parse(parser); - listener.dialbackError(this, streamError); - } else { - String unhandledStanza = XmlUtils.parseToString(parser, false); - logger.warn("Unhandled stanza from {} {} : {}", to, streamID, unhandledStanza); - } - } - listener.finished(this, false); - } catch (EOFException | SocketException eofex) { - listener.finished(this, true); - } catch (Exception e) { - listener.exception(this, e); - } - } - - public void sendDialbackVerify(String sid, String key) { - send("" + - key + ""); - } - public void setListener(ConnectionListener listener) { - this.listener = listener; - } - - public String getStreamID() { - return streamID; - } - - public boolean isSecured() { - return secured; - } - - public void setSecured(boolean secured) { - this.secured = secured; - } - - public boolean isTrusted() { - return trusted; - } - - public void setTrusted(boolean trusted) { - this.trusted = trusted; - } -} diff --git a/src/main/java/com/juick/server/xmpp/s2s/DNSQueries.java b/src/main/java/com/juick/server/xmpp/s2s/DNSQueries.java deleted file mode 100644 index 1367d333..00000000 --- a/src/main/java/com/juick/server/xmpp/s2s/DNSQueries.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2008-2017, Juick - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package com.juick.server.xmpp.s2s; - -import org.apache.commons.lang3.math.NumberUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.InetSocketAddress; -import java.util.Hashtable; -import java.util.Random; -import javax.naming.NamingException; -import javax.naming.directory.Attribute; -import javax.naming.directory.DirContext; -import javax.naming.directory.InitialDirContext; - -/** - * - * @author ugnich - */ -public class DNSQueries { - - private static final Logger logger = LoggerFactory.getLogger(DNSQueries.class); - - private static Random rand = new Random(); - - public static InetSocketAddress getServerAddress(String hostname) { - - String host = hostname; - int port = 5269; - - Hashtable env = new Hashtable<>(5); - env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory"); - try { - DirContext ctx = new InitialDirContext(env); - Attribute att = ctx.getAttributes("_xmpp-server._tcp." + hostname, new String[]{"SRV"}).get("SRV"); - - if (att != null && att.size() > 0) { - int i = rand.nextInt(att.size()); - String srv[] = att.get(i).toString().split(" "); - port = NumberUtils.toInt(srv[2], 5269); - host = srv[3]; - } - ctx.close(); - } catch (NamingException e) { - logger.debug("SRV record for {} is not resolved, falling back to A record", hostname); - } - return new InetSocketAddress(host, port); - } -} diff --git a/src/main/java/com/juick/server/xmpp/s2s/util/DialbackUtils.java b/src/main/java/com/juick/server/xmpp/s2s/util/DialbackUtils.java deleted file mode 100644 index d25dbad8..00000000 --- a/src/main/java/com/juick/server/xmpp/s2s/util/DialbackUtils.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2008-2017, Juick - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package com.juick.server.xmpp.s2s.util; - -import org.apache.commons.codec.digest.DigestUtils; -import org.apache.commons.codec.digest.HmacAlgorithms; -import org.apache.commons.codec.digest.HmacUtils; -import rocks.xmpp.addr.Jid; - -/** - * Created by vitalyster on 05.12.2016. - */ -public class DialbackUtils { - private DialbackUtils() { - throw new IllegalStateException(); - } - - public static String generateDialbackKey(String secret, Jid to, Jid from, String id) { - return new HmacUtils(HmacAlgorithms.HMAC_SHA_256, DigestUtils.sha256(secret)) - .hmacHex(to.toEscapedString() + " " + from.toEscapedString() + " " + id); - } -} diff --git a/src/test/java/com/juick/server/tests/ServerTests.java b/src/test/java/com/juick/server/tests/ServerTests.java index 32afd4f9..620e210c 100644 --- a/src/test/java/com/juick/server/tests/ServerTests.java +++ b/src/test/java/com/juick/server/tests/ServerTests.java @@ -42,8 +42,6 @@ import com.juick.server.api.xnodeinfo2.model.NodeInfo; import com.juick.server.util.HttpUtils; import com.juick.server.util.ImageUtils; import com.juick.server.www.WebApp; -import com.juick.server.xmpp.helpers.XMPPStatus; -import com.juick.server.xmpp.s2s.ConnectionIn; import com.juick.service.*; import com.juick.service.component.MessageEvent; import com.juick.util.DateFormattersHolder; @@ -51,7 +49,6 @@ import com.juick.util.MessageUtils; import com.mitchellbosecke.pebble.PebbleEngine; import com.mitchellbosecke.pebble.error.PebbleException; import com.mitchellbosecke.pebble.template.PebbleTemplate; -import org.apache.commons.codec.CharEncoding; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.IteratorUtils; import org.apache.commons.io.IOUtils; @@ -162,8 +159,6 @@ public class ServerTests { @Inject private ObjectMapper jsonMapper; @Inject - private XMPPServer server; - @Inject private CommandsManager commandsManager; @Inject private XMPPConnection router; @@ -687,63 +682,6 @@ public class ServerTests { .andExpect(xpath("/rss/channel/description").string("The latest messages at Juick")); } @Test - public void statusPageIsUp() throws Exception { - mockMvc.perform(get("/api/status").with(httpBasic(ugnichName, ugnichPassword))).andExpect(status().isOk()); - assertThat(server.getJid(), equalTo(jid)); - } - @Test - public void botIsUpAndProcessingResourceConstraints() throws Exception { - jdbcTemplate.execute("DELETE FROM users WHERE nick='renha'"); - int renhaId = userService.createUser("renha", "umnnbt"); - Jid from = Jid.of("renha@serverstorageisfull.tld"); - jdbcTemplate.update("INSERT INTO jids(user_id,jid,active) VALUES(?,?,?)", - renhaId, from.toEscapedString(), 1); - rocks.xmpp.core.stanza.model.Message xmppMessage = new rocks.xmpp.core.stanza.model.Message(); - xmppMessage.setType(rocks.xmpp.core.stanza.model.Message.Type.ERROR); - xmppMessage.setFrom(from); - xmppMessage.setTo(botJid); - StanzaError err = new StanzaError(StanzaError.Type.CANCEL, Condition.RESOURCE_CONSTRAINT); - xmppMessage.setError(err); - Function isActive = f -> jdbcTemplate.queryForObject("SELECT active FROM jids WHERE user_id=?", Integer.class, f) == 1; - assertThat(isActive.apply(renhaId), equalTo(true)); - ClientMessage result = router.incomingMessage(xmppMessage); - assertNull(result); - assertThat(isActive.apply(renhaId), equalTo(false)); - xmppMessage.setError(null); - xmppMessage.setType(rocks.xmpp.core.stanza.model.Message.Type.CHAT); - xmppMessage.setBody("On"); - result = router.incomingMessage(xmppMessage); - assertThat(result.getBody(), equalTo("XMPP notifications are activated")); - assertTrue(isActive.apply(renhaId)); - xmppMessage.setBody("*test test"); - result = router.incomingMessage(xmppMessage); - assertThat(result.getBody(), startsWith("New message posted")); - xmppMessage.setFrom(from); - xmppMessage.setBody("PING"); - result = router.incomingMessage(xmppMessage); - assertThat(result.getBody(), equalTo("PONG")); - int secretlySadId = userService.createUser("secretlysad", "bbk"); - xmppMessage.setTo(botJid.withLocal("secretlysad")); - xmppMessage.setBody("What's up?"); - result = router.incomingMessage(xmppMessage); - assertThat(result, is(nullValue())); - xmppMessage.setTo(botJid); - xmppMessage.setBody("@secretlysad Hey!"); - result = router.incomingMessage(xmppMessage); - assertThat(result.getBody(), is("Private message sent")); - assertThat(pmQueriesService.getPMMessages(renhaId, secretlySadId).size(), is(2)); - String xml = "@yo:\n" + - "343432434\n" + - "\n" + - "#2 http://juick.com/2juick-2343432434@yo"; - result = router.incomingMessage((rocks.xmpp.core.stanza.model.Message)server.parse(xml)); - String active = ""; - result = router.incomingMessage((rocks.xmpp.core.stanza.model.Message)server.parse(active)); - xmppMessage.setFrom(botJid); - // TODO: assert events - } - @Test public void botCommandsTests() throws Exception { assertThat(commandsManager.processCommand(AnonymousUser.INSTANCE, "PING", emptyUri).getText(), is("PONG")); // subscription commands have two lines, others have 1 @@ -1328,25 +1266,6 @@ public class ServerTests { assertThat(subscriptionService.getUsersSubscribedToComments(msg, reply).size(), is(0)); } @Test - public void xmppStatusApi() throws Exception { - Supplier getStatus = () -> { - try { - MvcResult result = mockMvc.perform(get("/api/xmpp-status").with(httpBasic(ugnichName, ugnichPassword))) - .andExpect(status().isOk()).andReturn(); - return jsonMapper.readValue(result.getResponse().getContentAsString(), XMPPStatus.class); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - }; - assertThat(getStatus.get().getInbound(), is(nullValue())); - ConnectionIn test = new ConnectionIn(server, new Socket("localhost", server.getServerPort())); - test.from.add(Jid.of("test")); - server.getInConnections().clear(); - server.addConnectionIn(test); - assertThat(getStatus.get().getInbound().size(), is(1)); - } - @Test public void credentialsShouldNeverBeSerialized() throws Exception { int uid = userService.createUser("yyy", "xxxx"); User yyy = userService.getUserByUID(uid).get(); -- cgit v1.2.3 From dcf1b9e5e4312cfd685c1f3cb0f819e7ac64be75 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Mon, 31 Dec 2018 01:35:11 +0300 Subject: XMPP cleanup --- src/main/java/com/juick/server/XMPPConnection.java | 672 -------------------- src/main/java/com/juick/server/XMPPManager.java | 678 +++++++++++++++++++++ .../com/juick/server/configuration/XMPPConfig.java | 23 +- .../juick/server/xmpp/s2s/BasicXmppSession.java | 68 --- .../com/juick/server/xmpp/s2s/StanzaListener.java | 28 - .../java/com/juick/server/tests/ServerTests.java | 8 - 6 files changed, 681 insertions(+), 796 deletions(-) delete mode 100644 src/main/java/com/juick/server/XMPPConnection.java create mode 100644 src/main/java/com/juick/server/XMPPManager.java delete mode 100644 src/main/java/com/juick/server/xmpp/s2s/BasicXmppSession.java delete mode 100644 src/main/java/com/juick/server/xmpp/s2s/StanzaListener.java (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/XMPPConnection.java b/src/main/java/com/juick/server/XMPPConnection.java deleted file mode 100644 index c4816478..00000000 --- a/src/main/java/com/juick/server/XMPPConnection.java +++ /dev/null @@ -1,672 +0,0 @@ -/* - * Copyright (C) 2008-2017, Juick - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package com.juick.server; - -import com.juick.User; -import com.juick.formatters.PlainTextFormatter; -import com.juick.server.www.WebApp; -import com.juick.service.component.*; -import com.juick.model.CommandResult; -import com.juick.server.xmpp.iq.MessageQuery; -import com.juick.server.xmpp.s2s.BasicXmppSession; -import com.juick.service.*; -import com.juick.util.MessageUtils; -import org.apache.commons.codec.digest.DigestUtils; -import org.apache.commons.io.FilenameUtils; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import rocks.xmpp.addr.Jid; -import rocks.xmpp.core.XmppException; -import rocks.xmpp.core.session.XmppSession; -import rocks.xmpp.core.stanza.AbstractIQHandler; -import rocks.xmpp.core.stanza.model.*; -import rocks.xmpp.core.stanza.model.client.ClientMessage; -import rocks.xmpp.core.stanza.model.client.ClientPresence; -import rocks.xmpp.core.stanza.model.errors.Condition; -import rocks.xmpp.extensions.caps.EntityCapabilitiesManager; -import rocks.xmpp.extensions.component.accept.ExternalComponent; -import rocks.xmpp.extensions.disco.ServiceDiscoveryManager; -import rocks.xmpp.extensions.disco.model.info.Identity; -import rocks.xmpp.extensions.filetransfer.FileTransfer; -import rocks.xmpp.extensions.filetransfer.FileTransferManager; -import rocks.xmpp.extensions.nick.model.Nickname; -import rocks.xmpp.extensions.oob.model.x.OobX; -import rocks.xmpp.extensions.ping.PingManager; -import rocks.xmpp.extensions.receipts.MessageDeliveryReceiptsManager; -import rocks.xmpp.extensions.vcard.temp.model.VCard; -import rocks.xmpp.extensions.version.SoftwareVersionManager; -import rocks.xmpp.extensions.version.model.SoftwareVersion; -import rocks.xmpp.util.XmppUtils; - -import javax.annotation.Nonnull; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import javax.inject.Inject; -import javax.xml.bind.JAXBException; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamWriter; -import java.io.IOException; -import java.io.StringWriter; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; - -/** - * @author ugnich - */ -public class XMPPConnection implements NotificationListener { - - private static final Logger logger = LoggerFactory.getLogger("com.juick.server.xmpp"); - - private ExternalComponent router; - @Inject - private CommandsManager commandsManager; - @Value("${xmppbot_jid:juick@localhost}") - private Jid jid; - @Value("${hostname:localhost}") - private String componentName; - @Value("${component_port:5347}") - private int componentPort; - @Value("${component_host:localhost}") - private String componentHost; - @Value("${xmpp_password:secret}") - private String password; - @Value("${upload_tmp_dir:#{systemEnvironment['TEMP'] ?: '/tmp'}}") - private String tmpDir; - - @Inject - private MessagesService messagesService; - @Inject - private UserService userService; - @Inject - private PMQueriesService pmQueriesService; - @Inject - private BasicXmppSession session; - @Inject - private ExecutorService executorService; - @Value("${service_user:juick}") - private String serviceUsername; - @Inject - private WebApp webApp; - - private User serviceUser; - - @PostConstruct - public void init() { - logger.info("stream router start connecting to {}", componentPort); - router = ExternalComponent.create(componentName, password, session.getConfiguration(), componentHost, componentPort); - ServiceDiscoveryManager serviceDiscoveryManager = router.getManager(ServiceDiscoveryManager.class); - serviceDiscoveryManager.addIdentity(Identity.clientBot().withName("Juick")); - EntityCapabilitiesManager entityCapabilitiesManager = router.getManager(EntityCapabilitiesManager.class); - entityCapabilitiesManager.setNode("https://juick.com/caps"); - MessageDeliveryReceiptsManager messageDeliveryReceiptsManager = router.getManager(MessageDeliveryReceiptsManager.class); - messageDeliveryReceiptsManager.setEnabled(true); - PingManager pingManager = router.getManager(PingManager.class); - pingManager.setEnabled(true); - SoftwareVersionManager softwareVersionManager = router.getManager(SoftwareVersionManager.class); - softwareVersionManager.setSoftwareVersion(new SoftwareVersion("Juick", "2.x", - System.getProperty("os.name", "generic"))); - VCard vCard = new VCard(); - vCard.setFormattedName("Juick"); - vCard.setBirthday(LocalDate.of(2008, 10, 22)); - try { - vCard.setUrl(new URL("http://juick.com/")); - vCard.setPhoto(new VCard.Image("image/png", IOUtils.toByteArray( - getClass().getClassLoader().getResource("juick.png")))); - } catch (MalformedURLException e) { - logger.error("invalid url", e); - } catch (IOException e) { - logger.warn("invalid resource", e); - } - router.addIQHandler(MessageQuery.class, iq -> { - Message warningMessage = new Message(iq.getFrom(), Message.Type.CHAT); - warningMessage.setFrom(jid); - warningMessage.setBody("Your XMPP client constantly polls us with XMPP query which is unsupported for years, please find http://juick.com/query#messages in your client code and remove that"); - router.send(warningMessage); - return iq.createError(new StanzaError(Condition.BAD_REQUEST, "Please stop this spam")); - }); - router.addIQHandler(VCard.class, new AbstractIQHandler(IQ.Type.GET) { - @Override - protected IQ processRequest(IQ iq) { - if (iq.getTo().equals(jid) || iq.getTo().asBareJid().equals(jid.asBareJid()) - || iq.getTo().asBareJid().toEscapedString().equals(jid.getDomain())) { - return iq.createResult(vCard); - } - User user = userService.getUserByName(iq.getTo().getLocal()); - if (!user.isAnonymous()) { - User info = userService.getUserInfo(user); - VCard userVCard = new VCard(); - userVCard.setFormattedName(info.getFullName()); - userVCard.setNickname(user.getName()); - try { - userVCard.setPhoto(new VCard.Image(URI.create(webApp.getAvatarUrl(user)))); - if (info.getUrl() != null) { - userVCard.setUrl(new URL(info.getUrl())); - } - } catch (MalformedURLException e) { - logger.warn("url exception", e); - } - return iq.createResult(userVCard); - } - return iq.createError(Condition.BAD_REQUEST); - } - }); - router.addInboundMessageListener(e -> { - ClientMessage result = incomingMessage(e.getMessage()); - if (result != null) { - router.send(result); - } - }); - router.addInboundIQListener(e -> { - IQ iq = e.getIQ(); - Jid jid = iq.getTo(); - if (!jid.getDomain().equals(this.jid.getDomain())) { - router.send(iq); - } - }); - FileTransferManager fileTransferManager = router.getManager(FileTransferManager.class); - fileTransferManager.addFileTransferOfferListener(e -> { - try { - List allowedTypes = new ArrayList() {{ - add("png"); - add("jpg"); - }}; - String attachmentExtension = FilenameUtils.getExtension(e.getName()).toLowerCase(); - String targetFilename = String.format("%s.%s", - DigestUtils.md5Hex(String.format("%s-%s", - e.getInitiator().toString(), e.getSessionId()).getBytes()), attachmentExtension); - if (allowedTypes.contains(attachmentExtension)) { - Path filePath = Paths.get(tmpDir, targetFilename); - FileTransfer ft = e.accept(filePath).get(); - ft.addFileTransferStatusListener(st -> { - logger.debug("{}: received {} of {}", e.getName(), st.getBytesTransferred(), e.getSize()); - if (st.getStatus().equals(FileTransfer.Status.COMPLETED)) { - logger.info("transfer completed"); - try { - Jid initiator = e.getInitiator(); - ClientMessage result = incomingMessageJuick( - userService.getUserByJID(initiator.asBareJid().toEscapedString()), initiator, - jid.getLocal(), StringUtils.defaultString(e.getDescription()).trim(), URI.create(String.format("juick://%s", targetFilename))); - if (result != null) { - router.send(result); - } - } catch (Exception e1) { - logger.error("ft error", e1); - } - - } else if (st.getStatus().equals(FileTransfer.Status.FAILED)) { - logger.info("transfer failed", ft.getException()); - Message msg = new Message(); - msg.setType(Message.Type.CHAT); - msg.setFrom(jid); - msg.setTo(e.getInitiator()); - msg.setBody("File transfer failed, please report to us"); - router.sendMessage(msg); - } else if (st.getStatus().equals(FileTransfer.Status.CANCELED)) { - logger.info("transfer cancelled"); - } - }); - ft.transfer(); - logger.info("transfer started"); - } else { - e.reject(); - logger.info("transfer rejected"); - } - } catch (IOException | InterruptedException | ExecutionException e1) { - logger.error("ft error", e1); - } - }); - router.addConnectionListener(event -> { - if (event.getType().equals(rocks.xmpp.core.session.ConnectionEvent.Type.RECONNECTION_SUCCEEDED)) { - logger.info("component connected"); - } - }); - router.addSessionStatusListener(event -> { - logger.info("event: " + event.getStatus(), event.getThrowable()); - if (event.getStatus().equals(XmppSession.Status.AUTHENTICATED)) { - logger.info("Authenticated, broadcasting..."); - broadcastPresence(null); - } - }); - router.addInboundPresenceListener(event -> { - incomingPresence(event.getPresence()); - }); - executorService.submit(() -> { - try { - router.connect(); - } catch (XmppException e) { - logger.warn("xmpp exception", e); - } - }); - serviceUser = userService.getUserByName(serviceUsername); - } - - private String stanzaToString(Stanza stanza) throws XMLStreamException, JAXBException { - StringWriter stanzaWriter = new StringWriter(); - XMLStreamWriter xmppStreamWriter = XmppUtils.createXmppStreamWriter( - router.getConfiguration().getXmlOutputFactory().createXMLStreamWriter(stanzaWriter)); - router.createMarshaller().marshal(stanza, xmppStreamWriter); - xmppStreamWriter.flush(); - xmppStreamWriter.close(); - return stanzaWriter.toString(); - } - - private void sendJuickMessage(com.juick.Message jmsg, List users) { - List jids = new ArrayList<>(); - - for (User user : users) { - jids.addAll(userService.getJIDsbyUID(user.getUid())); - } - com.juick.Message fullMsg = messagesService.getMessage(jmsg.getMid()).orElseThrow(IllegalStateException::new); - String txt = "@" + jmsg.getUser().getName() + ":" + MessageUtils.getTagsString(fullMsg) + "\n"; - String attachmentUrl = MessageUtils.attachmentUrl(fullMsg); - if (StringUtils.isNotEmpty(attachmentUrl)) { - txt += attachmentUrl + "\n"; - } - txt += StringUtils.defaultString(jmsg.getText()) + "\n\n"; - txt += "#" + jmsg.getMid() + " http://juick.com/m/" + jmsg.getMid(); - - Nickname nick = new Nickname("@" + jmsg.getUser().getName()); - - Message msg = new Message(); - msg.setFrom(jid); - msg.setBody(txt); - msg.setType(Message.Type.CHAT); - msg.setThread("juick-" + jmsg.getMid()); - msg.addExtension(jmsg); - msg.addExtension(nick); - if (StringUtils.isNotEmpty(attachmentUrl)) { - try { - OobX oob = new OobX(new URI(attachmentUrl)); - msg.addExtension(oob); - } catch (URISyntaxException e) { - logger.warn("uri exception", e); - } - } - for (String jid : jids) { - msg.setTo(Jid.of(jid)); - router.send(ClientMessage.from(msg)); - } - } - - public void sendJuickComment(com.juick.Message jmsg, List users) { - String replyQuote; - String replyTo; - - com.juick.Message replyMessage = jmsg.getReplyto() > 0 ? messagesService.getReply(jmsg.getMid(), jmsg.getReplyto()) - : messagesService.getMessage(jmsg.getMid()).orElseThrow(IllegalStateException::new); - replyTo = replyMessage.getUser().getName(); - com.juick.Message fullReply = messagesService.getReply(jmsg.getMid(), jmsg.getRid()); - replyQuote = StringUtils.defaultString(fullReply.getReplyQuote()); - - String txt = "Reply by @" + jmsg.getUser().getName() + ":\n" + replyQuote + "\n@" + replyTo + " "; - String attachmentUrl = MessageUtils.attachmentUrl(fullReply); - if (StringUtils.isNotEmpty(attachmentUrl)) { - txt += attachmentUrl + "\n"; - } - txt += StringUtils.defaultString(jmsg.getText()) + "\n\n" + "#" + jmsg.getMid() + "/" + jmsg.getRid() + " http://juick.com/m/" + jmsg.getMid() + "#" + jmsg.getRid(); - - Message msg = new Message(); - msg.setFrom(jid); - msg.setBody(txt); - msg.setType(Message.Type.CHAT); - msg.addExtension(jmsg); - for (User user : users) { - for (String jid : userService.getJIDsbyUID(user.getUid())) { - msg.setTo(Jid.of(jid)); - router.send(ClientMessage.from(msg)); - } - } - } - - @Override - public void processMessageEvent(MessageEvent event) { - com.juick.Message msg = event.getMessage(); - List subscribers = event.getUsers(); - if (msg.isService()) { - return; - } - if (MessageUtils.isPM(msg)) { - userService.getJIDsbyUID(msg.getTo().getUid()) - .forEach(userJid -> { - Message mm = new Message(); - mm.setTo(Jid.of(userJid)); - mm.setType(Message.Type.CHAT); - boolean inroster = pmQueriesService.havePMinRoster(msg.getUser().getUid(), userJid); - if (inroster) { - mm.setFrom(Jid.of(msg.getUser().getName(), "juick.com", "Juick")); - mm.setBody(msg.getText()); - } else { - mm.setFrom(jid); - mm.setBody("Private message from @" + msg.getUser().getName() + ":\n" + msg.getText()); - } - router.send(ClientMessage.from(mm)); - }); - } else if (MessageUtils.isReply(msg)) { - sendJuickComment(msg, subscribers); - } - else { - sendJuickMessage(msg, subscribers); - } - } - - private ClientMessage makeReply(Jid jidTo, String txt) { - Message reply = new Message(); - reply.setFrom(jid); - reply.setTo(jidTo); - reply.setType(Message.Type.CHAT); - reply.setBody(txt); - return ClientMessage.from(reply); - } - - @Override - public void processSubscribeEvent(SubscribeEvent subscribeEvent) { - - } - - @Override - public void processLikeEvent(LikeEvent likeEvent) { - List users = likeEvent.getSubscribers(); - com.juick.Message jmsg = likeEvent.getMessage(); - User liker = likeEvent.getUser(); - - if (!userService.isInBLAny(jmsg.getUser().getUid(), liker.getUid())) { - userService.getJIDsbyUID(jmsg.getUser().getUid()).forEach(authorJid -> { - Message xmppMessage = new Message(); - xmppMessage.setFrom(jid); - xmppMessage.setTo(Jid.of(authorJid)); - xmppMessage.setType(Message.Type.CHAT); - xmppMessage.addExtension(jmsg); - xmppMessage.setBody(String.format("%s recommended your post #%d. %s", - liker.getName(), jmsg.getMid(), PlainTextFormatter.formatUrl(jmsg))); - router.send(ClientMessage.from(xmppMessage)); - }); - } - - String txt = "Recommended by @" + liker.getName() + ":\n"; - txt += "@" + jmsg.getUser().getName() + ":" + MessageUtils.getTagsString(jmsg) + "\n"; - String attachmentUrl = MessageUtils.attachmentUrl(jmsg); - if (StringUtils.isNotEmpty(attachmentUrl)) { - txt += attachmentUrl + "\n"; - } - txt += StringUtils.defaultString(jmsg.getText()) + "\n\n"; - txt += "#" + jmsg.getMid(); - if (jmsg.getReplies() > 0) { - if (jmsg.getReplies() % 10 == 1 && jmsg.getReplies() % 100 != 11) { - txt += " (" + jmsg.getReplies() + " reply)"; - } else { - txt += " (" + jmsg.getReplies() + " replies)"; - } - } - txt += " http://juick.com/m/" + jmsg.getMid(); - - Nickname nick = new Nickname("@" + jmsg.getUser().getName()); - - Message msg = new Message(); - msg.setFrom(jid); - msg.setBody(txt); - msg.setType(Message.Type.CHAT); - msg.setThread("juick-" + jmsg.getMid()); - msg.addExtension(jmsg); - msg.addExtension(nick); - if (StringUtils.isNotEmpty(attachmentUrl)) { - try { - OobX oob = new OobX(new URI(attachmentUrl)); - msg.addExtension(oob); - } catch (URISyntaxException e) { - logger.warn("uri exception", e); - } - } - - for (User user : users) { - for (String jid : userService.getJIDsbyUID(user.getUid())) { - msg.setTo(Jid.of(jid)); - router.send(ClientMessage.from(msg)); - } - } - } - - @Override - public void processPingEvent(PingEvent pingEvent) { - userService.getJIDsbyUID(pingEvent.getPinger().getUid()) - .forEach(userJid -> { - Presence p = new Presence(Jid.of(userJid)); - p.setFrom(jid); - p.setPriority((byte) 10); - router.send(ClientPresence.from(p)); - }); - } - - @Override - public void processMessageReadEvent(MessageReadEvent messageReadEvent) { - - } - - @Override - public void processTopEvent(TopEvent topEvent) { - com.juick.Message message = topEvent.getMessage(); - try { - commandsManager.processCommand(serviceUser, String.format("! #%d", message.getMid()), URI.create(StringUtils.EMPTY)); - } catch (Exception e) { - logger.warn("XMPP error", e); - } - } - - private void incomingPresence(Presence p) { - final String username = p.getTo().getLocal(); - final boolean toJuick = username.equals(jid.getLocal()); - - if (p.getType() == null) { - Presence reply = new Presence(); - reply.setFrom(p.getTo().asBareJid()); - reply.setTo(p.getFrom().asBareJid()); - reply.setType(Presence.Type.UNSUBSCRIBE); - router.send(ClientPresence.from(reply)); - } else if (p.getType().equals(Presence.Type.PROBE)) { - int uid_to = 0; - if (!toJuick) { - uid_to = userService.getUIDbyName(username); - } else { - User visitor = userService.getUserByJID(p.getFrom().asBareJid().toEscapedString()); - if (visitor != null) { - userService.updateLastSeen(visitor); - } - } - - if (toJuick || uid_to > 0) { - Presence reply = new Presence(); - reply.setFrom(p.getTo().withResource(jid.getResource())); - reply.setTo(p.getFrom()); - reply.setPriority((byte)10); - if (!userService.getActiveJIDs().contains(p.getFrom().asBareJid().toEscapedString())) { - reply.setStatus("Send ON to enable notifications"); - } - router.send(ClientPresence.from(reply)); - } else { - Presence reply = new Presence(); - reply.setFrom(p.getTo()); - reply.setTo(p.getFrom()); - reply.setType(Presence.Type.ERROR); - reply.setId(p.getId()); - reply.setError(new StanzaError(StanzaError.Type.CANCEL, Condition.ITEM_NOT_FOUND)); - router.send(ClientPresence.from(reply)); - } - } else if (p.getType().equals(Presence.Type.SUBSCRIBE)) { - boolean canSubscribe = false; - if (toJuick) { - canSubscribe = true; - } else { - int uid_to = userService.getUIDbyName(username); - if (uid_to > 0) { - pmQueriesService.addPMinRoster(uid_to, p.getFrom().asBareJid().toEscapedString()); - canSubscribe = true; - } - } - if (canSubscribe) { - Presence reply = new Presence(); - reply.setFrom(p.getTo()); - reply.setTo(p.getFrom()); - reply.setType(Presence.Type.SUBSCRIBED); - router.send(ClientPresence.from(reply)); - - reply.setFrom(reply.getFrom().withResource(jid.getResource())); - reply.setPriority((byte) 10); - reply.setType(null); - router.send(ClientPresence.from(reply)); - } else { - Presence reply = new Presence(); - reply.setFrom(p.getTo()); - reply.setTo(p.getFrom()); - reply.setType(Presence.Type.ERROR); - reply.setId(p.getId()); - reply.setError(new StanzaError(StanzaError.Type.CANCEL, Condition.ITEM_NOT_FOUND)); - router.send(ClientPresence.from(reply)); - } - } else if (p.getType().equals(Presence.Type.UNSUBSCRIBE)) { - if (!toJuick) { - int uid_to = userService.getUIDbyName(username); - if (uid_to > 0) { - pmQueriesService.removePMinRoster(uid_to, p.getFrom().asBareJid().toEscapedString()); - } - } - - Presence reply = new Presence(); - reply.setFrom(p.getTo()); - reply.setTo(p.getFrom()); - reply.setType(Presence.Type.UNSUBSCRIBED); - router.send(ClientPresence.from(reply)); - } - } - - public ClientMessage incomingMessage(Message msg) { - ClientMessage result = null; - if (msg.getType() != null && msg.getType().equals(Message.Type.ERROR)) { - StanzaError error = msg.getError(); - if (error != null && error.getCondition().equals(Condition.RESOURCE_CONSTRAINT)) { - // offline query is full, deactivating this jid - if (userService.setActiveStatusForJID(msg.getFrom().toEscapedString(), UserService.ActiveStatus.Inactive)) { - logger.info("{} is inactive now", msg.getFrom()); - return null; - } - } - return null; - } - Jid to = msg.getTo(); - if (to.getDomain().equals(router.getDomain().toEscapedString()) || to.equals(this.jid)) { - User user_from = userService.getUserByJID(msg.getFrom().asBareJid().toEscapedString()); - if ((user_from == null || user_from.isAnonymous()) && !msg.getFrom().equals(jid)) { - String signuphash = userService.getSignUpHashByJID(msg.getFrom().asBareJid().toEscapedString()); - return makeReply(msg.getFrom(), "Для того, чтобы начать пользоваться сервисом, пожалуйста пройдите быструю регистрацию: http://juick.com/signup?type=xmpp&hash=" + signuphash + "\nЕсли у вас уже есть учетная запись на Juick, вы сможете присоединить этот JabberID к ней.\n\nTo start using Juick, please sign up: http://juick.com/signup?type=xmpp&hash=" + signuphash + "\nIf you already have an account on Juick, you will be proposed to attach this JabberID to your existing account."); - } - URI attachment = URI.create(StringUtils.EMPTY); - OobX oobX = msg.getExtension(OobX.class); - if (oobX != null) { - attachment = oobX.getUri(); - } - try { - return incomingMessageJuick(user_from, msg.getFrom(), msg.getTo().getLocal(), StringUtils.defaultString(msg.getBody()).trim(), attachment); - } catch (Exception e1) { - logger.warn("message exception", e1); - } - } else if (to.getDomain().endsWith(jid.getDomain()) && (to.getDomain().equals(jid.getDomain()) - || to.getDomain().endsWith("." + jid.getDomain()))) { - if (logger.isInfoEnabled()) { - try { - logger.info("unhandled message: {}", stanzaToString(msg)); - } catch (JAXBException | XMLStreamException ex) { - logger.error("JAXB exception", ex); - } - } - } else { - return ClientMessage.from(msg); - } - return result; - } - private ClientMessage incomingMessageJuick(User user_from, Jid from, String to, String command, @Nonnull URI attachment) { - if (StringUtils.isBlank(command) && attachment.toString().isEmpty()) { - return null; - } - - messagesService.getUnread(user_from).forEach(mid -> messagesService.setRead(user_from, mid)); - - int commandlen = command.length(); - - // COMPATIBILITY - if (commandlen > 7 && command.substring(0, 3).equalsIgnoreCase("PM ")) { - command = command.substring(3); - } - - if (!jid.getLocal().equals(to)) { - // PM - if (!StringUtils.isEmpty(command)) { - commandsManager.commandPM(user_from, null, to, command); - return null; - } - } - - try { - CommandResult result = commandsManager.processCommand(user_from, command, attachment); - if (StringUtils.isNotBlank(result.getText())) { - return makeReply(from, result.getText()); - } - } catch (Exception e) { - logger.warn("xmpp command exception", e); - return makeReply(from, "Error processing command"); - } - return null; - } - - private void broadcastPresence(Presence.Type type) { - Presence presence = new Presence(); - presence.setFrom(jid); - if (type != null) { - presence.setType(type); - } - userService.getActiveJIDs().forEach(j -> { - try { - presence.setTo(Jid.of(j)); - router.send(ClientPresence.from(presence)); - } catch (IllegalArgumentException ex) { - logger.warn("Invalid jid: {}", j, ex); - } - }); - } - - @PreDestroy - public void close() throws Exception { - broadcastPresence(Presence.Type.UNAVAILABLE); - if (router != null) { - router.close(); - } - } - - public ExternalComponent getRouter() { - return router; - } -} diff --git a/src/main/java/com/juick/server/XMPPManager.java b/src/main/java/com/juick/server/XMPPManager.java new file mode 100644 index 00000000..fb8f9711 --- /dev/null +++ b/src/main/java/com/juick/server/XMPPManager.java @@ -0,0 +1,678 @@ +/* + * Copyright (C) 2008-2017, Juick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.juick.server; + +import com.juick.User; +import com.juick.formatters.PlainTextFormatter; +import com.juick.server.www.WebApp; +import com.juick.service.component.*; +import com.juick.model.CommandResult; +import com.juick.server.xmpp.iq.MessageQuery; +import com.juick.service.*; +import com.juick.util.MessageUtils; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import rocks.xmpp.addr.Jid; +import rocks.xmpp.core.XmppException; +import rocks.xmpp.core.session.Extension; +import rocks.xmpp.core.session.XmppSession; +import rocks.xmpp.core.session.XmppSessionConfiguration; +import rocks.xmpp.core.session.debug.LogbackDebugger; +import rocks.xmpp.core.stanza.AbstractIQHandler; +import rocks.xmpp.core.stanza.model.*; +import rocks.xmpp.core.stanza.model.client.ClientMessage; +import rocks.xmpp.core.stanza.model.client.ClientPresence; +import rocks.xmpp.core.stanza.model.errors.Condition; +import rocks.xmpp.extensions.caps.EntityCapabilitiesManager; +import rocks.xmpp.extensions.component.accept.ExternalComponent; +import rocks.xmpp.extensions.disco.ServiceDiscoveryManager; +import rocks.xmpp.extensions.disco.model.info.Identity; +import rocks.xmpp.extensions.filetransfer.FileTransfer; +import rocks.xmpp.extensions.filetransfer.FileTransferManager; +import rocks.xmpp.extensions.nick.model.Nickname; +import rocks.xmpp.extensions.oob.model.x.OobX; +import rocks.xmpp.extensions.ping.PingManager; +import rocks.xmpp.extensions.receipts.MessageDeliveryReceiptsManager; +import rocks.xmpp.extensions.vcard.temp.model.VCard; +import rocks.xmpp.extensions.version.SoftwareVersionManager; +import rocks.xmpp.extensions.version.model.SoftwareVersion; +import rocks.xmpp.util.XmppUtils; + +import javax.annotation.Nonnull; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; +import javax.xml.bind.JAXBException; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import java.io.IOException; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Duration; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; + +/** + * @author ugnich + */ +public class XMPPManager implements NotificationListener { + + private static final Logger logger = LoggerFactory.getLogger("com.juick.server.xmpp"); + + private ExternalComponent router; + @Inject + private CommandsManager commandsManager; + @Value("${xmppbot_jid:juick@localhost}") + private Jid jid; + @Value("${hostname:localhost}") + private String componentName; + @Value("${component_port:5347}") + private int componentPort; + @Value("${component_host:localhost}") + private String componentHost; + @Value("${xmpp_password:secret}") + private String password; + @Value("${upload_tmp_dir:#{systemEnvironment['TEMP'] ?: '/tmp'}}") + private String tmpDir; + + @Inject + private MessagesService messagesService; + @Inject + private UserService userService; + @Inject + private PMQueriesService pmQueriesService; + @Inject + private ExecutorService executorService; + @Value("${service_user:juick}") + private String serviceUsername; + @Inject + private WebApp webApp; + + private User serviceUser; + + @PostConstruct + public void init() { + logger.info("stream router start connecting to {}", componentPort); + XmppSessionConfiguration configuration = XmppSessionConfiguration.builder() + .extensions(Extension.of(com.juick.Message.class), Extension.of(MessageQuery.class)) + .debugger(LogbackDebugger.class) + .defaultResponseTimeout(Duration.ofMillis(120000)) + .build(); + router = ExternalComponent.create(componentName, password, configuration, componentHost, componentPort); + ServiceDiscoveryManager serviceDiscoveryManager = router.getManager(ServiceDiscoveryManager.class); + serviceDiscoveryManager.addIdentity(Identity.clientBot().withName("Juick")); + EntityCapabilitiesManager entityCapabilitiesManager = router.getManager(EntityCapabilitiesManager.class); + entityCapabilitiesManager.setNode("https://juick.com/caps"); + MessageDeliveryReceiptsManager messageDeliveryReceiptsManager = router.getManager(MessageDeliveryReceiptsManager.class); + messageDeliveryReceiptsManager.setEnabled(true); + PingManager pingManager = router.getManager(PingManager.class); + pingManager.setEnabled(true); + SoftwareVersionManager softwareVersionManager = router.getManager(SoftwareVersionManager.class); + softwareVersionManager.setSoftwareVersion(new SoftwareVersion("Juick", "2.x", + System.getProperty("os.name", "generic"))); + VCard vCard = new VCard(); + vCard.setFormattedName("Juick"); + vCard.setBirthday(LocalDate.of(2008, 10, 22)); + try { + vCard.setUrl(new URL("http://juick.com/")); + vCard.setPhoto(new VCard.Image("image/png", IOUtils.toByteArray( + getClass().getClassLoader().getResource("juick.png")))); + } catch (MalformedURLException e) { + logger.error("invalid url", e); + } catch (IOException e) { + logger.warn("invalid resource", e); + } + router.addIQHandler(MessageQuery.class, iq -> { + Message warningMessage = new Message(iq.getFrom(), Message.Type.CHAT); + warningMessage.setFrom(jid); + warningMessage.setBody("Your XMPP client constantly polls us with XMPP query which is unsupported for years, please find http://juick.com/query#messages in your client code and remove that"); + router.send(warningMessage); + return iq.createError(new StanzaError(Condition.BAD_REQUEST, "Please stop this spam")); + }); + router.addIQHandler(VCard.class, new AbstractIQHandler(IQ.Type.GET) { + @Override + protected IQ processRequest(IQ iq) { + if (iq.getTo().equals(jid) || iq.getTo().asBareJid().equals(jid.asBareJid()) + || iq.getTo().asBareJid().toEscapedString().equals(jid.getDomain())) { + return iq.createResult(vCard); + } + User user = userService.getUserByName(iq.getTo().getLocal()); + if (!user.isAnonymous()) { + User info = userService.getUserInfo(user); + VCard userVCard = new VCard(); + userVCard.setFormattedName(info.getFullName()); + userVCard.setNickname(user.getName()); + try { + userVCard.setPhoto(new VCard.Image(URI.create(webApp.getAvatarUrl(user)))); + if (info.getUrl() != null) { + userVCard.setUrl(new URL(info.getUrl())); + } + } catch (MalformedURLException e) { + logger.warn("url exception", e); + } + return iq.createResult(userVCard); + } + return iq.createError(Condition.BAD_REQUEST); + } + }); + router.addInboundMessageListener(e -> { + ClientMessage result = incomingMessage(e.getMessage()); + if (result != null) { + router.send(result); + } + }); + router.addInboundIQListener(e -> { + IQ iq = e.getIQ(); + Jid jid = iq.getTo(); + if (!jid.getDomain().equals(this.jid.getDomain())) { + router.send(iq); + } + }); + FileTransferManager fileTransferManager = router.getManager(FileTransferManager.class); + fileTransferManager.addFileTransferOfferListener(e -> { + try { + List allowedTypes = new ArrayList() {{ + add("png"); + add("jpg"); + }}; + String attachmentExtension = FilenameUtils.getExtension(e.getName()).toLowerCase(); + String targetFilename = String.format("%s.%s", + DigestUtils.md5Hex(String.format("%s-%s", + e.getInitiator().toString(), e.getSessionId()).getBytes()), attachmentExtension); + if (allowedTypes.contains(attachmentExtension)) { + Path filePath = Paths.get(tmpDir, targetFilename); + FileTransfer ft = e.accept(filePath).get(); + ft.addFileTransferStatusListener(st -> { + logger.debug("{}: received {} of {}", e.getName(), st.getBytesTransferred(), e.getSize()); + if (st.getStatus().equals(FileTransfer.Status.COMPLETED)) { + logger.info("transfer completed"); + try { + Jid initiator = e.getInitiator(); + ClientMessage result = incomingMessageJuick( + userService.getUserByJID(initiator.asBareJid().toEscapedString()), initiator, + jid.getLocal(), StringUtils.defaultString(e.getDescription()).trim(), URI.create(String.format("juick://%s", targetFilename))); + if (result != null) { + router.send(result); + } + } catch (Exception e1) { + logger.error("ft error", e1); + } + + } else if (st.getStatus().equals(FileTransfer.Status.FAILED)) { + logger.info("transfer failed", ft.getException()); + Message msg = new Message(); + msg.setType(Message.Type.CHAT); + msg.setFrom(jid); + msg.setTo(e.getInitiator()); + msg.setBody("File transfer failed, please report to us"); + router.sendMessage(msg); + } else if (st.getStatus().equals(FileTransfer.Status.CANCELED)) { + logger.info("transfer cancelled"); + } + }); + ft.transfer(); + logger.info("transfer started"); + } else { + e.reject(); + logger.info("transfer rejected"); + } + } catch (IOException | InterruptedException | ExecutionException e1) { + logger.error("ft error", e1); + } + }); + router.addConnectionListener(event -> { + if (event.getType().equals(rocks.xmpp.core.session.ConnectionEvent.Type.RECONNECTION_SUCCEEDED)) { + logger.info("component connected"); + } + }); + router.addSessionStatusListener(event -> { + logger.info("event: " + event.getStatus(), event.getThrowable()); + if (event.getStatus().equals(XmppSession.Status.AUTHENTICATED)) { + logger.info("Authenticated, broadcasting..."); + broadcastPresence(null); + } + }); + router.addInboundPresenceListener(event -> { + incomingPresence(event.getPresence()); + }); + executorService.submit(() -> { + try { + router.connect(); + } catch (XmppException e) { + logger.warn("xmpp exception", e); + } + }); + serviceUser = userService.getUserByName(serviceUsername); + } + + private String stanzaToString(Stanza stanza) throws XMLStreamException, JAXBException { + StringWriter stanzaWriter = new StringWriter(); + XMLStreamWriter xmppStreamWriter = XmppUtils.createXmppStreamWriter( + router.getConfiguration().getXmlOutputFactory().createXMLStreamWriter(stanzaWriter)); + router.createMarshaller().marshal(stanza, xmppStreamWriter); + xmppStreamWriter.flush(); + xmppStreamWriter.close(); + return stanzaWriter.toString(); + } + + private void sendJuickMessage(com.juick.Message jmsg, List users) { + List jids = new ArrayList<>(); + + for (User user : users) { + jids.addAll(userService.getJIDsbyUID(user.getUid())); + } + com.juick.Message fullMsg = messagesService.getMessage(jmsg.getMid()).orElseThrow(IllegalStateException::new); + String txt = "@" + jmsg.getUser().getName() + ":" + MessageUtils.getTagsString(fullMsg) + "\n"; + String attachmentUrl = MessageUtils.attachmentUrl(fullMsg); + if (StringUtils.isNotEmpty(attachmentUrl)) { + txt += attachmentUrl + "\n"; + } + txt += StringUtils.defaultString(jmsg.getText()) + "\n\n"; + txt += "#" + jmsg.getMid() + " http://juick.com/m/" + jmsg.getMid(); + + Nickname nick = new Nickname("@" + jmsg.getUser().getName()); + + Message msg = new Message(); + msg.setFrom(jid); + msg.setBody(txt); + msg.setType(Message.Type.CHAT); + msg.setThread("juick-" + jmsg.getMid()); + msg.addExtension(jmsg); + msg.addExtension(nick); + if (StringUtils.isNotEmpty(attachmentUrl)) { + try { + OobX oob = new OobX(new URI(attachmentUrl)); + msg.addExtension(oob); + } catch (URISyntaxException e) { + logger.warn("uri exception", e); + } + } + for (String jid : jids) { + msg.setTo(Jid.of(jid)); + router.send(ClientMessage.from(msg)); + } + } + + public void sendJuickComment(com.juick.Message jmsg, List users) { + String replyQuote; + String replyTo; + + com.juick.Message replyMessage = jmsg.getReplyto() > 0 ? messagesService.getReply(jmsg.getMid(), jmsg.getReplyto()) + : messagesService.getMessage(jmsg.getMid()).orElseThrow(IllegalStateException::new); + replyTo = replyMessage.getUser().getName(); + com.juick.Message fullReply = messagesService.getReply(jmsg.getMid(), jmsg.getRid()); + replyQuote = StringUtils.defaultString(fullReply.getReplyQuote()); + + String txt = "Reply by @" + jmsg.getUser().getName() + ":\n" + replyQuote + "\n@" + replyTo + " "; + String attachmentUrl = MessageUtils.attachmentUrl(fullReply); + if (StringUtils.isNotEmpty(attachmentUrl)) { + txt += attachmentUrl + "\n"; + } + txt += StringUtils.defaultString(jmsg.getText()) + "\n\n" + "#" + jmsg.getMid() + "/" + jmsg.getRid() + " http://juick.com/m/" + jmsg.getMid() + "#" + jmsg.getRid(); + + Message msg = new Message(); + msg.setFrom(jid); + msg.setBody(txt); + msg.setType(Message.Type.CHAT); + msg.addExtension(jmsg); + for (User user : users) { + for (String jid : userService.getJIDsbyUID(user.getUid())) { + msg.setTo(Jid.of(jid)); + router.send(ClientMessage.from(msg)); + } + } + } + + @Override + public void processMessageEvent(MessageEvent event) { + com.juick.Message msg = event.getMessage(); + List subscribers = event.getUsers(); + if (msg.isService()) { + return; + } + if (MessageUtils.isPM(msg)) { + userService.getJIDsbyUID(msg.getTo().getUid()) + .forEach(userJid -> { + Message mm = new Message(); + mm.setTo(Jid.of(userJid)); + mm.setType(Message.Type.CHAT); + boolean inroster = pmQueriesService.havePMinRoster(msg.getUser().getUid(), userJid); + if (inroster) { + mm.setFrom(Jid.of(msg.getUser().getName(), "juick.com", "Juick")); + mm.setBody(msg.getText()); + } else { + mm.setFrom(jid); + mm.setBody("Private message from @" + msg.getUser().getName() + ":\n" + msg.getText()); + } + router.send(ClientMessage.from(mm)); + }); + } else if (MessageUtils.isReply(msg)) { + sendJuickComment(msg, subscribers); + } + else { + sendJuickMessage(msg, subscribers); + } + } + + private ClientMessage makeReply(Jid jidTo, String txt) { + Message reply = new Message(); + reply.setFrom(jid); + reply.setTo(jidTo); + reply.setType(Message.Type.CHAT); + reply.setBody(txt); + return ClientMessage.from(reply); + } + + @Override + public void processSubscribeEvent(SubscribeEvent subscribeEvent) { + + } + + @Override + public void processLikeEvent(LikeEvent likeEvent) { + List users = likeEvent.getSubscribers(); + com.juick.Message jmsg = likeEvent.getMessage(); + User liker = likeEvent.getUser(); + + if (!userService.isInBLAny(jmsg.getUser().getUid(), liker.getUid())) { + userService.getJIDsbyUID(jmsg.getUser().getUid()).forEach(authorJid -> { + Message xmppMessage = new Message(); + xmppMessage.setFrom(jid); + xmppMessage.setTo(Jid.of(authorJid)); + xmppMessage.setType(Message.Type.CHAT); + xmppMessage.addExtension(jmsg); + xmppMessage.setBody(String.format("%s recommended your post #%d. %s", + liker.getName(), jmsg.getMid(), PlainTextFormatter.formatUrl(jmsg))); + router.send(ClientMessage.from(xmppMessage)); + }); + } + + String txt = "Recommended by @" + liker.getName() + ":\n"; + txt += "@" + jmsg.getUser().getName() + ":" + MessageUtils.getTagsString(jmsg) + "\n"; + String attachmentUrl = MessageUtils.attachmentUrl(jmsg); + if (StringUtils.isNotEmpty(attachmentUrl)) { + txt += attachmentUrl + "\n"; + } + txt += StringUtils.defaultString(jmsg.getText()) + "\n\n"; + txt += "#" + jmsg.getMid(); + if (jmsg.getReplies() > 0) { + if (jmsg.getReplies() % 10 == 1 && jmsg.getReplies() % 100 != 11) { + txt += " (" + jmsg.getReplies() + " reply)"; + } else { + txt += " (" + jmsg.getReplies() + " replies)"; + } + } + txt += " http://juick.com/m/" + jmsg.getMid(); + + Nickname nick = new Nickname("@" + jmsg.getUser().getName()); + + Message msg = new Message(); + msg.setFrom(jid); + msg.setBody(txt); + msg.setType(Message.Type.CHAT); + msg.setThread("juick-" + jmsg.getMid()); + msg.addExtension(jmsg); + msg.addExtension(nick); + if (StringUtils.isNotEmpty(attachmentUrl)) { + try { + OobX oob = new OobX(new URI(attachmentUrl)); + msg.addExtension(oob); + } catch (URISyntaxException e) { + logger.warn("uri exception", e); + } + } + + for (User user : users) { + for (String jid : userService.getJIDsbyUID(user.getUid())) { + msg.setTo(Jid.of(jid)); + router.send(ClientMessage.from(msg)); + } + } + } + + @Override + public void processPingEvent(PingEvent pingEvent) { + userService.getJIDsbyUID(pingEvent.getPinger().getUid()) + .forEach(userJid -> { + Presence p = new Presence(Jid.of(userJid)); + p.setFrom(jid); + p.setPriority((byte) 10); + router.send(ClientPresence.from(p)); + }); + } + + @Override + public void processMessageReadEvent(MessageReadEvent messageReadEvent) { + + } + + @Override + public void processTopEvent(TopEvent topEvent) { + com.juick.Message message = topEvent.getMessage(); + try { + commandsManager.processCommand(serviceUser, String.format("! #%d", message.getMid()), URI.create(StringUtils.EMPTY)); + } catch (Exception e) { + logger.warn("XMPP error", e); + } + } + + private void incomingPresence(Presence p) { + final String username = p.getTo().getLocal(); + final boolean toJuick = username.equals(jid.getLocal()); + + if (p.getType() == null) { + Presence reply = new Presence(); + reply.setFrom(p.getTo().asBareJid()); + reply.setTo(p.getFrom().asBareJid()); + reply.setType(Presence.Type.UNSUBSCRIBE); + router.send(ClientPresence.from(reply)); + } else if (p.getType().equals(Presence.Type.PROBE)) { + int uid_to = 0; + if (!toJuick) { + uid_to = userService.getUIDbyName(username); + } else { + User visitor = userService.getUserByJID(p.getFrom().asBareJid().toEscapedString()); + if (visitor != null) { + userService.updateLastSeen(visitor); + } + } + + if (toJuick || uid_to > 0) { + Presence reply = new Presence(); + reply.setFrom(p.getTo().withResource(jid.getResource())); + reply.setTo(p.getFrom()); + reply.setPriority((byte)10); + if (!userService.getActiveJIDs().contains(p.getFrom().asBareJid().toEscapedString())) { + reply.setStatus("Send ON to enable notifications"); + } + router.send(ClientPresence.from(reply)); + } else { + Presence reply = new Presence(); + reply.setFrom(p.getTo()); + reply.setTo(p.getFrom()); + reply.setType(Presence.Type.ERROR); + reply.setId(p.getId()); + reply.setError(new StanzaError(StanzaError.Type.CANCEL, Condition.ITEM_NOT_FOUND)); + router.send(ClientPresence.from(reply)); + } + } else if (p.getType().equals(Presence.Type.SUBSCRIBE)) { + boolean canSubscribe = false; + if (toJuick) { + canSubscribe = true; + } else { + int uid_to = userService.getUIDbyName(username); + if (uid_to > 0) { + pmQueriesService.addPMinRoster(uid_to, p.getFrom().asBareJid().toEscapedString()); + canSubscribe = true; + } + } + if (canSubscribe) { + Presence reply = new Presence(); + reply.setFrom(p.getTo()); + reply.setTo(p.getFrom()); + reply.setType(Presence.Type.SUBSCRIBED); + router.send(ClientPresence.from(reply)); + + reply.setFrom(reply.getFrom().withResource(jid.getResource())); + reply.setPriority((byte) 10); + reply.setType(null); + router.send(ClientPresence.from(reply)); + } else { + Presence reply = new Presence(); + reply.setFrom(p.getTo()); + reply.setTo(p.getFrom()); + reply.setType(Presence.Type.ERROR); + reply.setId(p.getId()); + reply.setError(new StanzaError(StanzaError.Type.CANCEL, Condition.ITEM_NOT_FOUND)); + router.send(ClientPresence.from(reply)); + } + } else if (p.getType().equals(Presence.Type.UNSUBSCRIBE)) { + if (!toJuick) { + int uid_to = userService.getUIDbyName(username); + if (uid_to > 0) { + pmQueriesService.removePMinRoster(uid_to, p.getFrom().asBareJid().toEscapedString()); + } + } + + Presence reply = new Presence(); + reply.setFrom(p.getTo()); + reply.setTo(p.getFrom()); + reply.setType(Presence.Type.UNSUBSCRIBED); + router.send(ClientPresence.from(reply)); + } + } + + public ClientMessage incomingMessage(Message msg) { + ClientMessage result = null; + if (msg.getType() != null && msg.getType().equals(Message.Type.ERROR)) { + StanzaError error = msg.getError(); + if (error != null && error.getCondition().equals(Condition.RESOURCE_CONSTRAINT)) { + // offline query is full, deactivating this jid + if (userService.setActiveStatusForJID(msg.getFrom().toEscapedString(), UserService.ActiveStatus.Inactive)) { + logger.info("{} is inactive now", msg.getFrom()); + return null; + } + } + return null; + } + Jid to = msg.getTo(); + if (to.getDomain().equals(router.getDomain().toEscapedString()) || to.equals(this.jid)) { + User user_from = userService.getUserByJID(msg.getFrom().asBareJid().toEscapedString()); + if ((user_from == null || user_from.isAnonymous()) && !msg.getFrom().equals(jid)) { + String signuphash = userService.getSignUpHashByJID(msg.getFrom().asBareJid().toEscapedString()); + return makeReply(msg.getFrom(), "Для того, чтобы начать пользоваться сервисом, пожалуйста пройдите быструю регистрацию: http://juick.com/signup?type=xmpp&hash=" + signuphash + "\nЕсли у вас уже есть учетная запись на Juick, вы сможете присоединить этот JabberID к ней.\n\nTo start using Juick, please sign up: http://juick.com/signup?type=xmpp&hash=" + signuphash + "\nIf you already have an account on Juick, you will be proposed to attach this JabberID to your existing account."); + } + URI attachment = URI.create(StringUtils.EMPTY); + OobX oobX = msg.getExtension(OobX.class); + if (oobX != null) { + attachment = oobX.getUri(); + } + try { + return incomingMessageJuick(user_from, msg.getFrom(), msg.getTo().getLocal(), StringUtils.defaultString(msg.getBody()).trim(), attachment); + } catch (Exception e1) { + logger.warn("message exception", e1); + } + } else if (to.getDomain().endsWith(jid.getDomain()) && (to.getDomain().equals(jid.getDomain()) + || to.getDomain().endsWith("." + jid.getDomain()))) { + if (logger.isInfoEnabled()) { + try { + logger.info("unhandled message: {}", stanzaToString(msg)); + } catch (JAXBException | XMLStreamException ex) { + logger.error("JAXB exception", ex); + } + } + } else { + return ClientMessage.from(msg); + } + return result; + } + private ClientMessage incomingMessageJuick(User user_from, Jid from, String to, String command, @Nonnull URI attachment) { + if (StringUtils.isBlank(command) && attachment.toString().isEmpty()) { + return null; + } + + messagesService.getUnread(user_from).forEach(mid -> messagesService.setRead(user_from, mid)); + + int commandlen = command.length(); + + // COMPATIBILITY + if (commandlen > 7 && command.substring(0, 3).equalsIgnoreCase("PM ")) { + command = command.substring(3); + } + + if (!jid.getLocal().equals(to)) { + // PM + if (!StringUtils.isEmpty(command)) { + commandsManager.commandPM(user_from, null, to, command); + return null; + } + } + + try { + CommandResult result = commandsManager.processCommand(user_from, command, attachment); + if (StringUtils.isNotBlank(result.getText())) { + return makeReply(from, result.getText()); + } + } catch (Exception e) { + logger.warn("xmpp command exception", e); + return makeReply(from, "Error processing command"); + } + return null; + } + + private void broadcastPresence(Presence.Type type) { + Presence presence = new Presence(); + presence.setFrom(jid); + if (type != null) { + presence.setType(type); + } + userService.getActiveJIDs().forEach(j -> { + try { + presence.setTo(Jid.of(j)); + router.send(ClientPresence.from(presence)); + } catch (IllegalArgumentException ex) { + logger.warn("Invalid jid: {}", j, ex); + } + }); + } + + @PreDestroy + public void close() throws Exception { + broadcastPresence(Presence.Type.UNAVAILABLE); + if (router != null) { + router.close(); + } + } + + public ExternalComponent getRouter() { + return router; + } +} diff --git a/src/main/java/com/juick/server/configuration/XMPPConfig.java b/src/main/java/com/juick/server/configuration/XMPPConfig.java index ffacf4ef..f9b6f092 100644 --- a/src/main/java/com/juick/server/configuration/XMPPConfig.java +++ b/src/main/java/com/juick/server/configuration/XMPPConfig.java @@ -1,21 +1,13 @@ package com.juick.server.configuration; -import com.juick.server.XMPPConnection; +import com.juick.server.XMPPManager; import com.juick.server.xmpp.JidConverter; -import com.juick.server.xmpp.iq.MessageQuery; -import com.juick.server.xmpp.s2s.BasicXmppSession; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.DependsOn; import org.springframework.core.convert.ConversionService; import org.springframework.format.support.DefaultFormattingConversionService; -import rocks.xmpp.core.session.Extension; -import rocks.xmpp.core.session.XmppSessionConfiguration; -import rocks.xmpp.core.session.debug.LogbackDebugger; - -import java.time.Duration; @Configuration @ConditionalOnProperty("xmppbot_jid") @@ -23,22 +15,13 @@ public class XMPPConfig { @Value("${hostname:localhost}") private String hostname; @Bean - public BasicXmppSession session() { - XmppSessionConfiguration configuration = XmppSessionConfiguration.builder() - .extensions(Extension.of(com.juick.Message.class), Extension.of(MessageQuery.class)) - .debugger(LogbackDebugger.class) - .defaultResponseTimeout(Duration.ofMillis(120000)) - .build(); - return BasicXmppSession.create(hostname, configuration); - } - @Bean public static ConversionService conversionService() { DefaultFormattingConversionService cs = new DefaultFormattingConversionService(); cs.addConverter(new JidConverter()); return cs; } @Bean - public XMPPConnection xmppConnection() { - return new XMPPConnection(); + public XMPPManager xmppConnection() { + return new XMPPManager(); } } diff --git a/src/main/java/com/juick/server/xmpp/s2s/BasicXmppSession.java b/src/main/java/com/juick/server/xmpp/s2s/BasicXmppSession.java deleted file mode 100644 index ae28f827..00000000 --- a/src/main/java/com/juick/server/xmpp/s2s/BasicXmppSession.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2008-2017, Juick - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package com.juick.server.xmpp.s2s; - -import rocks.xmpp.addr.Jid; -import rocks.xmpp.core.XmppException; -import rocks.xmpp.core.session.XmppSession; -import rocks.xmpp.core.session.XmppSessionConfiguration; -import rocks.xmpp.core.stanza.model.IQ; -import rocks.xmpp.core.stanza.model.Message; -import rocks.xmpp.core.stanza.model.Presence; -import rocks.xmpp.core.stanza.model.server.ServerIQ; -import rocks.xmpp.core.stanza.model.server.ServerMessage; -import rocks.xmpp.core.stanza.model.server.ServerPresence; -import rocks.xmpp.core.stream.model.StreamElement; - -/** - * Created by vitalyster on 06.02.2017. - */ -public class BasicXmppSession extends XmppSession { - protected BasicXmppSession(String xmppServiceDomain, XmppSessionConfiguration configuration) { - super(xmppServiceDomain, configuration); - } - - public static BasicXmppSession create(String xmppServiceDomain, XmppSessionConfiguration configuration) { - BasicXmppSession session = new BasicXmppSession(xmppServiceDomain, configuration); - notifyCreationListeners(session); - return session; - } - - @Override - public void connect(Jid from) throws XmppException { - - } - - @Override - public Jid getConnectedResource() { - return null; - } - - @Override - protected StreamElement prepareElement(StreamElement element) { - if (element instanceof Message) { - element = ServerMessage.from((Message) element); - } else if (element instanceof Presence) { - element = ServerPresence.from((Presence) element); - } else if (element instanceof IQ) { - element = ServerIQ.from((IQ) element); - } - - return element; - } -} diff --git a/src/main/java/com/juick/server/xmpp/s2s/StanzaListener.java b/src/main/java/com/juick/server/xmpp/s2s/StanzaListener.java deleted file mode 100644 index 6932298f..00000000 --- a/src/main/java/com/juick/server/xmpp/s2s/StanzaListener.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2008-2017, Juick - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package com.juick.server.xmpp.s2s; - - -import rocks.xmpp.core.stanza.model.Stanza; - -/** - * Created by vitalyster on 07.12.2016. - */ -public interface StanzaListener { - void stanzaReceived(Stanza xmlValue); -} diff --git a/src/test/java/com/juick/server/tests/ServerTests.java b/src/test/java/com/juick/server/tests/ServerTests.java index 620e210c..fa2e2ce9 100644 --- a/src/test/java/com/juick/server/tests/ServerTests.java +++ b/src/test/java/com/juick/server/tests/ServerTests.java @@ -87,9 +87,6 @@ import rocks.xmpp.addr.Jid; import rocks.xmpp.core.session.Extension; import rocks.xmpp.core.session.XmppSession; import rocks.xmpp.core.session.XmppSessionConfiguration; -import rocks.xmpp.core.stanza.model.StanzaError; -import rocks.xmpp.core.stanza.model.client.ClientMessage; -import rocks.xmpp.core.stanza.model.errors.Condition; import javax.inject.Inject; import javax.servlet.http.Cookie; @@ -101,7 +98,6 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import java.io.*; -import java.net.Socket; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; @@ -113,8 +109,6 @@ import java.sql.Timestamp; import java.time.Instant; import java.util.*; import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.StreamSupport; @@ -161,8 +155,6 @@ public class ServerTests { @Inject private CommandsManager commandsManager; @Inject - private XMPPConnection router; - @Inject private SubscriptionService subscriptionService; @Inject private PrivacyQueriesService privacyQueriesService; -- cgit v1.2.3 From 2273e26543e947bc9b227f7085187721ba2bfed4 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Mon, 31 Dec 2018 02:40:05 +0300 Subject: XMPP cleanup --- src/main/java/com/juick/server/XMPPManager.java | 32 +------------------------ 1 file changed, 1 insertion(+), 31 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/XMPPManager.java b/src/main/java/com/juick/server/XMPPManager.java index fb8f9711..9b886784 100644 --- a/src/main/java/com/juick/server/XMPPManager.java +++ b/src/main/java/com/juick/server/XMPPManager.java @@ -189,13 +189,6 @@ public class XMPPManager implements NotificationListener { router.send(result); } }); - router.addInboundIQListener(e -> { - IQ iq = e.getIQ(); - Jid jid = iq.getTo(); - if (!jid.getDomain().equals(this.jid.getDomain())) { - router.send(iq); - } - }); FileTransferManager fileTransferManager = router.getManager(FileTransferManager.class); fileTransferManager.addFileTransferOfferListener(e -> { try { @@ -273,16 +266,6 @@ public class XMPPManager implements NotificationListener { serviceUser = userService.getUserByName(serviceUsername); } - private String stanzaToString(Stanza stanza) throws XMLStreamException, JAXBException { - StringWriter stanzaWriter = new StringWriter(); - XMLStreamWriter xmppStreamWriter = XmppUtils.createXmppStreamWriter( - router.getConfiguration().getXmlOutputFactory().createXMLStreamWriter(stanzaWriter)); - router.createMarshaller().marshal(stanza, xmppStreamWriter); - xmppStreamWriter.flush(); - xmppStreamWriter.close(); - return stanzaWriter.toString(); - } - private void sendJuickMessage(com.juick.Message jmsg, List users) { List jids = new ArrayList<>(); @@ -321,7 +304,7 @@ public class XMPPManager implements NotificationListener { } } - public void sendJuickComment(com.juick.Message jmsg, List users) { + private void sendJuickComment(com.juick.Message jmsg, List users) { String replyQuote; String replyTo; @@ -600,15 +583,6 @@ public class XMPPManager implements NotificationListener { } catch (Exception e1) { logger.warn("message exception", e1); } - } else if (to.getDomain().endsWith(jid.getDomain()) && (to.getDomain().equals(jid.getDomain()) - || to.getDomain().endsWith("." + jid.getDomain()))) { - if (logger.isInfoEnabled()) { - try { - logger.info("unhandled message: {}", stanzaToString(msg)); - } catch (JAXBException | XMLStreamException ex) { - logger.error("JAXB exception", ex); - } - } } else { return ClientMessage.from(msg); } @@ -671,8 +645,4 @@ public class XMPPManager implements NotificationListener { router.close(); } } - - public ExternalComponent getRouter() { - return router; - } } -- cgit v1.2.3 From 9599599d665e65f35d804eade620cf063ee723ff Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Mon, 31 Dec 2018 02:46:35 +0300 Subject: Add error payload to error messages --- src/main/java/com/juick/server/XMPPManager.java | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/XMPPManager.java b/src/main/java/com/juick/server/XMPPManager.java index 9b886784..51b3b04e 100644 --- a/src/main/java/com/juick/server/XMPPManager.java +++ b/src/main/java/com/juick/server/XMPPManager.java @@ -19,11 +19,13 @@ package com.juick.server; import com.juick.User; import com.juick.formatters.PlainTextFormatter; -import com.juick.server.www.WebApp; -import com.juick.service.component.*; import com.juick.model.CommandResult; +import com.juick.server.www.WebApp; import com.juick.server.xmpp.iq.MessageQuery; -import com.juick.service.*; +import com.juick.service.MessagesService; +import com.juick.service.PMQueriesService; +import com.juick.service.UserService; +import com.juick.service.component.*; import com.juick.util.MessageUtils; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.FilenameUtils; @@ -39,7 +41,10 @@ import rocks.xmpp.core.session.XmppSession; import rocks.xmpp.core.session.XmppSessionConfiguration; import rocks.xmpp.core.session.debug.LogbackDebugger; import rocks.xmpp.core.stanza.AbstractIQHandler; -import rocks.xmpp.core.stanza.model.*; +import rocks.xmpp.core.stanza.model.IQ; +import rocks.xmpp.core.stanza.model.Message; +import rocks.xmpp.core.stanza.model.Presence; +import rocks.xmpp.core.stanza.model.StanzaError; import rocks.xmpp.core.stanza.model.client.ClientMessage; import rocks.xmpp.core.stanza.model.client.ClientPresence; import rocks.xmpp.core.stanza.model.errors.Condition; @@ -56,17 +61,12 @@ import rocks.xmpp.extensions.receipts.MessageDeliveryReceiptsManager; import rocks.xmpp.extensions.vcard.temp.model.VCard; import rocks.xmpp.extensions.version.SoftwareVersionManager; import rocks.xmpp.extensions.version.model.SoftwareVersion; -import rocks.xmpp.util.XmppUtils; import javax.annotation.Nonnull; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.inject.Inject; -import javax.xml.bind.JAXBException; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamWriter; import java.io.IOException; -import java.io.StringWriter; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; @@ -554,7 +554,6 @@ public class XMPPManager implements NotificationListener { } public ClientMessage incomingMessage(Message msg) { - ClientMessage result = null; if (msg.getType() != null && msg.getType().equals(Message.Type.ERROR)) { StanzaError error = msg.getError(); if (error != null && error.getCondition().equals(Condition.RESOURCE_CONSTRAINT)) { @@ -583,10 +582,10 @@ public class XMPPManager implements NotificationListener { } catch (Exception e1) { logger.warn("message exception", e1); } - } else { - return ClientMessage.from(msg); } - return result; + ClientMessage errorMessage = ClientMessage.from(msg); + errorMessage.setError(new StanzaError(StanzaError.Type.CANCEL, Condition.ITEM_NOT_FOUND)); + return errorMessage; } private ClientMessage incomingMessageJuick(User user_from, Jid from, String to, String command, @Nonnull URI attachment) { if (StringUtils.isBlank(command) && attachment.toString().isEmpty()) { -- cgit v1.2.3 From 0a13451621e67675c1fc4a74923df1ebfefe3967 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Fri, 4 Jan 2019 10:53:23 +0300 Subject: catch okhttp exceptions --- src/main/java/com/juick/server/SignatureManager.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/SignatureManager.java b/src/main/java/com/juick/server/SignatureManager.java index 26e482ad..9ecdaad5 100644 --- a/src/main/java/com/juick/server/SignatureManager.java +++ b/src/main/java/com/juick/server/SignatureManager.java @@ -97,12 +97,17 @@ public class SignatureManager { throw new IOException("Person not found"); } public Optional getContext(URI contextUri) { - Context context = apClient.getForEntity(contextUri, Context.class).getBody(); - if (context == null) { - logger.warn("Cannot identify {}", contextUri); - return Optional.empty(); + try { + Context context = apClient.getForEntity(contextUri, Context.class).getBody(); + if (context == null) { + logger.warn("Cannot identify {}", contextUri); + return Optional.empty(); + } + return Optional.of(context); + } catch (Exception e) { + logger.warn("REST Exception processing {}", contextUri, e); } - return Optional.of(context); + return Optional.empty(); } public Optional discoverPerson(String acct) { Jid acctId = Jid.of(acct); -- cgit v1.2.3 From 020b4bd111d02fd5273291c85024402ae6c46ab6 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Wed, 9 Jan 2019 20:11:15 +0300 Subject: Facebook API update --- src/main/java/com/juick/model/facebook/User.java | 70 ---------------------- .../java/com/juick/server/api/ApiSocialLogin.java | 19 +++--- .../juick/server/www/controllers/SocialLogin.java | 19 +++--- .../java/com/juick/service/CrosspostService.java | 4 +- .../com/juick/service/CrosspostServiceImpl.java | 12 ++-- 5 files changed, 24 insertions(+), 100 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/model/facebook/User.java b/src/main/java/com/juick/model/facebook/User.java index 80838de6..a9288fe4 100644 --- a/src/main/java/com/juick/model/facebook/User.java +++ b/src/main/java/com/juick/model/facebook/User.java @@ -27,98 +27,28 @@ import com.fasterxml.jackson.annotation.JsonProperty; public class User { private String id; private String name; - private String link; - private boolean verified; private String firstName; private String lastName; - private String gender; - private String locale; - private String timezone; - private String updatedTime; private String email; public String getId() { return id; } - public void setId(String id) { - this.id = id; - } - public String getName() { return name; } - public void setName(String name) { - this.name = name; - } - - public String getLink() { - return link; - } - - public void setLink(String link) { - this.link = link; - } - - public boolean getVerified() { - return verified; - } - - public void setVerified(boolean verified) { - this.verified = verified; - } - @JsonProperty("first_name") public String getFirstName() { return firstName; } - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getGender() { - return gender; - } - - public void setGender(String gender) { - this.gender = gender; - } @JsonProperty("last_name") public String getLastName() { return lastName; } - public void setLastName(String lastName) { - this.lastName = lastName; - } - - public String getLocale() { - return locale; - } - - public void setLocale(String locale) { - this.locale = locale; - } - - public String getTimezone() { - return timezone; - } - - public void setTimezone(String timezone) { - this.timezone = timezone; - } - - @JsonProperty("updated_time") - public String getUpdatedTime() { - return updatedTime; - } - - public void setUpdatedTime(String updatedTime) { - this.updatedTime = updatedTime; - } - public String getEmail() { return email; } diff --git a/src/main/java/com/juick/server/api/ApiSocialLogin.java b/src/main/java/com/juick/server/api/ApiSocialLogin.java index 72cda0af..75fd6d11 100644 --- a/src/main/java/com/juick/server/api/ApiSocialLogin.java +++ b/src/main/java/com/juick/server/api/ApiSocialLogin.java @@ -148,7 +148,7 @@ public class ApiSocialLogin { .state(state) .build(FacebookApi.instance()); OAuth2AccessToken token = facebookService.getAccessToken(code); - final OAuthRequest meRequest = new OAuthRequest(Verb.GET, "https://graph.facebook.com/v2.10/me?fields=id,name,link,verified,email"); + final OAuthRequest meRequest = new OAuthRequest(Verb.GET, "https://graph.facebook.com/v3.2/me?fields=id,name,email"); facebookService.signRequest(token, meRequest); String graph = facebookService.execute(meRequest).getBody(); if (StringUtils.isBlank(graph)) { @@ -157,36 +157,33 @@ public class ApiSocialLogin { } User fb = jsonMapper.readValue(graph, User.class); long fbID = NumberUtils.toLong(fb.getId(), 0); - if (fbID == 0 || StringUtils.isBlank(fb.getName()) || StringUtils.isBlank(fb.getLink())) { - logger.error("Missing required fields, id: {}, name: {}, link: {}", fbID, fb.getName(), fb.getLink()); + if (fbID == 0 || StringUtils.isBlank(fb.getName())) { + logger.error("Missing required fields, id: {}, name: {}", fbID, fb.getName()); throw new HttpBadRequestException(); } int uid = crosspostService.getUIDbyFBID(fbID); if (uid > 0) { - if (!crosspostService.updateFacebookUser(fbID, token.getAccessToken(), fb.getName(), fb.getLink())) { + if (!crosspostService.updateFacebookUser(fbID, token.getAccessToken(), fb.getName())) { logger.error("error updating facebook user, id: {}, token: {}", fbID, token.getAccessToken()); throw new HttpBadRequestException(); } UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(redirectUrl); uriComponentsBuilder.queryParam("hash", userService.getHashByUID(uid)); return "redirect:" + uriComponentsBuilder.build().toUriString(); - } else if (fb.getVerified()) { - if (!crosspostService.createFacebookUser(fbID, state, token.getAccessToken(), fb.getName(), fb.getLink())) { + } else { + if (!crosspostService.createFacebookUser(fbID, state, token.getAccessToken(), fb.getName())) { if (StringUtils.isNotEmpty(fb.getEmail())) { - logger.info("found {} for facebook user {}", fb.getEmail(), fb.getLink()); + logger.info("found {} for facebook user {}", fb.getEmail()); Integer userId = crosspostService.getUIDbyFBID(fbID); if (!emailService.getEmails(userId, false).contains(fb.getEmail())) { emailService.addEmail(userId, fb.getEmail()); } } - logger.info("email not found for facebook user {}", fb.getLink()); + logger.info("email not found for facebook user {}", fb.getName()); throw new HttpBadRequestException(); } return "redirect:/signup?type=fb&hash=" + state; - } else { - logger.error("Facebook account is not verified, id: {}", fbID); - throw new HttpBadRequestException(); } }/* @GetMapping("/_twitter") diff --git a/src/main/java/com/juick/server/www/controllers/SocialLogin.java b/src/main/java/com/juick/server/www/controllers/SocialLogin.java index bc631a1a..b071b6ca 100644 --- a/src/main/java/com/juick/server/www/controllers/SocialLogin.java +++ b/src/main/java/com/juick/server/www/controllers/SocialLogin.java @@ -142,7 +142,7 @@ public class SocialLogin { .state(state) .build(FacebookApi.instance()); OAuth2AccessToken token = facebookService.getAccessToken(code); - final OAuthRequest meRequest = new OAuthRequest(Verb.GET, "https://graph.facebook.com/v2.10/me?fields=id,name,link,verified,email"); + final OAuthRequest meRequest = new OAuthRequest(Verb.GET, "https://graph.facebook.com/v3.2/me?fields=id,name,link,verified,email"); facebookService.signRequest(token, meRequest); String graph = facebookService.execute(meRequest).getBody(); if (StringUtils.isBlank(graph)) { @@ -151,14 +151,14 @@ public class SocialLogin { } User fb = jsonMapper.readValue(graph, User.class); long fbID = NumberUtils.toLong(fb.getId(), 0); - if (fbID == 0 || StringUtils.isBlank(fb.getName()) || StringUtils.isBlank(fb.getLink())) { - logger.error("Missing required fields, id: {}, name: {}, link: {}", fbID, fb.getName(), fb.getLink()); + if (fbID == 0 || StringUtils.isBlank(fb.getName())) { + logger.error("Missing required fields, id: {}, name: {}", fbID, fb.getName()); throw new HttpBadRequestException(); } int uid = crosspostService.getUIDbyFBID(fbID); if (uid > 0) { - if (!crosspostService.updateFacebookUser(fbID, token.getAccessToken(), fb.getName(), fb.getLink())) { + if (!crosspostService.updateFacebookUser(fbID, token.getAccessToken(), fb.getName())) { logger.error("error updating facebook user, id: {}, token: {}", fbID, token.getAccessToken()); throw new HttpBadRequestException(); } @@ -166,22 +166,19 @@ public class SocialLogin { c.setMaxAge(50 * 24 * 60 * 60); response.addCookie(c); return "redirect:" + redirectUrl; - } else if (fb.getVerified()) { - if (!crosspostService.createFacebookUser(fbID, state, token.getAccessToken(), fb.getName(), fb.getLink())) { + } else { + if (!crosspostService.createFacebookUser(fbID, state, token.getAccessToken(), fb.getName())) { if (StringUtils.isNotEmpty(fb.getEmail())) { - logger.info("found {} for facebook user {}", fb.getEmail(), fb.getLink()); + logger.info("found {} for facebook user {}", fb.getEmail()); Integer userId = crosspostService.getUIDbyFBID(fbID); if (!emailService.getEmails(userId, false).contains(fb.getEmail())) { emailService.addEmail(userId, fb.getEmail()); } } - logger.info("email not found for facebook user {}", fb.getLink()); + logger.info("email not found for facebook user {}", fb.getName()); throw new HttpBadRequestException(); } return "redirect:/signup?type=fb&hash=" + state; - } else { - logger.error("Facebook account is not verified, id: {}", fbID); - throw new HttpBadRequestException(); } } @GetMapping("/_twitter") diff --git a/src/main/java/com/juick/service/CrosspostService.java b/src/main/java/com/juick/service/CrosspostService.java index 99911250..28b9e8ab 100644 --- a/src/main/java/com/juick/service/CrosspostService.java +++ b/src/main/java/com/juick/service/CrosspostService.java @@ -60,9 +60,9 @@ public interface CrosspostService { int getUIDbyFBID(long fbID); - boolean createFacebookUser(long fbID, String loginhash, String token, String fbName, String fbLink); + boolean createFacebookUser(long fbID, String loginhash, String token, String fbName); - boolean updateFacebookUser(long fbID, String token, String fbName, String fbLink); + boolean updateFacebookUser(long fbID, String token, String fbName); int getUIDbyVKID(long vkID); diff --git a/src/main/java/com/juick/service/CrosspostServiceImpl.java b/src/main/java/com/juick/service/CrosspostServiceImpl.java index d190faba..0eeb8c78 100644 --- a/src/main/java/com/juick/service/CrosspostServiceImpl.java +++ b/src/main/java/com/juick/service/CrosspostServiceImpl.java @@ -180,16 +180,16 @@ public class CrosspostServiceImpl extends BaseJdbcService implements CrosspostSe @Transactional @Override - public boolean createFacebookUser(long fbID, String loginhash, String token, String fbName, String fbLink) { - return getJdbcTemplate().update("UPDATE facebook SET fb_id=?, access_token=?, fb_name=?, fb_link=? WHERE loginhash=?", - fbID, token, fbName, fbLink, loginhash) > 0; + public boolean createFacebookUser(long fbID, String loginhash, String token, String fbName) { + return getJdbcTemplate().update("UPDATE facebook SET fb_id=?, access_token=?, fb_name=? WHERE loginhash=?", + fbID, token, fbName, loginhash) > 0; } @Transactional @Override - public boolean updateFacebookUser(long fbID, String token, String fbName, String fbLink) { - return getJdbcTemplate().update("UPDATE facebook SET access_token=?,fb_name=?,fb_link=? WHERE fb_id=?", - token, fbName, fbLink, fbID) > 0; + public boolean updateFacebookUser(long fbID, String token, String fbName) { + return getJdbcTemplate().update("UPDATE facebook SET access_token=?,fb_name=? WHERE fb_id=?", + token, fbName, fbID) > 0; } @Transactional(readOnly = true) -- cgit v1.2.3 From 809ef60e18bb8ab7c95db93b7777f3c0ffb30872 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Thu, 20 Dec 2018 09:41:32 +0300 Subject: HTTPSignatureAuthenticationFilter --- .../java/com/juick/server/KeystoreManager.java | 9 +-- .../java/com/juick/server/SignatureManager.java | 60 +++++++++++------ .../com/juick/server/api/activity/Profile.java | 26 ++----- .../server/configuration/BaseWebConfiguration.java | 10 +++ .../juick/server/configuration/SecurityConfig.java | 5 ++ .../HTTPSignatureAuthenticationFilter.java | 68 +++++++++++++++++++ .../configuration/TestActivityConfiguration.java | 19 ++++++ .../java/com/juick/server/tests/ServerTests.java | 75 +++++++++++++++++++-- src/test/resources/mocks/activity/testfollow.json | 15 +++++ src/test/resources/mocks/activity/testuser.json | 27 ++++++++ src/test/resources/test.p12 | Bin 0 -> 2386 bytes 11 files changed, 260 insertions(+), 54 deletions(-) create mode 100644 src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java create mode 100644 src/test/java/com/juick/server/configuration/TestActivityConfiguration.java create mode 100644 src/test/resources/mocks/activity/testfollow.json create mode 100644 src/test/resources/mocks/activity/testuser.json create mode 100644 src/test/resources/test.p12 (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/KeystoreManager.java b/src/main/java/com/juick/server/KeystoreManager.java index 67a24f11..3ae7b866 100644 --- a/src/main/java/com/juick/server/KeystoreManager.java +++ b/src/main/java/com/juick/server/KeystoreManager.java @@ -19,20 +19,17 @@ import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; import java.util.stream.Collectors; -@Component public class KeystoreManager { private static final Logger logger = LoggerFactory.getLogger("com.juick.server"); - @Value("${keystore:juick.p12}") - private String keystore; - @Value("${keystore_password:secret}") + private String keystorePassword; private KeyStore ks; private KeyManagerFactory kmf; - @PostConstruct - public void init() { + public KeystoreManager(String keystore, String keystorePassword) { + this.keystorePassword = keystorePassword; try (InputStream ksIs = new FileInputStream(keystore)) { ks = KeyStore.getInstance("PKCS12"); ks.load(ksIs, keystorePassword.toCharArray()); diff --git a/src/main/java/com/juick/server/SignatureManager.java b/src/main/java/com/juick/server/SignatureManager.java index 9ecdaad5..23f5c37a 100644 --- a/src/main/java/com/juick/server/SignatureManager.java +++ b/src/main/java/com/juick/server/SignatureManager.java @@ -2,6 +2,7 @@ package com.juick.server; import com.fasterxml.jackson.databind.ObjectMapper; import com.juick.User; +import com.juick.model.AnonymousUser; import com.juick.server.api.activity.model.Context; import com.juick.server.api.activity.model.objects.Person; import com.juick.server.api.webfinger.model.Account; @@ -11,7 +12,6 @@ import com.juick.util.DateFormattersHolder; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; @@ -53,28 +53,43 @@ public class SignatureManager { URI inbox = uriComponentsBuilder.build().toUri(); Instant now = Instant.now(); String requestDate = DateFormattersHolder.getHttpDateFormatter().format(now); - Signature templateSignature = new Signature(from.getPublicKey().getId(), "rsa-sha256", null, - "(request-target)", "host", "date"); - Signer signer = new Signer(keystoreManager.getPrivateKey(), templateSignature); - Map headers = new HashMap<>(); - headers.put("host", inbox.getHost()); - headers.put("date", requestDate); - Signature signature = signer.sign("POST", inbox.getPath(), headers); + String host = inbox.getPort() > 0 ? String.format("%s:%d", inbox.getHost(), inbox.getPort()) : inbox.getHost(); + String signatureString = addSignature(from, host, "POST", inbox.getPath(), requestDate); + HttpHeaders requestHeaders = new HttpHeaders(); requestHeaders.add("Content-Type", Context.ACTIVITYSTREAMS_PROFILE_MEDIA_TYPE); requestHeaders.add("Date", requestDate); - requestHeaders.add("Signature", signature.toString().substring(10)); + requestHeaders.add("Host", host); + requestHeaders.add("Signature", signatureString); HttpEntity request = new HttpEntity<>(Context.build(data), requestHeaders); - //boolean valid = verifySignature(Signature.fromString(requestHeaders.getFirst("Signature")), - // keystoreManager.getPublicKey(), "POST", inbox.getPath(), headers); logger.info("Sending context: {}", jsonMapper.writeValueAsString(data)); logger.info("Request date: {}", requestDate); ResponseEntity response = apClient.postForEntity(inbox, request, Void.class); logger.info("accepted follower: {}", response.getStatusCodeValue()); + } + + public String addSignature(Person from, String host, String method, String path, String dateString) throws IOException { + return addSignature(from, host, method, path, dateString, keystoreManager); + } + public String addSignature(Person from, String host, String method, String path, String dateString, KeystoreManager keystoreManager) throws IOException { + Signature templateSignature = new Signature(from.getPublicKey().getId(), "rsa-sha256", null, + "(request-target)", "host", "date"); + Map headers = new HashMap<>(); + headers.put("host", host); + headers.put("date", dateString); + Signer signer = new Signer(keystoreManager.getPrivateKey(), templateSignature); + Signature signature = signer.sign(method, path, headers); + // remove "Signature: " from result + return signature.toString().substring(10); } + public User verifySignature(String method, String path, Map headers) throws IOException { - Signature signature = Signature.fromString(headers.get("signature")); + String signatureString = headers.get("signature"); + if (StringUtils.isEmpty(signatureString)) { + return AnonymousUser.INSTANCE; + } + Signature signature = Signature.fromString(signatureString); Optional context = getContext(URI.create(signature.getKeyId())); if (context.isPresent() && context.get() instanceof Person) { Person person = (Person) context.get(); @@ -84,12 +99,16 @@ public class SignatureManager { try { boolean result = verifier.verify(method, path, headers); logger.info("signature is valid: {}", result); - User user = new User(); - user.setUri(URI.create(person.getId())); - if (key.equals(keystoreManager.getPublicKey())) { - return userService.getUserByName(person.getName()); + if (result) { + User user = new User(); + user.setUri(URI.create(person.getId())); + if (key.equals(keystoreManager.getPublicKey())) { + return userService.getUserByName(person.getName()); + } + return user; + } else { + return AnonymousUser.INSTANCE; } - return user; } catch (NoSuchAlgorithmException | SignatureException | IOException e) { throw new IOException("Invalid signature"); } @@ -110,9 +129,12 @@ public class SignatureManager { return Optional.empty(); } public Optional discoverPerson(String acct) { - Jid acctId = Jid.of(acct); + String[] accountParts = acct.split(":", 2); + String account = accountParts[0]; + int port = accountParts.length > 1 ? Integer.valueOf(accountParts[1]) : 80; + Jid acctId = Jid.of(account); URI resourceUri = UriComponentsBuilder.fromUriString( - String.format("https://%s/.well-known/webfinger?resource=acct:%s", acctId.getDomain(), acct)).build().toUri(); + String.format("http://%s:%d/.well-known/webfinger?resource=acct:%s", acctId.getDomain(), port, account)).build().toUri(); Account acctData = apClient.getForEntity(resourceUri, Account.class).getBody(); if (acctData != null) { for (Link l : acctData.getLinks()) { diff --git a/src/main/java/com/juick/server/api/activity/Profile.java b/src/main/java/com/juick/server/api/activity/Profile.java index 2614cded..404f0f84 100644 --- a/src/main/java/com/juick/server/api/activity/Profile.java +++ b/src/main/java/com/juick/server/api/activity/Profile.java @@ -44,6 +44,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; @@ -252,30 +253,11 @@ public class Profile { } @PostMapping(value = "/api/inbox", consumes = {Context.LD_JSON_MEDIA_TYPE, Context.ACTIVITYSTREAMS_PROFILE_MEDIA_TYPE}) - public ResponseEntity processInbox(@RequestBody Activity activity, - @RequestHeader(name = "Host") String host, - @RequestHeader(name = "Date") String date, - @RequestHeader(name = "Digest", required = false) String digest, - @RequestHeader(name = "Content-Type") String contentType, - @RequestHeader(name = "User-Agent", required = false) String userAgent, - @RequestHeader(name = "Accept-Encoding", required = false) String acceptEncoding, - @RequestHeader(name = "Signature", required = false) String signature) throws Exception { - UriComponents componentsBuilder = ServletUriComponentsBuilder.fromCurrentRequestUri().build(); - Map headers = new HashMap<>(); - headers.put("host", host.split(":", 2)[0]); - headers.put("date", date); - headers.put("digest", digest); - headers.put("content-type", contentType); - headers.put("user-agent", userAgent); - headers.put("accept-encoding", acceptEncoding); - headers.put("signature", signature); - User signedUser = signatureManager.verifySignature( "POST", - componentsBuilder.getPath(), headers); - if ((StringUtils.isNotEmpty(signedUser.getUri().toString()) && signedUser.getUri().equals(URI.create(activity.getActor()))) || !signedUser.isAnonymous()) { + public ResponseEntity processInbox(@RequestBody Activity activity) throws Exception { + User visitor = UserUtils.getCurrentUser(); + if ((StringUtils.isNotEmpty(visitor.getUri().toString()) && visitor.getUri().equals(URI.create(activity.getActor()))) || !visitor.isAnonymous()) { if (activity instanceof Follow) { Follow followRequest = (Follow) activity; - String actor = followRequest.getActor(); - Person follower = (Person) signatureManager.getContext(URI.create(actor)).orElseThrow(HttpBadRequestException::new); applicationEventPublisher.publishEvent( new FollowEvent(this, followRequest)); return new ResponseEntity<>(HttpStatus.ACCEPTED); diff --git a/src/main/java/com/juick/server/configuration/BaseWebConfiguration.java b/src/main/java/com/juick/server/configuration/BaseWebConfiguration.java index 6a2a8142..16693995 100644 --- a/src/main/java/com/juick/server/configuration/BaseWebConfiguration.java +++ b/src/main/java/com/juick/server/configuration/BaseWebConfiguration.java @@ -17,6 +17,8 @@ package com.juick.server.configuration; +import com.juick.server.KeystoreManager; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.SchedulingConfigurer; @@ -36,6 +38,10 @@ import java.util.concurrent.Executors; @Configuration public class BaseWebConfiguration implements WebMvcConfigurer, SchedulingConfigurer { + @Value("${keystore:juick.p12}") + private String keystore; + @Value("${keystore_password:secret}") + private String keystorePassword; @Override public void configurePathMatch(PathMatchConfigurer configurer) { @@ -61,4 +67,8 @@ public class BaseWebConfiguration implements WebMvcConfigurer, SchedulingConfigu public ExecutorService executorService() { return Executors.newCachedThreadPool(); } + @Bean + public KeystoreManager keystoreManager() { + return new KeystoreManager(keystore, keystorePassword); + } } diff --git a/src/main/java/com/juick/server/configuration/SecurityConfig.java b/src/main/java/com/juick/server/configuration/SecurityConfig.java index 7145e9d5..d2d3ab13 100644 --- a/src/main/java/com/juick/server/configuration/SecurityConfig.java +++ b/src/main/java/com/juick/server/configuration/SecurityConfig.java @@ -17,7 +17,9 @@ package com.juick.server.configuration; +import com.juick.server.SignatureManager; import com.juick.service.UserService; +import com.juick.service.security.HTTPSignatureAuthenticationFilter; import com.juick.service.security.HashParamAuthenticationFilter; import com.juick.service.security.JuickUserDetailsService; import com.juick.service.security.deprecated.RequestParamHashRememberMeServices; @@ -93,6 +95,8 @@ public class SecurityConfig { private String webDomain; @Resource private UserService userService; + @Resource + private SignatureManager signatureManager; ApiConfig() { super(true); } @@ -109,6 +113,7 @@ public class SecurityConfig { protected void configure(HttpSecurity http) throws Exception { http.antMatcher("/api/**") .addFilterBefore(apiAuthenticationFilter(), BasicAuthenticationFilter.class) + .addFilterBefore(new HTTPSignatureAuthenticationFilter(signatureManager, userService), BasicAuthenticationFilter.class) .authorizeRequests() .antMatchers(HttpMethod.OPTIONS).permitAll() .antMatchers("/api/", "/api/messages", "/api/messages/discussions", "/api/users", "/api/thread", "/api/tags", "/api/tlgmbtwbhk", "/api/fbwbhk", diff --git a/src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java b/src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java new file mode 100644 index 00000000..8332fc8c --- /dev/null +++ b/src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java @@ -0,0 +1,68 @@ +package com.juick.service.security; + +import com.juick.User; +import com.juick.server.SignatureManager; +import com.juick.service.UserService; +import com.juick.service.security.entities.JuickUser; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.annotation.Nonnull; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.net.URI; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Collectors; + +public class HTTPSignatureAuthenticationFilter extends OncePerRequestFilter { + + private final SignatureManager signatureManager; + private final UserService userService; + + + public HTTPSignatureAuthenticationFilter( + final SignatureManager signatureManager, + final UserService userService) { + this.signatureManager = signatureManager; + this.userService = userService; + } + @Override + protected void doFilterInternal(@Nonnull HttpServletRequest request, @Nonnull HttpServletResponse response, @Nonnull FilterChain filterChain) throws IOException, ServletException { + if (authenticationIsRequired()) { + Map headers = Collections.list(request.getHeaderNames()) + .stream() + .collect(Collectors.toMap(String::toLowerCase, request::getHeader)); + User user = signatureManager.verifySignature(request.getMethod(), request.getRequestURI(), headers); + if (!user.isAnonymous()) { + String userUri = user.getUri().toString(); + if (userUri.length() == 0) { + User userWithPassword = userService.getUserByName(user.getName()); + userWithPassword.setAuthHash(userService.getHashByUID(userWithPassword.getUid())); + Authentication authentication = new UsernamePasswordAuthenticationToken(userWithPassword.getName(), userWithPassword.getCredentials()); + SecurityContextHolder.getContext().setAuthentication(authentication); + } else { + Authentication authentication = new AnonymousAuthenticationToken(userUri, user, Collections.singletonList(new SimpleGrantedAuthority("ROLE_ANONYMOUS"))); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } + } + + filterChain.doFilter(request, response); + } + + private boolean authenticationIsRequired() { + Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication(); + + return existingAuth == null || + !existingAuth.isAuthenticated() || + existingAuth instanceof AnonymousAuthenticationToken; + } +} diff --git a/src/test/java/com/juick/server/configuration/TestActivityConfiguration.java b/src/test/java/com/juick/server/configuration/TestActivityConfiguration.java new file mode 100644 index 00000000..5daf4900 --- /dev/null +++ b/src/test/java/com/juick/server/configuration/TestActivityConfiguration.java @@ -0,0 +1,19 @@ +package com.juick.server.configuration; + +import com.juick.server.KeystoreManager; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; + +import java.io.IOException; + +@Configuration +public class TestActivityConfiguration { + @Value("classpath:test.p12") + Resource keystoreFile; + @Bean + public KeystoreManager testKeystoreManager() throws IOException { + return new KeystoreManager(keystoreFile.getFile().getAbsolutePath(), "secret"); + } +} diff --git a/src/test/java/com/juick/server/tests/ServerTests.java b/src/test/java/com/juick/server/tests/ServerTests.java index fa2e2ce9..fedbaba0 100644 --- a/src/test/java/com/juick/server/tests/ServerTests.java +++ b/src/test/java/com/juick/server/tests/ServerTests.java @@ -65,10 +65,13 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.core.io.ClassPathResource; +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.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringRunner; +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.MockMvcRequestBuilders; @@ -76,6 +79,7 @@ import org.springframework.util.DigestUtils; import org.springframework.util.FileSystemUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; import org.w3c.dom.Document; @@ -118,6 +122,9 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; +import static org.springframework.test.web.client.ExpectedCount.times; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @@ -130,9 +137,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @TestPropertySource(properties = { "broken_ssl_hosts=localhost,serverstorageisfull.tld", "ios_app_id=12345678.com.juick.ExampleApp", - "xmppbot_jid=juick@localhost/Juick", - "hostname=localhost", - "componentname=localhost", "spring.jackson.default-property-inclusion=non_default" }) @AutoConfigureMockMvc @@ -174,10 +178,6 @@ public class ServerTests { private ServerManager serverManager; @Inject private KeystoreManager keystoreManager; - @Value("${hostname:localhost}") - private Jid jid; - @Value("${xmppbot_jid:juick@localhost}") - private Jid botJid; @Value("${upload_tmp_dir:#{systemEnvironment['TEMP'] ?: '/tmp'}}") private String tmpDir; @Value("${img_path:#{systemEnvironment['TEMP'] ?: '/tmp'}}") @@ -192,6 +192,18 @@ public class ServerTests { private ActivityPubManager activityPubManager; @Inject private WebApp webApp; + @Inject + private RestTemplate apClient; + + @Value("classpath:mocks/activity/testuser.json") + private Resource testuserResponse; + @Value("classpath:mocks/activity/testfollow.json") + private Resource testfollowRequest; + + @Inject + private KeystoreManager testKeystoreManager; + + private MockRestServiceServer restServiceServer; private static User ugnich, freefd, juick; static String ugnichName, ugnichPassword, freefdName, freefdPassword, juickName, juickPassword; @@ -1699,6 +1711,55 @@ public class ServerTests { signatureManager.post(from, to, follow); } @Test + public void serviceSignatureAuth() throws Exception { + String meUri = "/api/me"; + String testHost = "localhost:8080"; + Person ugnichPerson = (Person) signatureManager.discoverPerson("ugnich@localhost:8080").get(); + Instant now = Instant.now(); + String requestDate = DateFormattersHolder.getHttpDateFormatter().format(now); + String signatureString = signatureManager.addSignature(ugnichPerson, testHost, "GET", meUri, requestDate); + MvcResult me = mockMvc.perform(get("/api/me") + .header("Host", testHost) + .header("Date", requestDate) + .header("Signature", signatureString)) + .andExpect(status().isOk()) + .andReturn(); + User meUser = jsonMapper.readValue(me.getResponse().getContentAsString(), User.class); + assertThat(meUser, is(ugnich)); + String testuserResponseString = IOUtils.toString(testuserResponse.getInputStream(), StandardCharsets.UTF_8); + ClientHttpRequestFactory originalRequestFactory = apClient.getRequestFactory(); + restServiceServer = MockRestServiceServer.bindTo(apClient).build(); + URI testuserUri = URI.create("https://example.com/u/testuser"); + URI testuserkeyUri = URI.create("https://example.com/u/testuser#main-key"); + restServiceServer.expect(times(3), requestTo(testuserUri)) + .andRespond(withSuccess(testuserResponseString, MediaType.APPLICATION_JSON_UTF8)); + restServiceServer.expect(times(3), requestTo(testuserkeyUri)) + .andRespond(withSuccess(testuserResponseString, MediaType.APPLICATION_JSON_UTF8)); + Person testuser = (Person)signatureManager.getContext(testuserUri).get(); + Assert.assertThat(testuser.getPublicKey().getPublicKeyPem(), is(testKeystoreManager.getPublicKeyPem())); + Instant now2 = Instant.now(); + String testRequestDate = DateFormattersHolder.getHttpDateFormatter().format(now2); + String inboxUri = "/api/inbox"; + String testSignatureString = + signatureManager.addSignature(testuser, testHost, "POST", + inboxUri, testRequestDate, testKeystoreManager); + mockMvc.perform(post(inboxUri) + .header("Host", testHost) + .header("Date", testRequestDate) + .header("Signature", testSignatureString) + .contentType(Context.LD_JSON_MEDIA_TYPE) + .content(IOUtils.toByteArray(testfollowRequest.getInputStream()))) + .andExpect(status().isAccepted()); + mockMvc.perform(post(inboxUri) + .header("Host", "wronghost") + .header("Date", testRequestDate) + .header("Signature", testSignatureString) + .contentType(Context.LD_JSON_MEDIA_TYPE) + .content(IOUtils.toByteArray(testfollowRequest.getInputStream()))) + .andExpect(status().isUnauthorized()); + apClient.setRequestFactory(originalRequestFactory); + } + @Test public void hostmeta() throws Exception { MvcResult result = mockMvc.perform(get("/.well-known/host-meta")) .andExpect(status().isOk()).andReturn(); diff --git a/src/test/resources/mocks/activity/testfollow.json b/src/test/resources/mocks/activity/testfollow.json new file mode 100644 index 00000000..e308e52e --- /dev/null +++ b/src/test/resources/mocks/activity/testfollow.json @@ -0,0 +1,15 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "schema": "http://schema.org#", + "PropertyValue": "schema:PropertyValue", + "value": "schema:value" + } + ], + "id": "https://example.com/12345678", + "type": "Follow", + "actor": "https://example.com/u/testuser", + "object": "http://localhost:8080/u/ugnich" +} \ No newline at end of file diff --git a/src/test/resources/mocks/activity/testuser.json b/src/test/resources/mocks/activity/testuser.json new file mode 100644 index 00000000..95fc2aa9 --- /dev/null +++ b/src/test/resources/mocks/activity/testuser.json @@ -0,0 +1,27 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "schema": "http://schema.org#", + "PropertyValue": "schema:PropertyValue", + "value": "schema:value" + } + ], + "id": "https://example.com/u/testuser", + "type": "Person", + "following": "https://example.com/u/testuser/following", + "followers": "https://example.com/u/testuser/followers", + "inbox": "https://example.com/u/testuser/inbox", + "outbox": "https://example.com/u/testuser/outbox", + "preferredUsername": "testuser", + "url": "https://example.com/@testuser", + "publicKey": { + "id": "https://example.com/u/testuser#main-key", + "owner": "https://example.com/u/testuser", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiHKRdKFFeT4P/MVlNbxC\nbbgXOkEdeQzvJB/wAJgSYbUwm9SzNFzttePQXk3/MWoK2awWUInZTduVHsWt8zU7\nO3d9PAW6YH6L1oDkjgMLAb9aUWV2ClQWMwsn88WKK9Rb1WOmd8BrXjPfmeFK2ypQ\n9eg8aKpH36WAXiiaTDfBupBZ0Ki2+E87BrWxpbUeDC1dkV+zbl8BMm7X0rp+reoC\nYUWMcjQMzhMmQOXUd4zwJIDPZDMdF4beq/y6WPSUTVgjs4kPDS1HT60ATnsUqyPE\n6tuGxG4j0msb4TTre87PKxMU5YPOxSiqNL0O/3u9/2shVPpjDa/uy9W+VaeBHbFm\nSQIDAQAB\n-----END PUBLIC KEY-----\n" + }, + "endpoints": { + "sharedInbox": "https://example.com/inbox" + } +} \ No newline at end of file diff --git a/src/test/resources/test.p12 b/src/test/resources/test.p12 new file mode 100644 index 00000000..7f7457eb Binary files /dev/null and b/src/test/resources/test.p12 differ -- cgit v1.2.3 From 0f83e951d1ca5185067cde9b73613b0371f1ba2b Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Sun, 20 Jan 2019 14:47:04 +0300 Subject: Drop Websocket support --- build.gradle | 1 - src/main/java/com/juick/server/ServerManager.java | 103 +----------- .../java/com/juick/server/WebsocketManager.java | 174 --------------------- src/main/java/com/juick/server/api/Index.java | 14 +- .../server/configuration/ApiAppConfiguration.java | 29 +--- 5 files changed, 3 insertions(+), 318 deletions(-) delete mode 100644 src/main/java/com/juick/server/WebsocketManager.java (limited to 'src/main/java/com/juick/server') diff --git a/build.gradle b/build.gradle index 8e7dd87f..7deec746 100644 --- a/build.gradle +++ b/build.gradle @@ -128,7 +128,6 @@ dependencies { exclude group: 'org.jboss.threads' } compile 'org.jboss.threads:jboss-threads:2.3.3.Final' - compile ("org.springframework.boot:spring-boot-starter-websocket") compile ("org.springframework.boot:spring-boot-starter-json") compile ('org.springframework.boot:spring-boot-devtools') diff --git a/src/main/java/com/juick/server/ServerManager.java b/src/main/java/com/juick/server/ServerManager.java index 7a95ec43..be537409 100644 --- a/src/main/java/com/juick/server/ServerManager.java +++ b/src/main/java/com/juick/server/ServerManager.java @@ -16,7 +16,6 @@ */ package com.juick.server; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.juick.Message; import com.juick.User; @@ -24,13 +23,7 @@ import com.juick.model.AnonymousUser; import com.juick.service.MessagesService; import com.juick.service.SubscriptionService; import com.juick.service.UserService; -import com.juick.service.component.LikeEvent; -import com.juick.service.component.MessageEvent; -import com.juick.service.component.MessageReadEvent; -import com.juick.service.component.NotificationListener; -import com.juick.service.component.PingEvent; -import com.juick.service.component.SubscribeEvent; -import com.juick.service.component.TopEvent; +import com.juick.service.component.*; import com.juick.util.MessageUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,11 +31,9 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; -import org.springframework.web.socket.TextMessage; import javax.annotation.PostConstruct; import javax.inject.Inject; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -62,8 +53,6 @@ public class ServerManager implements NotificationListener { @Inject private MessagesService messagesService; @Inject - private WebsocketManager wsHandler; - @Inject private SubscriptionService subscriptionService; @Inject private UserService userService; @@ -81,96 +70,15 @@ public class ServerManager implements NotificationListener { private void onJuickPM(final User to, final com.juick.Message jmsg) { - try { - String json = jsonMapper.writeValueAsString(jmsg); - synchronized (wsHandler.getClients()) { - wsHandler.getClients().stream().filter(c -> - (!c.legacy && c.visitor.getUid() == to.getUid()) || c.visitor.equals(serviceUser)) - .forEach(c -> { - try { - logger.debug("sending pm to {}", c.visitor.getUid()); - c.sendMessage(new TextMessage(json)); - } catch (IOException e) { - logger.warn("ws error", e); - } - }); - } - } catch (JsonProcessingException e) { - logger.warn("Invalid JSON", e); - } messageEvent(jmsg, Collections.singletonList(to)); } private void onJuickMessagePost(final com.juick.Message jmsg, List subscribedUsers) { - try { - String json = jsonMapper.writeValueAsString(jmsg); - List uids = subscribedUsers - .stream().map(User::getUid).collect(Collectors.toList()); - synchronized (wsHandler.getClients()) { - wsHandler.getClients().stream().filter(c -> - (!c.legacy && c.visitor.isAnonymous()) // anonymous users - || c.visitor.equals(serviceUser) // services - || (!c.legacy && uids.contains(c.visitor.getUid()))) // subscriptions - .forEach(c -> { - try { - logger.debug("sending message to {}", c.visitor.getUid()); - c.sendMessage(new TextMessage(json)); - } catch (IOException e) { - logger.warn("ws error", e); - } - }); - wsHandler.getClients().stream().filter(c -> - c.legacy && c.allMessages) // legacy all posts - .forEach(c -> { - try { - logger.debug("sending message to legacy client {}", c.visitor.getUid()); - c.sendMessage(new TextMessage(json)); - } catch (IOException e) { - logger.warn("ws error", e); - } - }); - } - } catch (JsonProcessingException e) { - logger.warn("Invalid JSON", e); - } messageEvent(jmsg, subscribedUsers); messageEvent(jmsg, Collections.singletonList(AnonymousUser.INSTANCE)); } private void onJuickMessageReply(final com.juick.Message jmsg, final List subscribedUsers) { - try { - - String json = jsonMapper.writeValueAsString(jmsg); - List threadUsers = - subscribedUsers - .stream().map(User::getUid).collect(Collectors.toList()); - synchronized (wsHandler.getClients()) { - wsHandler.getClients().stream().filter(c -> - (!c.legacy && c.visitor.isAnonymous()) // anonymous users - || c.visitor.equals(serviceUser) // services - || (!c.legacy && threadUsers.contains(c.visitor.getUid()))) // subscriptions - .forEach(c -> { - try { - logger.debug("sending reply to {}", c.visitor.getUid()); - c.sendMessage(new TextMessage(json)); - } catch (IOException e) { - logger.warn("ws error", e); - } - }); - wsHandler.getClients().stream().filter(c -> - (c.legacy && c.allReplies) || (c.legacy && c.MID == jmsg.getMid())) // legacy replies - .forEach(c -> { - try { - logger.debug("sending reply to legacy client {}", c.visitor.getUid()); - c.sendMessage(new TextMessage(json)); - } catch (IOException e) { - logger.warn("ws error", e); - } - }); - } - } catch (JsonProcessingException e) { - logger.warn("Invalid JSON", e); - } messageEvent(jmsg, subscribedUsers); messageEvent(jmsg, Collections.singletonList(AnonymousUser.INSTANCE)); } @@ -226,15 +134,6 @@ public class ServerManager implements NotificationListener { serviceMessage.setUser(user); serviceMessage.setMid(source.getMid()); serviceMessage.setUnread(false); - wsHandler.getClients().stream().filter(c -> - (!c.legacy && c.visitor == user) || c.visitor.equals(serviceUser) - ).forEach(u -> { - try { - u.sendMessage(new TextMessage(jsonMapper.writeValueAsString(serviceMessage))); - } catch (IOException e) { - logger.error("JSON error", e); - } - }); readEvent(serviceMessage, Collections.singletonList(serviceUser)); } diff --git a/src/main/java/com/juick/server/WebsocketManager.java b/src/main/java/com/juick/server/WebsocketManager.java deleted file mode 100644 index 1b62b984..00000000 --- a/src/main/java/com/juick/server/WebsocketManager.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2008-2017, Juick - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package com.juick.server; - -import com.juick.User; -import com.juick.model.AnonymousUser; -import com.juick.model.CommandResult; -import com.juick.server.util.HttpForbiddenException; -import com.juick.server.util.HttpNotFoundException; -import com.juick.service.MessagesService; -import com.juick.service.UserService; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.math.NumberUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.HttpHeaders; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; -import org.springframework.web.socket.CloseStatus; -import org.springframework.web.socket.PingMessage; -import org.springframework.web.socket.TextMessage; -import org.springframework.web.socket.WebSocketSession; -import org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator; -import org.springframework.web.socket.handler.TextWebSocketHandler; -import org.springframework.web.util.UriComponents; -import org.springframework.web.util.UriComponentsBuilder; - -import javax.annotation.Nonnull; -import javax.inject.Inject; -import java.io.IOException; -import java.net.URI; -import java.time.Instant; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * Created by vitalyster on 28.06.2016. - */ -@Component -public class WebsocketManager extends TextWebSocketHandler { - private static final Logger logger = LoggerFactory.getLogger(WebsocketManager.class); - - private final List clients = new CopyOnWriteArrayList<>(); - - @Inject - private UserService userService; - @Inject - private MessagesService messagesService; - @Inject - private CommandsManager commandsManager; - - - @Override - public void afterConnectionEstablished(WebSocketSession session) { - - UserSession userSession = new UserSession(session); - URI hLocation = session.getUri(); - - // Auth - UriComponents uriComponents = UriComponentsBuilder.fromUri(hLocation).build(); - List hash = uriComponents.getQueryParams().get("hash"); - if (hash != null && hash.get(0).length() == 16) { - userSession.visitor = userService.getUserByHash(hash.get(0)); - } else { - logger.debug("wrong hash for {} from {}", userSession.visitor.getUid(), userSession); - } - - if (hLocation.getPath().equals("/ws/")) { - logger.debug("user {} connected", userSession.visitor.getUid()); - } else if (hLocation.getPath().equals("/ws/_all")) { - logger.debug("user {} connected to legacy _all ({})", userSession.visitor.getUid(), hLocation.getPath()); - userSession.legacy = true; - userSession.allMessages = true; - } else if (hLocation.getPath().equals("/ws/_replies")) { - logger.debug("user {} connected to legacy _replies ({})", userSession.visitor.getUid(), hLocation.getPath()); - userSession.legacy = true; - userSession.allReplies = true; - } else if (hLocation.getPath().matches("^/ws/(\\d)+$")) { - int MID = NumberUtils.toInt(hLocation.getPath().substring(4), 0); - if (MID > 0) { - if (messagesService.canViewThread(MID, userSession.visitor.getUid())) { - logger.debug("user {} connected to legacy thread ({}) from {}", userSession.visitor.getUid(), MID, userSession); - userSession.legacy = true; - userSession.MID = MID; - } else { - throw new HttpForbiddenException(); - } - } - } else { - throw new HttpNotFoundException(); - } - clients.add(userSession); - logger.debug("{} clients connected", clients.size()); - } - - @Override - public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { - logger.debug("session closed with status {}: {}", status.getCode(), status.getReason()); - clients.removeIf(c -> c.getDelegate().getId().equals(session.getId())); - logger.debug("{} clients connected", clients.size()); - } - - @Scheduled(fixedRate = 30000) - public void ping() { - clients.forEach(c -> { - try { - if (c.isOpen()) { - c.sendMessage(new PingMessage()); - } - } catch (IOException e) { - logger.error("WebSocket PING exception", e); - } - }); - } - - @Override - protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { - UserSession ws = clients.stream().filter(c -> c.getDelegate().equals(session)) - .findFirst().orElseThrow(IllegalStateException::new); - if (!ws.visitor.isAnonymous()) { - String command = message.getPayload().trim(); - if (StringUtils.isNotEmpty(command)) { - CommandResult result = commandsManager.processCommand(ws.visitor, command, URI.create("")); - ws.sendMessage(new TextMessage(result.getText())); - } - } else { - ws.sendMessage(new TextMessage("Authorization required")); - } - } - - public List getClients() { - return clients; - } - - class UserSession extends ConcurrentWebSocketSessionDecorator { - User visitor; - int MID; - boolean allMessages; - boolean allReplies; - Instant tsConnected; - Instant tsLastData; - boolean legacy; - - UserSession(WebSocketSession session) { - super(session, 60000, 65536); - this.visitor = AnonymousUser.INSTANCE; - tsConnected = tsLastData = Instant.now(); - } - - @Nonnull - @Override - public String toString() { - HttpHeaders headers = getHandshakeHeaders(); - return headers.getOrDefault("X-Real-IP", - Collections.singletonList(getRemoteAddress().toString())).get(0); - } - } -} diff --git a/src/main/java/com/juick/server/api/Index.java b/src/main/java/com/juick/server/api/Index.java index 56f01370..4573641b 100644 --- a/src/main/java/com/juick/server/api/Index.java +++ b/src/main/java/com/juick/server/api/Index.java @@ -17,10 +17,7 @@ package com.juick.server.api; -import com.juick.Status; -import com.juick.server.WebsocketManager; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -28,7 +25,6 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import springfox.documentation.annotations.ApiIgnore; -import javax.inject.Inject; import java.net.URI; /** @@ -36,19 +32,11 @@ import java.net.URI; */ @RestController public class Index { - @Inject - private WebsocketManager wsHandler; @ApiIgnore - @RequestMapping(value = { "/api/", "/ws/" }, method = RequestMethod.GET, headers = "Connection!=Upgrade") + @RequestMapping(value = { "/api/", "/ws/" }, method = RequestMethod.GET) public ResponseEntity description() { URI redirectUri = ServletUriComponentsBuilder.fromCurrentRequestUri().path("/swagger-ui.html").build().toUri(); return ResponseEntity.status(HttpStatus.MOVED_PERMANENTLY).location(redirectUri).build(); } - @ApiIgnore - @RequestMapping(value = "/api/status", method = RequestMethod.GET, - produces = MediaType.APPLICATION_JSON_UTF8_VALUE, headers = "Connection!=Upgrade") - public Status status() { - return Status.getStatus(String.valueOf(wsHandler.getClients().size())); - } } diff --git a/src/main/java/com/juick/server/configuration/ApiAppConfiguration.java b/src/main/java/com/juick/server/configuration/ApiAppConfiguration.java index 5a5d2c7b..68b3d35f 100644 --- a/src/main/java/com/juick/server/configuration/ApiAppConfiguration.java +++ b/src/main/java/com/juick/server/configuration/ApiAppConfiguration.java @@ -17,25 +17,15 @@ package com.juick.server.configuration; -import com.juick.server.WebsocketManager; import com.juick.server.api.rss.MessagesView; import com.juick.server.api.rss.RepliesView; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.Ordered; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.view.BeanNameViewResolver; import org.springframework.web.servlet.view.feed.AbstractRssFeedView; -import org.springframework.web.socket.config.annotation.EnableWebSocket; -import org.springframework.web.socket.config.annotation.ServletWebSocketHandlerRegistry; -import org.springframework.web.socket.config.annotation.WebSocketConfigurer; -import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; -import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean; - -import javax.annotation.Nonnull; -import javax.inject.Inject; /** * Created by aalexeev on 11/12/16. @@ -43,24 +33,7 @@ import javax.inject.Inject; @Configuration @EnableAsync(proxyTargetClass = true) @EnableScheduling -@EnableWebSocket -public class ApiAppConfiguration implements WebMvcConfigurer, WebSocketConfigurer { - @Inject - private WebsocketManager websocketManager; - - @Override - public void registerWebSocketHandlers(@Nonnull WebSocketHandlerRegistry registry) { - ((ServletWebSocketHandlerRegistry) registry).setOrder(Ordered.HIGHEST_PRECEDENCE); - registry.addHandler(websocketManager, "/ws/**").setAllowedOrigins("*"); - } - - @Bean - public ServletServerContainerFactoryBean createWebSocketContainer() { - ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean(); - container.setMaxTextMessageBufferSize(8192); - container.setMaxBinaryMessageBufferSize(8192); - return container; - } +public class ApiAppConfiguration implements WebMvcConfigurer { @Bean public BeanNameViewResolver beanNameViewResolver() { return new BeanNameViewResolver(); -- cgit v1.2.3 From 8b474748883b9a4d780c7d696445126666c2094f Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Sun, 20 Jan 2019 14:52:45 +0300 Subject: Set avatar in events --- src/main/java/com/juick/server/CommandsManager.java | 7 ++++++- src/main/java/com/juick/server/ServerManager.java | 3 +-- src/main/java/com/juick/server/api/PM.java | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/CommandsManager.java b/src/main/java/com/juick/server/CommandsManager.java index fa3c5537..087ba444 100644 --- a/src/main/java/com/juick/server/CommandsManager.java +++ b/src/main/java/com/juick/server/CommandsManager.java @@ -25,6 +25,7 @@ import com.juick.model.CommandResult; import com.juick.model.TagStats; import com.juick.server.helpers.annotation.UserCommand; import com.juick.server.util.HttpUtils; +import com.juick.server.www.WebApp; import com.juick.service.*; import com.juick.service.activities.DeleteMessageEvent; import com.juick.service.component.*; @@ -75,6 +76,8 @@ public class CommandsManager { private ApplicationEventPublisher applicationEventPublisher; @Inject private ImagesService imagesService; + @Inject + private WebApp webApp; public CommandResult processCommand(@Nonnull User user, String data, @Nonnull URI attachment) throws Exception { if (!user.isAnonymous()) { @@ -127,6 +130,7 @@ public class CommandsManager { imagesService.saveImageWithPreviews(attachmentFName, fname); } Message msg = messagesService.getMessage(mid).orElseThrow(IllegalStateException::new); + msg.getUser().setAvatar(webApp.getAvatarWebPath(msg.getUser())); subscriptionService.subscribeMessage(msg, user); applicationEventPublisher.publishEvent(new MessageReadEvent(this, user, msg)); @@ -160,7 +164,7 @@ public class CommandsManager { String body = arguments[1]; User user_to = userService.getUserByName(arguments[0]); - + user_to.setAvatar(webApp.getAvatarWebPath(user_to)); if (!user_to.isAnonymous()) { if (!userService.isInBLAny(user_to.getUid(), user_from.getUid())) { if (pmQueriesService.createPM(user_from.getUid(), user_to.getUid(), body)) { @@ -537,6 +541,7 @@ public class CommandsManager { Message original = messagesService.getMessage(mid).orElseThrow(IllegalStateException::new); subscriptionService.subscribeMessage(original, user); Message reply = messagesService.getReply(mid, newrid); + reply.getUser().setAvatar(webApp.getAvatarWebPath(reply.getUser())); applicationEventPublisher.publishEvent(new MessageEvent(this, reply, subscriptionService.getUsersSubscribedToComments(original, reply))); return CommandResult.build(reply,"Reply posted.\n#" + mid + "/" + newrid + " " + "https://juick.com/m/" + mid + "#" + newrid, diff --git a/src/main/java/com/juick/server/ServerManager.java b/src/main/java/com/juick/server/ServerManager.java index be537409..5f96da08 100644 --- a/src/main/java/com/juick/server/ServerManager.java +++ b/src/main/java/com/juick/server/ServerManager.java @@ -94,8 +94,7 @@ public class ServerManager implements NotificationListener { if (MessageUtils.isPM(jmsg)) { onJuickPM(jmsg.getTo(), jmsg); } else if (!MessageUtils.isReply(jmsg)) { - // to get full message with attachment, etc. - onJuickMessagePost(messagesService.getMessage(jmsg.getMid()).orElseThrow(IllegalStateException::new), subscribedUsers); + onJuickMessagePost(jmsg, subscribedUsers); } else { // to get quote and attachment Message op = messagesService.getMessage(jmsg.getMid()).orElseThrow(IllegalStateException::new); diff --git a/src/main/java/com/juick/server/api/PM.java b/src/main/java/com/juick/server/api/PM.java index a80ad0dc..242cd31c 100644 --- a/src/main/java/com/juick/server/api/PM.java +++ b/src/main/java/com/juick/server/api/PM.java @@ -99,6 +99,7 @@ public class PM { jmsg.setUser(visitor); jmsg.setText(body); jmsg.setTo(userTo); + jmsg.getUser().setAvatar(webApp.getAvatarWebPath(jmsg.getUser())); applicationEventPublisher.publishEvent(new MessageEvent(this, jmsg, Collections.singletonList(jmsg.getTo()))); return jmsg; -- cgit v1.2.3 From 920ccf071828cb49e573dd873f1f4c124950c259 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Sun, 20 Jan 2019 15:51:36 +0300 Subject: AvatarWebPath -> AvatarUrl --- src/main/java/com/juick/server/CommandsManager.java | 6 +++--- src/main/java/com/juick/server/api/PM.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/CommandsManager.java b/src/main/java/com/juick/server/CommandsManager.java index 087ba444..4a7f90ef 100644 --- a/src/main/java/com/juick/server/CommandsManager.java +++ b/src/main/java/com/juick/server/CommandsManager.java @@ -130,7 +130,7 @@ public class CommandsManager { imagesService.saveImageWithPreviews(attachmentFName, fname); } Message msg = messagesService.getMessage(mid).orElseThrow(IllegalStateException::new); - msg.getUser().setAvatar(webApp.getAvatarWebPath(msg.getUser())); + msg.getUser().setAvatar(webApp.getAvatarUrl(msg.getUser())); subscriptionService.subscribeMessage(msg, user); applicationEventPublisher.publishEvent(new MessageReadEvent(this, user, msg)); @@ -164,7 +164,7 @@ public class CommandsManager { String body = arguments[1]; User user_to = userService.getUserByName(arguments[0]); - user_to.setAvatar(webApp.getAvatarWebPath(user_to)); + user_to.setAvatar(webApp.getAvatarUrl(user_to)); if (!user_to.isAnonymous()) { if (!userService.isInBLAny(user_to.getUid(), user_from.getUid())) { if (pmQueriesService.createPM(user_from.getUid(), user_to.getUid(), body)) { @@ -541,7 +541,7 @@ public class CommandsManager { Message original = messagesService.getMessage(mid).orElseThrow(IllegalStateException::new); subscriptionService.subscribeMessage(original, user); Message reply = messagesService.getReply(mid, newrid); - reply.getUser().setAvatar(webApp.getAvatarWebPath(reply.getUser())); + reply.getUser().setAvatar(webApp.getAvatarUrl(reply.getUser())); applicationEventPublisher.publishEvent(new MessageEvent(this, reply, subscriptionService.getUsersSubscribedToComments(original, reply))); return CommandResult.build(reply,"Reply posted.\n#" + mid + "/" + newrid + " " + "https://juick.com/m/" + mid + "#" + newrid, diff --git a/src/main/java/com/juick/server/api/PM.java b/src/main/java/com/juick/server/api/PM.java index 242cd31c..e61fef6e 100644 --- a/src/main/java/com/juick/server/api/PM.java +++ b/src/main/java/com/juick/server/api/PM.java @@ -99,7 +99,7 @@ public class PM { jmsg.setUser(visitor); jmsg.setText(body); jmsg.setTo(userTo); - jmsg.getUser().setAvatar(webApp.getAvatarWebPath(jmsg.getUser())); + jmsg.getUser().setAvatar(webApp.getAvatarUrl(jmsg.getUser())); applicationEventPublisher.publishEvent(new MessageEvent(this, jmsg, Collections.singletonList(jmsg.getTo()))); return jmsg; -- cgit v1.2.3 From a125a8d20c8a95bc877e14716318d6df08622af5 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Sun, 20 Jan 2019 16:10:07 +0300 Subject: Do not overwrite user avatar url in reply --- src/main/java/com/juick/server/ServerManager.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/ServerManager.java b/src/main/java/com/juick/server/ServerManager.java index 5f96da08..4ea290fc 100644 --- a/src/main/java/com/juick/server/ServerManager.java +++ b/src/main/java/com/juick/server/ServerManager.java @@ -98,11 +98,10 @@ public class ServerManager implements NotificationListener { } else { // to get quote and attachment Message op = messagesService.getMessage(jmsg.getMid()).orElseThrow(IllegalStateException::new); - com.juick.Message reply = messagesService.getReply(jmsg.getMid(), jmsg.getRid()); - subscriptionService.getUsersSubscribedToComments(op, reply, true).stream() - .filter(u -> userService.isReplyToBL(u, reply)) - .forEach(b -> messagesService.setLastReadComment(b, reply.getMid(), reply.getRid())); - onJuickMessageReply(reply, subscribedUsers); + subscriptionService.getUsersSubscribedToComments(op, jmsg, true).stream() + .filter(u -> userService.isReplyToBL(u, jmsg)) + .forEach(b -> messagesService.setLastReadComment(b, jmsg.getMid(), jmsg.getRid())); + onJuickMessageReply(jmsg, subscribedUsers); } messageEvent(jmsg, Collections.singletonList(serviceUser)); } -- cgit v1.2.3 From 41b6764be12c2b21f0de55b8a80091e279577ab5 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Mon, 21 Jan 2019 14:49:07 +0300 Subject: ActivityPub: convert HTML to Markdown --- build.gradle | 1 + src/main/java/com/juick/server/api/activity/Profile.java | 9 +++++++-- .../com/juick/server/configuration/BaseWebConfiguration.java | 8 ++++++++ src/main/java/com/juick/service/MessagesServiceImpl.java | 2 +- src/test/java/com/juick/MessageTest.java | 10 ++++++++++ src/test/java/com/juick/server/tests/ServerTests.java | 12 ++++++------ 6 files changed, 33 insertions(+), 9 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/build.gradle b/build.gradle index 7deec746..fcf52dfc 100644 --- a/build.gradle +++ b/build.gradle @@ -180,6 +180,7 @@ dependencies { compile 'org.tomitribe:tomitribe-http-signatures:1.1' compile 'com.squareup.okhttp3:okhttp:3.12.1' compile 'com.google.api-client:google-api-client:1.28.0' + compile "com.kotcrab.remark:remark:1.0.0" testCompile("org.springframework.boot:spring-boot-starter-test") testCompile('net.sourceforge.htmlunit:htmlunit:2.33') diff --git a/src/main/java/com/juick/server/api/activity/Profile.java b/src/main/java/com/juick/server/api/activity/Profile.java index 404f0f84..3e0179c8 100644 --- a/src/main/java/com/juick/server/api/activity/Profile.java +++ b/src/main/java/com/juick/server/api/activity/Profile.java @@ -29,6 +29,7 @@ import com.juick.server.www.WebApp; import com.juick.service.MessagesService; import com.juick.service.UserService; import com.juick.service.activities.*; +import com.overzealous.remark.Remark; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -82,6 +83,8 @@ public class Profile { private ObjectMapper jsonMapper; @Inject private WebApp webApp; + @Inject + private Remark remarkConverter; @GetMapping(value = "/u/{userName}", produces = {Context.LD_JSON_MEDIA_TYPE, Context.ACTIVITYSTREAMS_PROFILE_MEDIA_TYPE}) public Person getUser(@PathVariable String userName) { @@ -301,7 +304,8 @@ public class Profile { Map attachmentObj = (Map) ((List) note.get("attachment")).get(0); attachment = (String) attachmentObj.get("url"); } - CommandResult result = commandsManager.processCommand(user, String.format("#%s %s", postId, note.get("content")), URI.create(attachment)); + String markdown = remarkConverter.convertFragment((String)note.get("content")); + CommandResult result = commandsManager.processCommand(user, String.format("#%s %s", postId, markdown), URI.create(attachment)); logger.info(jsonMapper.writeValueAsString(result)); if (result.getNewMessage().isPresent()) { messagesService.updateReplyUri(result.getNewMessage().get(), noteId); @@ -319,7 +323,8 @@ public class Profile { Map attachmentObj = (Map) ((List) note.get("attachment")).get(0); attachment = (String) attachmentObj.get("url"); } - CommandResult result = commandsManager.processCommand(user, String.format("#%d/%d %s", reply.getMid(), reply.getRid(), note.get("content")), URI.create(attachment)); + String markdown = remarkConverter.convertFragment((String)note.get("content")); + CommandResult result = commandsManager.processCommand(user, String.format("#%d/%d %s", reply.getMid(), reply.getRid(), markdown), URI.create(attachment)); logger.info(jsonMapper.writeValueAsString(result)); if (result.getNewMessage().isPresent()) { messagesService.updateReplyUri(result.getNewMessage().get(), noteId); diff --git a/src/main/java/com/juick/server/configuration/BaseWebConfiguration.java b/src/main/java/com/juick/server/configuration/BaseWebConfiguration.java index 16693995..c8b88cd1 100644 --- a/src/main/java/com/juick/server/configuration/BaseWebConfiguration.java +++ b/src/main/java/com/juick/server/configuration/BaseWebConfiguration.java @@ -18,6 +18,8 @@ package com.juick.server.configuration; import com.juick.server.KeystoreManager; +import com.overzealous.remark.Options; +import com.overzealous.remark.Remark; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -71,4 +73,10 @@ public class BaseWebConfiguration implements WebMvcConfigurer, SchedulingConfigu public KeystoreManager keystoreManager() { return new KeystoreManager(keystore, keystorePassword); } + @Bean + public Remark remarkConverter() { + Options options = new Options(); + options.inlineLinks = true; + return new Remark(options); + } } diff --git a/src/main/java/com/juick/service/MessagesServiceImpl.java b/src/main/java/com/juick/service/MessagesServiceImpl.java index 245ae783..c3d319d5 100644 --- a/src/main/java/com/juick/service/MessagesServiceImpl.java +++ b/src/main/java/com/juick/service/MessagesServiceImpl.java @@ -1139,7 +1139,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ @Override public boolean updateReplyUri(Message reply, URI replyUri) { - return jdbcTemplate.update("UPDATE replies SET reply_uri=?, html=1 WHERE message_id=? AND reply_id=?", + return jdbcTemplate.update("UPDATE replies SET reply_uri=?, html=0 WHERE message_id=? AND reply_id=?", replyUri.toASCIIString(), reply.getMid(), reply.getRid()) > 0; } diff --git a/src/test/java/com/juick/MessageTest.java b/src/test/java/com/juick/MessageTest.java index 4c37a678..2d12a3df 100644 --- a/src/test/java/com/juick/MessageTest.java +++ b/src/test/java/com/juick/MessageTest.java @@ -20,6 +20,8 @@ package com.juick; import com.juick.model.Entity; import com.juick.test.util.MockUtils; import com.juick.util.MessageUtils; +import com.overzealous.remark.Options; +import com.overzealous.remark.Remark; import org.apache.commons.lang3.RandomUtils; import org.apache.commons.lang3.StringUtils; import org.junit.Test; @@ -196,4 +198,12 @@ public class MessageTest { assertThat(yo.getStart(), is(0)); assertThat(yo.getEnd(), is(7)); } + @Test + public void ActivityStreamsHTMLtoMarkdownTest() { + String text = "

@stanislavv я в @rf выкладывал =)

"; + Options options = new Options(); + options.inlineLinks = true; + Remark remark = new Remark(options); + String parsed = remark.convertFragment(text); + } } diff --git a/src/test/java/com/juick/server/tests/ServerTests.java b/src/test/java/com/juick/server/tests/ServerTests.java index fedbaba0..f1087d8c 100644 --- a/src/test/java/com/juick/server/tests/ServerTests.java +++ b/src/test/java/com/juick/server/tests/ServerTests.java @@ -442,7 +442,7 @@ public class ServerTests { @Test public void homeTestWithMessages() throws Exception { String msgText = "Привет, я - Угнич"; - CommandResult result = commandsManager.processCommand(ugnich, msgText, URI.create("http://static.juick.com/settings/facebook.png")); + CommandResult result = commandsManager.processCommand(ugnich, msgText, URI.create("https://static.juick.com/settings/facebook.png")); int mid = result.getNewMessage().get().getMid(); Message msg = messagesService.getMessage(mid).get(); tagService.createTag("тест"); @@ -697,15 +697,15 @@ public class ServerTests { int uid = userService.createUser("me", "secret"); User user = userService.getUserByUID(uid).orElse(AnonymousUser.INSTANCE); Tag yo = tagService.getTag("yo", true); - Message msg = commandsManager.processCommand(user, "*yo yoyo", URI.create("http://static.juick.com/settings/facebook.png")).getNewMessage().get(); + Message msg = commandsManager.processCommand(user, "*yo yoyo", URI.create("https://static.juick.com/settings/facebook.png")).getNewMessage().get(); assertThat(msg.getAttachmentType(), is("png")); - Message msgreply = commandsManager.processCommand(user, "#" + msg.getMid() + " yyy", HttpUtils.downloadImage(URI.create("http://static.juick.com/settings/xmpp.png").toURL(), tmpDir)).getNewMessage().get(); + Message msgreply = commandsManager.processCommand(user, "#" + msg.getMid() + " yyy", HttpUtils.downloadImage(URI.create("https://static.juick.com/settings/xmpp.png").toURL(), tmpDir)).getNewMessage().get(); assertThat(msgreply.getAttachmentType(), equalTo("png")); assertEquals("text should match", "yoyo", messagesService.getMessage(msg.getMid()).get().getText()); assertEquals("tag should match", "yo", tagService.getMessageTags(msg.getMid()).get(0).getTag().getName()); - CommandResult yoyoMsg = commandsManager.processCommand(user, "*yo", URI.create("http://static.juick.com/settings/facebook.png")); + CommandResult yoyoMsg = commandsManager.processCommand(user, "*yo", URI.create("https://static.juick.com/settings/facebook.png")); assertTrue(yoyoMsg.getNewMessage().isPresent()); assertThat(yoyoMsg.getNewMessage().get().getTags().get(0), is(yo)); Message msg2 = yoyoMsg.getNewMessage().get(); @@ -749,10 +749,10 @@ public class ServerTests { String expectedThirdReply = "Reply posted.\n#" + mid + "/3 " + "https://juick.com/m/" + mid + "#3"; assertEquals("should be second reply", expectedSecondReply, - commandsManager.processCommand(user, "#" + mid + " yoyo", URI.create("http://static.juick.com/settings/facebook.png")).getText()); + commandsManager.processCommand(user, "#" + mid + " yoyo", URI.create("https://static.juick.com/settings/facebook.png")).getText()); assertEquals("should be third reply", expectedThirdReply, commandsManager.processCommand(user, " \t\n #" + mid + "/2 ", - URI.create("http://static.juick.com/settings/facebook.png")).getText()); + URI.create("https://static.juick.com/settings/facebook.png")).getText()); Message reply = messagesService.getReplies(user, mid).stream().filter(m -> m.getRid() == 3).findFirst() .orElse(new Message()); Timestamp lastreply = jdbcTemplate.queryForObject("SELECT lastmessage FROM users WHERE id=?", Timestamp.class, user.getUid()); -- cgit v1.2.3 From e4f45130797df9dc94d10fd1c79ad8952c6390c1 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Tue, 22 Jan 2019 21:12:58 +0300 Subject: ActivityPub: make AP requests with correct Accept header --- .../configuration/ActivityPubClientConfig.java | 5 +++++ .../server/helpers/HeaderRequestInterceptor.java | 26 ++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 src/main/java/com/juick/server/helpers/HeaderRequestInterceptor.java (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/configuration/ActivityPubClientConfig.java b/src/main/java/com/juick/server/configuration/ActivityPubClientConfig.java index 43b638fe..6cb1126d 100644 --- a/src/main/java/com/juick/server/configuration/ActivityPubClientConfig.java +++ b/src/main/java/com/juick/server/configuration/ActivityPubClientConfig.java @@ -1,11 +1,14 @@ package com.juick.server.configuration; +import com.juick.server.api.activity.model.Activity; +import com.juick.server.helpers.HeaderRequestInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.OkHttp3ClientHttpRequestFactory; import org.springframework.web.client.RestTemplate; import javax.inject.Inject; +import java.util.Collections; @Configuration public class ActivityPubClientConfig { @@ -15,6 +18,8 @@ public class ActivityPubClientConfig { public RestTemplate apClient() { RestTemplate restTemplate = new RestTemplate(new OkHttp3ClientHttpRequestFactory()); restTemplate.setErrorHandler(activityPubClientErrorHandler); + restTemplate.setInterceptors(Collections.singletonList( + new HeaderRequestInterceptor("Accept", Activity.ACTIVITY_MEDIA_TYPE))); return restTemplate; } } \ No newline at end of file diff --git a/src/main/java/com/juick/server/helpers/HeaderRequestInterceptor.java b/src/main/java/com/juick/server/helpers/HeaderRequestInterceptor.java new file mode 100644 index 00000000..9121f5ce --- /dev/null +++ b/src/main/java/com/juick/server/helpers/HeaderRequestInterceptor.java @@ -0,0 +1,26 @@ +package com.juick.server.helpers; + +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; + +import java.io.IOException; + +public class HeaderRequestInterceptor implements ClientHttpRequestInterceptor { + + private final String headerName; + + private final String headerValue; + + public HeaderRequestInterceptor(String headerName, String headerValue) { + this.headerName = headerName; + this.headerValue = headerValue; + } + + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { + request.getHeaders().set(headerName, headerValue); + return execution.execute(request, body); + } +} -- cgit v1.2.3 From b09dac438ad29903a79916d20c90e1a2154fabf6 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Wed, 30 Jan 2019 09:16:22 +0300 Subject: ActivityPub: cc to global mentions --- src/main/java/com/juick/server/ActivityPubManager.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/ActivityPubManager.java b/src/main/java/com/juick/server/ActivityPubManager.java index 5249f3c9..24cc2123 100644 --- a/src/main/java/com/juick/server/ActivityPubManager.java +++ b/src/main/java/com/juick/server/ActivityPubManager.java @@ -182,9 +182,10 @@ public class ActivityPubManager implements ActivityListener, NotificationListene cc.add(replier); note.setCc(cc); } + subscribers.addAll(note.getCc()); subscribers.forEach(acct -> { Optional context = signatureManager.getContext(URI.create(acct)); - if (context.isPresent()) { + if (context.isPresent() && context.get() instanceof Person) { Person follower = (Person)context.get(); Create create = new Create(); create.setId(note.getId()); @@ -304,7 +305,7 @@ public class ActivityPubManager implements ActivityListener, NotificationListene Person person = (Person) personContext.get(); note.getTags().add(new Mention(person.getUrl(), person.getPreferredUsername())); List cc = new ArrayList<>(note.getCc()); - cc.add(person.getUrl()); + cc.add(person.getId()); note.setCc(cc); } }); -- cgit v1.2.3 From 8bcb61d239a9c56641d60b861a92c1a339994e52 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Wed, 30 Jan 2019 09:40:15 +0300 Subject: Append URL if attachment can not be downloaded --- .../java/com/juick/server/CommandsManager.java | 23 +++++++++++++++++----- .../java/com/juick/server/SignatureManager.java | 2 +- 2 files changed, 19 insertions(+), 6 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/CommandsManager.java b/src/main/java/com/juick/server/CommandsManager.java index 4a7f90ef..1c726e60 100644 --- a/src/main/java/com/juick/server/CommandsManager.java +++ b/src/main/java/com/juick/server/CommandsManager.java @@ -35,12 +35,15 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.apache.commons.lang3.reflect.MethodUtils; import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; import javax.annotation.Nonnull; import javax.inject.Inject; +import java.io.IOException; import java.lang.reflect.Method; import java.net.URI; import java.util.*; @@ -54,6 +57,7 @@ import java.util.stream.Collectors; */ @Component public class CommandsManager { + private static final Logger logger = LoggerFactory.getLogger(CommandsManager.class); @Inject private MessagesService messagesService; @Inject @@ -527,12 +531,21 @@ public class CommandsManager { String attachmentFName = null; String attachmentType = null; if (haveAttachment) { - attachmentFName = attachment.getScheme().equals("juick") ? attachment.getHost() - : HttpUtils.downloadImage(attachment.toURL(), tmpDir).getHost(); - attachmentType = attachmentFName.substring(attachmentFName.length() - 3); + if (attachment.getScheme().equals("juick")) { + attachmentFName = attachment.getHost(); + attachmentType = attachmentFName.substring(attachmentFName.length() - 3); + } else { + try { + attachmentFName = HttpUtils.downloadImage(attachment.toURL(), tmpDir).getHost(); + } catch (IOException e) { + logger.warn("Can not download {}", attachment.toURL()); + } + } } - int newrid = messagesService.createReply(mid, rid, user, txt, attachmentType); - if (haveAttachment) { + boolean attachmentDownloaded = StringUtils.isNotEmpty(attachmentType); + String messageText = attachmentDownloaded ? txt : String.format("%s %s", txt, attachment.toASCIIString()); + int newrid = messagesService.createReply(mid, rid, user, messageText, attachmentType); + if (attachmentDownloaded) { String fname = String.format("%d-%d.%s", mid, newrid, attachmentType); imagesService.saveImageWithPreviews(attachmentFName, fname); } diff --git a/src/main/java/com/juick/server/SignatureManager.java b/src/main/java/com/juick/server/SignatureManager.java index 23f5c37a..083a5edf 100644 --- a/src/main/java/com/juick/server/SignatureManager.java +++ b/src/main/java/com/juick/server/SignatureManager.java @@ -98,7 +98,7 @@ public class SignatureManager { Verifier verifier = new Verifier(key, signature); try { boolean result = verifier.verify(method, path, headers); - logger.info("signature is valid: {}", result); + logger.info("signature of {} is valid: {}", signature.getKeyId(), result); if (result) { User user = new User(); user.setUri(URI.create(person.getId())); -- cgit v1.2.3 From 996e58b2611fd1c2d3a18222b1683335753da680 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Wed, 30 Jan 2019 09:48:40 +0300 Subject: Do not modify replies without attachments --- src/main/java/com/juick/server/CommandsManager.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/CommandsManager.java b/src/main/java/com/juick/server/CommandsManager.java index 1c726e60..08dafe77 100644 --- a/src/main/java/com/juick/server/CommandsManager.java +++ b/src/main/java/com/juick/server/CommandsManager.java @@ -542,10 +542,10 @@ public class CommandsManager { } } } - boolean attachmentDownloaded = StringUtils.isNotEmpty(attachmentType); - String messageText = attachmentDownloaded ? txt : String.format("%s %s", txt, attachment.toASCIIString()); + boolean attachmentProcessed = !haveAttachment || StringUtils.isNotEmpty(attachmentType); + String messageText = attachmentProcessed ? txt : String.format("%s %s", txt, attachment.toASCIIString()); int newrid = messagesService.createReply(mid, rid, user, messageText, attachmentType); - if (attachmentDownloaded) { + if (haveAttachment && attachmentProcessed) { String fname = String.format("%d-%d.%s", mid, newrid, attachmentType); imagesService.saveImageWithPreviews(attachmentFName, fname); } -- cgit v1.2.3 From ba5d3815a7f40c2d824bd329dad571f8b883b25e Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Wed, 30 Jan 2019 10:19:00 +0300 Subject: ActivityPub: log inbox data --- src/main/java/com/juick/server/api/activity/Profile.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/api/activity/Profile.java b/src/main/java/com/juick/server/api/activity/Profile.java index 3e0179c8..84a3de33 100644 --- a/src/main/java/com/juick/server/api/activity/Profile.java +++ b/src/main/java/com/juick/server/api/activity/Profile.java @@ -30,6 +30,7 @@ import com.juick.service.MessagesService; import com.juick.service.UserService; import com.juick.service.activities.*; import com.overzealous.remark.Remark; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,7 +52,9 @@ import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; import javax.inject.Inject; +import java.io.InputStream; import java.net.URI; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -256,7 +259,10 @@ public class Profile { } @PostMapping(value = "/api/inbox", consumes = {Context.LD_JSON_MEDIA_TYPE, Context.ACTIVITYSTREAMS_PROFILE_MEDIA_TYPE}) - public ResponseEntity processInbox(@RequestBody Activity activity) throws Exception { + public ResponseEntity processInbox(InputStream inboxData) throws Exception { + String inbox = IOUtils.toString(inboxData, StandardCharsets.UTF_8); + logger.debug("Inbox: {}", inbox); + Activity activity = jsonMapper.readValue(inbox, Activity.class); User visitor = UserUtils.getCurrentUser(); if ((StringUtils.isNotEmpty(visitor.getUri().toString()) && visitor.getUri().equals(URI.create(activity.getActor()))) || !visitor.isAnonymous()) { if (activity instanceof Follow) { -- cgit v1.2.3 From 053300c67d2fe274f2f562712d8586e81700c085 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Wed, 30 Jan 2019 12:34:04 +0300 Subject: Corrected flow for federated user deletion --- .../java/com/juick/server/api/activity/Profile.java | 17 ++++++++++------- src/test/java/com/juick/server/tests/ServerTests.java | 14 ++++++++++++++ src/test/resources/delete_user.json | 1 + 3 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 src/test/resources/delete_user.json (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/api/activity/Profile.java b/src/main/java/com/juick/server/api/activity/Profile.java index 84a3de33..b1d325f9 100644 --- a/src/main/java/com/juick/server/api/activity/Profile.java +++ b/src/main/java/com/juick/server/api/activity/Profile.java @@ -284,13 +284,6 @@ public class Profile { return new ResponseEntity<>(HttpStatus.OK); } } - if (activity instanceof Delete) { - if (activity.getObject() instanceof String) { - // Delete user - applicationEventPublisher.publishEvent(new DeleteUserEvent(this, (String)activity.getObject())); - return new ResponseEntity<>(HttpStatus.OK); - } - } if (activity instanceof Create) { if (activity.getObject() instanceof Map) { Map note = (Map) activity.getObject(); @@ -361,6 +354,16 @@ public class Profile { logger.warn("Unknown activity: {}", jsonMapper.writeValueAsString(activity)); return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } + if (activity instanceof Delete) { + if (activity.getObject() instanceof String) { + // Delete gone user + if (activity.getActor().equals(activity.getObject())) { + if (signatureManager.getContext(URI.create(activity.getActor())).isEmpty()) { + return new ResponseEntity<>(HttpStatus.ACCEPTED); + } + } + } + } return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); } @PostMapping(value = "/u/", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) diff --git a/src/test/java/com/juick/server/tests/ServerTests.java b/src/test/java/com/juick/server/tests/ServerTests.java index f1087d8c..7fe39d0d 100644 --- a/src/test/java/com/juick/server/tests/ServerTests.java +++ b/src/test/java/com/juick/server/tests/ServerTests.java @@ -117,6 +117,7 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.StreamSupport; +import static com.juick.server.api.activity.model.Context.ACTIVITY_MEDIA_TYPE; import static junit.framework.TestCase.assertTrue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @@ -124,6 +125,7 @@ import static org.junit.Assert.*; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.test.web.client.ExpectedCount.times; import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus; import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; @@ -1885,4 +1887,16 @@ public class ServerTests { tst = jsonMapper.readValue(result.getResponse().getContentAsString(), User.class); assertThat(tst.getReaders().size(), is(1)); } + @Test + public void federatedUserDeletionFlow() throws Exception { + String deleteJsonStr = IOUtils.toString(new ClassPathResource("delete_user.json").getURI(), StandardCharsets.UTF_8); + Delete delete = jsonMapper.readValue(deleteJsonStr, Delete.class); + restServiceServer = MockRestServiceServer.bindTo(apClient).build(); + restServiceServer.expect(times(1), requestTo((String)delete.getObject())) + .andRespond(withStatus(HttpStatus.GONE)); + mockMvc.perform(post("/api/inbox") + .contentType(ACTIVITY_MEDIA_TYPE) + .content(deleteJsonStr)) + .andExpect(status().isAccepted()); + } } diff --git a/src/test/resources/delete_user.json b/src/test/resources/delete_user.json new file mode 100644 index 00000000..b68db011 --- /dev/null +++ b/src/test/resources/delete_user.json @@ -0,0 +1 @@ +{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","sensitive":"as:sensitive","movedTo":{"@id":"as:movedTo","@type":"@id"},"alsoKnownAs":{"@id":"as:alsoKnownAs","@type":"@id"},"Hashtag":"as:Hashtag","ostatus":"http://ostatus.org#","atomUri":"ostatus:atomUri","inReplyToAtomUri":"ostatus:inReplyToAtomUri","conversation":"ostatus:conversation","toot":"http://joinmastodon.org/ns#","Emoji":"toot:Emoji","focalPoint":{"@container":"@list","@id":"toot:focalPoint"},"featured":{"@id":"toot:featured","@type":"@id"},"schema":"http://schema.org#","PropertyValue":"schema:PropertyValue","value":"schema:value"}],"id":"https://example.com/users/deleted#delete","type":"Delete","actor":"https://example.com/users/deleted","to":["https://www.w3.org/ns/activitystreams#Public"],"object":"https://example.com/users/deleted","signature":{"type":"RsaSignature2017","creator":"https://mastodon.social/users/andoniserra#main-key","created":"2019-01-29T14:50:13Z","signatureValue":"svq8NDQeXb0widXDL1jygye+a536L4GFPTT+8euXgdHhzij6y5dIpT+s0I0ZheAIfHEe+k3N5XysQMvJ4Jmh8douWZ14DkZNai5luk4Ftg5v/RynYAY65UgsldTf9XUvAbSiRGAK4s2b8qE3zsQihEHRIUrzb2bgvhKUkr8FuuuNDDDSS9i9bxnzQp8DSVivqdW2zJYm3ARtW7sWKSXoSaiP2KxIfRPC6UdDDSFbRr3zHckxRjsPnfWr8VvhjxggzVYcp4ZIJDqJj0qoy1lyRIRTWaDJwZIjFX7JjE5OVoKBt++IcY6IARpTGVxV4GXeeFMB7/y1tMaZoold6VlqyQ=="}} \ No newline at end of file -- cgit v1.2.3 From 3b00d3c56affd4880daecc834017c6db51fb3dee Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Wed, 30 Jan 2019 13:23:57 +0300 Subject: ActivityPub: log signatures --- src/main/java/com/juick/server/SignatureManager.java | 1 + 1 file changed, 1 insertion(+) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/SignatureManager.java b/src/main/java/com/juick/server/SignatureManager.java index 083a5edf..0f2f1b39 100644 --- a/src/main/java/com/juick/server/SignatureManager.java +++ b/src/main/java/com/juick/server/SignatureManager.java @@ -89,6 +89,7 @@ public class SignatureManager { if (StringUtils.isEmpty(signatureString)) { return AnonymousUser.INSTANCE; } + logger.debug("Signature: {}", signatureString); Signature signature = Signature.fromString(signatureString); Optional context = getContext(URI.create(signature.getKeyId())); if (context.isPresent() && context.get() instanceof Person) { -- cgit v1.2.3 From aad2c76b7bfd38383fab8dd1b10a7ecb87547e4f Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Wed, 30 Jan 2019 14:18:15 +0300 Subject: Fix downloadImage flow --- src/main/java/com/juick/server/CommandsManager.java | 1 + 1 file changed, 1 insertion(+) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/CommandsManager.java b/src/main/java/com/juick/server/CommandsManager.java index 08dafe77..004fbf4f 100644 --- a/src/main/java/com/juick/server/CommandsManager.java +++ b/src/main/java/com/juick/server/CommandsManager.java @@ -537,6 +537,7 @@ public class CommandsManager { } else { try { attachmentFName = HttpUtils.downloadImage(attachment.toURL(), tmpDir).getHost(); + attachmentType = attachmentFName.substring(attachmentFName.length() - 3); } catch (IOException e) { logger.warn("Can not download {}", attachment.toURL()); } -- cgit v1.2.3 From 3f8ec341a8999d810fec2f9a504b98c20e63ce0e Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Wed, 30 Jan 2019 15:44:23 +0300 Subject: fix http authentication flow --- .../java/com/juick/server/SignatureManager.java | 49 +++++++++++----------- .../HTTPSignatureAuthenticationFilter.java | 27 +++++------- 2 files changed, 35 insertions(+), 41 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/SignatureManager.java b/src/main/java/com/juick/server/SignatureManager.java index 0f2f1b39..2cbe243e 100644 --- a/src/main/java/com/juick/server/SignatureManager.java +++ b/src/main/java/com/juick/server/SignatureManager.java @@ -84,37 +84,36 @@ public class SignatureManager { return signature.toString().substring(10); } - public User verifySignature(String method, String path, Map headers) throws IOException { + public User verifySignature(String method, String path, Map headers) { String signatureString = headers.get("signature"); - if (StringUtils.isEmpty(signatureString)) { - return AnonymousUser.INSTANCE; - } - logger.debug("Signature: {}", signatureString); - Signature signature = Signature.fromString(signatureString); - Optional context = getContext(URI.create(signature.getKeyId())); - if (context.isPresent() && context.get() instanceof Person) { - Person person = (Person) context.get(); - Key key = KeystoreManager.publicKeyOf(person); + if (StringUtils.isNotEmpty(signatureString)) { + logger.debug("Signature: {}", signatureString); + Signature signature = Signature.fromString(signatureString); + Optional context = getContext(URI.create(signature.getKeyId())); + if (context.isPresent() && context.get() instanceof Person) { + Person person = (Person) context.get(); + Key key = KeystoreManager.publicKeyOf(person); - Verifier verifier = new Verifier(key, signature); - try { - boolean result = verifier.verify(method, path, headers); - logger.info("signature of {} is valid: {}", signature.getKeyId(), result); - if (result) { - User user = new User(); - user.setUri(URI.create(person.getId())); - if (key.equals(keystoreManager.getPublicKey())) { - return userService.getUserByName(person.getName()); + Verifier verifier = new Verifier(key, signature); + try { + boolean result = verifier.verify(method, path, headers); + logger.info("signature of {} is valid: {}", signature.getKeyId(), result); + if (result) { + User user = new User(); + user.setUri(URI.create(person.getId())); + if (key.equals(keystoreManager.getPublicKey())) { + return userService.getUserByName(person.getName()); + } + return user; + } else { + return AnonymousUser.INSTANCE; } - return user; - } else { - return AnonymousUser.INSTANCE; + } catch (NoSuchAlgorithmException | SignatureException | IOException e) { + logger.warn("Invalid signature {}", signatureString); } - } catch (NoSuchAlgorithmException | SignatureException | IOException e) { - throw new IOException("Invalid signature"); } } - throw new IOException("Person not found"); + return AnonymousUser.INSTANCE; } public Optional getContext(URI contextUri) { try { diff --git a/src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java b/src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java index ffe4a125..3fb16dce 100644 --- a/src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java +++ b/src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java @@ -40,23 +40,18 @@ public class HTTPSignatureAuthenticationFilter extends OncePerRequestFilter { Map headers = Collections.list(request.getHeaderNames()) .stream() .collect(Collectors.toMap(String::toLowerCase, request::getHeader)); - try { - User user = signatureManager.verifySignature(request.getMethod(), request.getRequestURI(), headers); - if (!user.isAnonymous()) { - String userUri = user.getUri().toString(); - if (userUri.length() == 0) { - User userWithPassword = userService.getUserByName(user.getName()); - userWithPassword.setAuthHash(userService.getHashByUID(userWithPassword.getUid())); - Authentication authentication = new UsernamePasswordAuthenticationToken(userWithPassword.getName(), userWithPassword.getCredentials()); - SecurityContextHolder.getContext().setAuthentication(authentication); - } else { - Authentication authentication = new AnonymousAuthenticationToken(userUri, user, Collections.singletonList(new SimpleGrantedAuthority("ROLE_ANONYMOUS"))); - SecurityContextHolder.getContext().setAuthentication(authentication); - } + User user = signatureManager.verifySignature(request.getMethod(), request.getRequestURI(), headers); + if (!user.isAnonymous()) { + String userUri = user.getUri().toString(); + if (userUri.length() == 0) { + User userWithPassword = userService.getUserByName(user.getName()); + userWithPassword.setAuthHash(userService.getHashByUID(userWithPassword.getUid())); + Authentication authentication = new UsernamePasswordAuthenticationToken(userWithPassword.getName(), userWithPassword.getCredentials()); + SecurityContextHolder.getContext().setAuthentication(authentication); + } else { + Authentication authentication = new AnonymousAuthenticationToken(userUri, user, Collections.singletonList(new SimpleGrantedAuthority("ROLE_ANONYMOUS"))); + SecurityContextHolder.getContext().setAuthentication(authentication); } - } catch (IOException e) { - logger.warn(String.format("Signature %s is not verified, context: %s", headers.get("Signature"), - IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8)), e); } } -- cgit v1.2.3 From a2a86393941e9520a8f8a126fbad0c4fad406720 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Wed, 30 Jan 2019 16:01:22 +0300 Subject: info loglevel for signatures --- src/main/java/com/juick/server/SignatureManager.java | 2 +- src/main/java/com/juick/server/api/activity/Profile.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/SignatureManager.java b/src/main/java/com/juick/server/SignatureManager.java index 2cbe243e..032f71ee 100644 --- a/src/main/java/com/juick/server/SignatureManager.java +++ b/src/main/java/com/juick/server/SignatureManager.java @@ -87,7 +87,7 @@ public class SignatureManager { public User verifySignature(String method, String path, Map headers) { String signatureString = headers.get("signature"); if (StringUtils.isNotEmpty(signatureString)) { - logger.debug("Signature: {}", signatureString); + logger.info("Signature: {}", signatureString); Signature signature = Signature.fromString(signatureString); Optional context = getContext(URI.create(signature.getKeyId())); if (context.isPresent() && context.get() instanceof Person) { diff --git a/src/main/java/com/juick/server/api/activity/Profile.java b/src/main/java/com/juick/server/api/activity/Profile.java index b1d325f9..ff9df4fc 100644 --- a/src/main/java/com/juick/server/api/activity/Profile.java +++ b/src/main/java/com/juick/server/api/activity/Profile.java @@ -261,7 +261,7 @@ public class Profile { @PostMapping(value = "/api/inbox", consumes = {Context.LD_JSON_MEDIA_TYPE, Context.ACTIVITYSTREAMS_PROFILE_MEDIA_TYPE}) public ResponseEntity processInbox(InputStream inboxData) throws Exception { String inbox = IOUtils.toString(inboxData, StandardCharsets.UTF_8); - logger.debug("Inbox: {}", inbox); + logger.info("Inbox: {}", inbox); Activity activity = jsonMapper.readValue(inbox, Activity.class); User visitor = UserUtils.getCurrentUser(); if ((StringUtils.isNotEmpty(visitor.getUri().toString()) && visitor.getUri().equals(URI.create(activity.getActor()))) || !visitor.isAnonymous()) { -- cgit v1.2.3 From 6d83f5614a5273ff53f1ddc5f4c614460e228993 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Wed, 30 Jan 2019 16:40:15 +0300 Subject: fix user deletion flow when invalid key is present --- .../java/com/juick/server/SignatureManager.java | 45 +++++++++++----------- .../com/juick/server/api/activity/Profile.java | 4 +- .../HTTPSignatureAuthenticationFilter.java | 25 ++++++------ .../java/com/juick/server/tests/ServerTests.java | 7 +++- 4 files changed, 44 insertions(+), 37 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/SignatureManager.java b/src/main/java/com/juick/server/SignatureManager.java index 032f71ee..c863ae0f 100644 --- a/src/main/java/com/juick/server/SignatureManager.java +++ b/src/main/java/com/juick/server/SignatureManager.java @@ -86,32 +86,33 @@ public class SignatureManager { public User verifySignature(String method, String path, Map headers) { String signatureString = headers.get("signature"); - if (StringUtils.isNotEmpty(signatureString)) { - logger.info("Signature: {}", signatureString); - Signature signature = Signature.fromString(signatureString); - Optional context = getContext(URI.create(signature.getKeyId())); - if (context.isPresent() && context.get() instanceof Person) { - Person person = (Person) context.get(); - Key key = KeystoreManager.publicKeyOf(person); + logger.info("Signature: {}", signatureString); + Signature signature = Signature.fromString(signatureString); + Optional context = getContext(UriComponentsBuilder.fromUriString(signature.getKeyId()) + .fragment(null).build().toUri()); + if (context.isPresent() && context.get() instanceof Person) { + Person person = (Person) context.get(); + Key key = KeystoreManager.publicKeyOf(person); - Verifier verifier = new Verifier(key, signature); - try { - boolean result = verifier.verify(method, path, headers); - logger.info("signature of {} is valid: {}", signature.getKeyId(), result); - if (result) { - User user = new User(); - user.setUri(URI.create(person.getId())); - if (key.equals(keystoreManager.getPublicKey())) { - return userService.getUserByName(person.getName()); - } - return user; - } else { - return AnonymousUser.INSTANCE; + Verifier verifier = new Verifier(key, signature); + try { + boolean result = verifier.verify(method, path, headers); + logger.info("signature of {} is valid: {}", signature.getKeyId(), result); + if (result) { + User user = new User(); + user.setUri(URI.create(person.getId())); + if (key.equals(keystoreManager.getPublicKey())) { + return userService.getUserByName(person.getName()); } - } catch (NoSuchAlgorithmException | SignatureException | IOException e) { - logger.warn("Invalid signature {}", signatureString); + return user; + } else { + return AnonymousUser.INSTANCE; } + } catch (NoSuchAlgorithmException | SignatureException | IOException e) { + logger.warn("Invalid signature {}", signatureString); } + } else { + logger.warn("Unknown keyId"); } return AnonymousUser.INSTANCE; } diff --git a/src/main/java/com/juick/server/api/activity/Profile.java b/src/main/java/com/juick/server/api/activity/Profile.java index ff9df4fc..bd8b9ea8 100644 --- a/src/main/java/com/juick/server/api/activity/Profile.java +++ b/src/main/java/com/juick/server/api/activity/Profile.java @@ -358,9 +358,7 @@ public class Profile { if (activity.getObject() instanceof String) { // Delete gone user if (activity.getActor().equals(activity.getObject())) { - if (signatureManager.getContext(URI.create(activity.getActor())).isEmpty()) { - return new ResponseEntity<>(HttpStatus.ACCEPTED); - } + return new ResponseEntity<>(HttpStatus.ACCEPTED); } } } diff --git a/src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java b/src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java index 3fb16dce..22dc3b9c 100644 --- a/src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java +++ b/src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java @@ -4,6 +4,7 @@ import com.juick.User; import com.juick.server.SignatureManager; import com.juick.service.UserService; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; @@ -40,17 +41,19 @@ public class HTTPSignatureAuthenticationFilter extends OncePerRequestFilter { Map headers = Collections.list(request.getHeaderNames()) .stream() .collect(Collectors.toMap(String::toLowerCase, request::getHeader)); - User user = signatureManager.verifySignature(request.getMethod(), request.getRequestURI(), headers); - if (!user.isAnonymous()) { - String userUri = user.getUri().toString(); - if (userUri.length() == 0) { - User userWithPassword = userService.getUserByName(user.getName()); - userWithPassword.setAuthHash(userService.getHashByUID(userWithPassword.getUid())); - Authentication authentication = new UsernamePasswordAuthenticationToken(userWithPassword.getName(), userWithPassword.getCredentials()); - SecurityContextHolder.getContext().setAuthentication(authentication); - } else { - Authentication authentication = new AnonymousAuthenticationToken(userUri, user, Collections.singletonList(new SimpleGrantedAuthority("ROLE_ANONYMOUS"))); - SecurityContextHolder.getContext().setAuthentication(authentication); + if (StringUtils.isNotEmpty(headers.get("signature"))) { + User user = signatureManager.verifySignature(request.getMethod(), request.getRequestURI(), headers); + if (!user.isAnonymous()) { + String userUri = user.getUri().toString(); + if (userUri.length() == 0) { + User userWithPassword = userService.getUserByName(user.getName()); + userWithPassword.setAuthHash(userService.getHashByUID(userWithPassword.getUid())); + Authentication authentication = new UsernamePasswordAuthenticationToken(userWithPassword.getName(), userWithPassword.getCredentials()); + SecurityContextHolder.getContext().setAuthentication(authentication); + } else { + Authentication authentication = new AnonymousAuthenticationToken(userUri, user, Collections.singletonList(new SimpleGrantedAuthority("ROLE_ANONYMOUS"))); + SecurityContextHolder.getContext().setAuthentication(authentication); + } } } } diff --git a/src/test/java/com/juick/server/tests/ServerTests.java b/src/test/java/com/juick/server/tests/ServerTests.java index 5ee8b1f4..1e56668d 100644 --- a/src/test/java/com/juick/server/tests/ServerTests.java +++ b/src/test/java/com/juick/server/tests/ServerTests.java @@ -1896,12 +1896,17 @@ public class ServerTests { Delete delete = jsonMapper.readValue(deleteJsonStr, Delete.class); ClientHttpRequestFactory originalRequestFactory = apClient.getRequestFactory(); MockRestServiceServer restServiceServer = MockRestServiceServer.createServer(apClient); - restServiceServer.expect(times(1), requestTo((String)delete.getObject())) + restServiceServer.expect(times(2), requestTo((String)delete.getObject())) .andRespond(withStatus(HttpStatus.GONE)); mockMvc.perform(post("/api/inbox") .contentType(ACTIVITY_MEDIA_TYPE) .content(deleteJsonStr)) .andExpect(status().isAccepted()); + mockMvc.perform(post("/api/inbox") + .contentType(ACTIVITY_MEDIA_TYPE) + .content(deleteJsonStr) + .header("Signature", "keyId=\"https://example.com/users/deleted#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"wHoU91JJBsIYcR1W1/57B0oG98t5Aa/TvGPw1B8KQlAp5KhpePnOzD1MZRgivBx7YKO6eYwDx+AX9dn6tjlAvzRLygv21H6UoDZFihWzeE1HM8pY2Pe4EhUgYBN0YuiKUi7W4TS9bDRAJ5vGNPUWATe+2o5Jcbux5cZYXFKKYbLBLD+/IlqPdHA2IXLZ52HFVVfBkPH5sSklV6XJtD/PHLK9R/I9w/mUpj9moUPQu44rR7KvxiGNuHla3vfDtJbkBqLMdScX91EG8373AulXPUiCCF7R2lJB0fFQedm2nSbcwBoJ32GEyOyOPFgPKG5zd9Fd5TfB1pmA8ZIE0sChfA==\"")) + .andExpect(status().isAccepted()); apClient.setRequestFactory(originalRequestFactory); } } -- cgit v1.2.3 From 0bd3f0541974e7fc82b8ac8930cdf6971fa42b5c Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Thu, 31 Jan 2019 11:21:20 +0300 Subject: reduce logging --- .../java/com/juick/server/SignatureManager.java | 2 +- .../java/com/juick/server/tests/ServerTests.java | 33 ++++++++++++++++++---- 2 files changed, 29 insertions(+), 6 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/SignatureManager.java b/src/main/java/com/juick/server/SignatureManager.java index c863ae0f..755575ce 100644 --- a/src/main/java/com/juick/server/SignatureManager.java +++ b/src/main/java/com/juick/server/SignatureManager.java @@ -125,7 +125,7 @@ public class SignatureManager { } return Optional.of(context); } catch (Exception e) { - logger.warn("REST Exception processing {}", contextUri, e); + logger.warn("REST Exception on {}: {}", contextUri, e.getMessage()); } return Optional.empty(); } diff --git a/src/test/java/com/juick/server/tests/ServerTests.java b/src/test/java/com/juick/server/tests/ServerTests.java index 1e56668d..9e1e68ea 100644 --- a/src/test/java/com/juick/server/tests/ServerTests.java +++ b/src/test/java/com/juick/server/tests/ServerTests.java @@ -79,6 +79,7 @@ import org.springframework.util.DigestUtils; import org.springframework.util.FileSystemUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.web.client.ResourceAccessException; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; @@ -102,6 +103,7 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import java.io.*; +import java.net.ConnectException; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; @@ -1896,16 +1898,37 @@ public class ServerTests { Delete delete = jsonMapper.readValue(deleteJsonStr, Delete.class); ClientHttpRequestFactory originalRequestFactory = apClient.getRequestFactory(); MockRestServiceServer restServiceServer = MockRestServiceServer.createServer(apClient); - restServiceServer.expect(times(2), requestTo((String)delete.getObject())) - .andRespond(withStatus(HttpStatus.GONE)); + restServiceServer.expect(times(2), requestTo((String) delete.getObject())) + .andRespond(withStatus(HttpStatus.GONE)); + restServiceServer.expect(requestTo((String) delete.getObject())) + .andRespond(response -> { + throw new ResourceAccessException("Connection reset"); + }); + mockMvc.perform(post("/api/inbox") + .contentType(ACTIVITY_MEDIA_TYPE) + .content(deleteJsonStr)) + .andExpect(status().isAccepted()); mockMvc.perform(post("/api/inbox") .contentType(ACTIVITY_MEDIA_TYPE) - .content(deleteJsonStr)) + .content(deleteJsonStr) + .header("Signature", "keyId=\"https://example.com/users/deleted#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"wHoU91JJBsIYcR1W1/57B0oG98t5Aa/TvGPw1B8KQlAp5KhpePnOzD1MZRgivBx7YKO6eYwDx+AX9dn6tjlAvzRLygv21H6UoDZFihWzeE1HM8pY2Pe4EhUgYBN0YuiKUi7W4TS9bDRAJ5vGNPUWATe+2o5Jcbux5cZYXFKKYbLBLD+/IlqPdHA2IXLZ52HFVVfBkPH5sSklV6XJtD/PHLK9R/I9w/mUpj9moUPQu44rR7KvxiGNuHla3vfDtJbkBqLMdScX91EG8373AulXPUiCCF7R2lJB0fFQedm2nSbcwBoJ32GEyOyOPFgPKG5zd9Fd5TfB1pmA8ZIE0sChfA==\"")) .andExpect(status().isAccepted()); + apClient.setRequestFactory(originalRequestFactory); + } + @Test + public void handleIncorrectCertificates() throws Exception { + String deleteJsonStr = IOUtils.toString(new ClassPathResource("delete_user.json").getURI(), StandardCharsets.UTF_8); + Delete delete = jsonMapper.readValue(deleteJsonStr, Delete.class); + ClientHttpRequestFactory originalRequestFactory = apClient.getRequestFactory(); + MockRestServiceServer restServiceServer = MockRestServiceServer.createServer(apClient); + restServiceServer.expect(requestTo((String) delete.getObject())) + .andRespond(response -> { + throw new ResourceAccessException("Connection reset"); + }); mockMvc.perform(post("/api/inbox") .contentType(ACTIVITY_MEDIA_TYPE) - .content(deleteJsonStr) - .header("Signature", "keyId=\"https://example.com/users/deleted#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"wHoU91JJBsIYcR1W1/57B0oG98t5Aa/TvGPw1B8KQlAp5KhpePnOzD1MZRgivBx7YKO6eYwDx+AX9dn6tjlAvzRLygv21H6UoDZFihWzeE1HM8pY2Pe4EhUgYBN0YuiKUi7W4TS9bDRAJ5vGNPUWATe+2o5Jcbux5cZYXFKKYbLBLD+/IlqPdHA2IXLZ52HFVVfBkPH5sSklV6XJtD/PHLK9R/I9w/mUpj9moUPQu44rR7KvxiGNuHla3vfDtJbkBqLMdScX91EG8373AulXPUiCCF7R2lJB0fFQedm2nSbcwBoJ32GEyOyOPFgPKG5zd9Fd5TfB1pmA8ZIE0sChfA==\"")) + .content(deleteJsonStr) + .header("Signature", "keyId=\"https://example.com/users/deleted#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"wHoU91JJBsIYcR1W1/57B0oG98t5Aa/TvGPw1B8KQlAp5KhpePnOzD1MZRgivBx7YKO6eYwDx+AX9dn6tjlAvzRLygv21H6UoDZFihWzeE1HM8pY2Pe4EhUgYBN0YuiKUi7W4TS9bDRAJ5vGNPUWATe+2o5Jcbux5cZYXFKKYbLBLD+/IlqPdHA2IXLZ52HFVVfBkPH5sSklV6XJtD/PHLK9R/I9w/mUpj9moUPQu44rR7KvxiGNuHla3vfDtJbkBqLMdScX91EG8373AulXPUiCCF7R2lJB0fFQedm2nSbcwBoJ32GEyOyOPFgPKG5zd9Fd5TfB1pmA8ZIE0sChfA==\"")) .andExpect(status().isAccepted()); apClient.setRequestFactory(originalRequestFactory); } -- cgit v1.2.3 From 49cd7b074e8fa875b060668b0192a9cc35bac140 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Fri, 1 Feb 2019 09:50:24 +0300 Subject: SSE: own messages --- src/main/java/com/juick/server/ServerManager.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/ServerManager.java b/src/main/java/com/juick/server/ServerManager.java index 4ea290fc..46faf9eb 100644 --- a/src/main/java/com/juick/server/ServerManager.java +++ b/src/main/java/com/juick/server/ServerManager.java @@ -25,6 +25,7 @@ import com.juick.service.SubscriptionService; import com.juick.service.UserService; import com.juick.service.component.*; import com.juick.util.MessageUtils; +import org.apache.commons.collections4.ListUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -70,7 +71,7 @@ public class ServerManager implements NotificationListener { private void onJuickPM(final User to, final com.juick.Message jmsg) { - messageEvent(jmsg, Collections.singletonList(to)); + messageEvent(jmsg, Arrays.asList(to, jmsg.getUser())); } private void onJuickMessagePost(final com.juick.Message jmsg, List subscribedUsers) { @@ -86,7 +87,7 @@ public class ServerManager implements NotificationListener { @Override public void processMessageEvent(MessageEvent event) { com.juick.Message jmsg = event.getMessage(); - List subscribedUsers = event.getUsers(); + List subscribedUsers = ListUtils.union(event.getUsers(), Collections.singletonList(jmsg.getUser())); if (jmsg.isService()) { readEvent(jmsg, Collections.singletonList(serviceUser)); return; -- cgit v1.2.3 From 1eed5a8f1053f87b10cb3410e477792075d7f3e4 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Tue, 19 Feb 2019 10:23:07 +0300 Subject: refactor social login flow --- .../java/com/juick/server/api/ApiSocialLogin.java | 51 ++++++++-------------- .../juick/server/www/controllers/SocialLogin.java | 51 ++++++++-------------- 2 files changed, 38 insertions(+), 64 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/api/ApiSocialLogin.java b/src/main/java/com/juick/server/api/ApiSocialLogin.java index 75fd6d11..be306fe9 100644 --- a/src/main/java/com/juick/server/api/ApiSocialLogin.java +++ b/src/main/java/com/juick/server/api/ApiSocialLogin.java @@ -82,6 +82,7 @@ public class ApiSocialLogin { @Inject private ObjectMapper jsonMapper; private ServiceBuilder facebookBuilder, twitterBuilder, vkBuilder; + private OAuth20Service facebookAuthService, vkAuthService; @Value("${twitter_consumer_key:appid}") private String twitterConsumerKey; @@ -117,6 +118,16 @@ public class ApiSocialLogin { verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory) .setAudience(Collections.singletonList(googleClientId)) .build(); + facebookAuthService = facebookBuilder + .apiSecret(FACEBOOK_SECRET) + .callback(FACEBOOK_REDIRECT) + .scope("email") + .build(FacebookApi.instance()); + vkAuthService = vkBuilder + .apiSecret(VK_SECRET) + .scope("friends,wall,offline") + .callback(VK_REDIRECT) + .build(VkontakteApi.instance()); } @GetMapping("/api/_fblogin") @@ -125,13 +136,7 @@ public class ApiSocialLogin { if (StringUtils.isBlank(code)) { String fbstate = UUID.randomUUID().toString(); crosspostService.addFacebookState(fbstate, state); - OAuth20Service facebookAuthService = facebookBuilder - .apiSecret(FACEBOOK_SECRET) - .callback(FACEBOOK_REDIRECT) - .scope("email") - .state(fbstate) - .build(FacebookApi.instance()); - return "redirect:" + facebookAuthService.getAuthorizationUrl(); + return "redirect:" + facebookAuthService.getAuthorizationUrl(fbstate); } String redirectUrl = crosspostService.verifyFacebookState(state); @@ -140,17 +145,10 @@ public class ApiSocialLogin { logger.error("state is missing"); throw new HttpBadRequestException(); } - OAuth20Service facebookService = facebookBuilder - .apiKey(FACEBOOK_APPID) - .apiSecret(FACEBOOK_SECRET) - .callback(FACEBOOK_REDIRECT) - .scope("email") - .state(state) - .build(FacebookApi.instance()); - OAuth2AccessToken token = facebookService.getAccessToken(code); + OAuth2AccessToken token = facebookAuthService.getAccessToken(code); final OAuthRequest meRequest = new OAuthRequest(Verb.GET, "https://graph.facebook.com/v3.2/me?fields=id,name,email"); - facebookService.signRequest(token, meRequest); - String graph = facebookService.execute(meRequest).getBody(); + facebookAuthService.signRequest(token, meRequest); + String graph = facebookAuthService.execute(meRequest).getBody(); if (StringUtils.isBlank(graph)) { logger.error("FACEBOOK GRAPH ERROR"); throw new HttpBadRequestException(); @@ -241,13 +239,7 @@ public class ApiSocialLogin { if (StringUtils.isBlank(code)) { String vkstate = UUID.randomUUID().toString(); crosspostService.addVKState(vkstate, state); - OAuth20Service vkAuthService = vkBuilder - .apiSecret(VK_SECRET) - .scope("friends,wall,offline") - .state(vkstate) - .callback(VK_REDIRECT) - .build(VkontakteApi.instance()); - return "redirect:" + vkAuthService.getAuthorizationUrl(); + return "redirect:" + vkAuthService.getAuthorizationUrl(vkstate); } String redirectUrl = crosspostService.verifyVKState(state); @@ -255,16 +247,11 @@ public class ApiSocialLogin { logger.error("state is missing"); throw new HttpBadRequestException(); } - - OAuth20Service vkService = vkBuilder - .apiKey(VK_APPID) - .apiSecret(VK_SECRET) - .build(VkontakteApi.instance()); - OAuth2AccessToken token = vkService.getAccessToken(code); + OAuth2AccessToken token = vkAuthService.getAccessToken(code); OAuthRequest meRequest = new OAuthRequest(Verb.GET, "https://api.vk.com/method/users.get?fields=screen_name&v=5.73"); - vkService.signRequest(token, meRequest); - String graph = vkService.execute(meRequest).getBody(); + vkAuthService.signRequest(token, meRequest); + String graph = vkAuthService.execute(meRequest).getBody(); com.juick.model.vk.User jsonUser = jsonMapper.readValue(graph, UsersResponse.class).getUsers().get(0); String vkName = jsonUser.getFirstName() + " " + jsonUser.getLastName(); diff --git a/src/main/java/com/juick/server/www/controllers/SocialLogin.java b/src/main/java/com/juick/server/www/controllers/SocialLogin.java index b071b6ca..59b1ec0b 100644 --- a/src/main/java/com/juick/server/www/controllers/SocialLogin.java +++ b/src/main/java/com/juick/server/www/controllers/SocialLogin.java @@ -79,6 +79,7 @@ public class SocialLogin { @Inject private ObjectMapper jsonMapper; private ServiceBuilder facebookBuilder, twitterBuilder, vkBuilder; + private OAuth20Service facebookAuthService, vkAuthService; @Value("${twitter_consumer_key:appid}") private String twitterConsumerKey; @@ -107,6 +108,16 @@ public class SocialLogin { vkBuilder = new ServiceBuilder(VK_APPID); UriComponentsBuilder facebookRedirectBuilder = UriComponentsBuilder.fromUriString(baseUri); facebookRedirectUri = facebookRedirectBuilder.replacePath("/_fblogin").build().toUriString(); + facebookAuthService = facebookBuilder + .apiSecret(FACEBOOK_SECRET) + .callback(facebookRedirectUri) + .scope("email") + .build(FacebookApi.instance()); + vkAuthService = vkBuilder + .apiSecret(VK_SECRET) + .scope("friends,wall,offline") + .callback(VK_REDIRECT) + .build(VkontakteApi.instance()); } @GetMapping("/_fblogin") @@ -120,13 +131,7 @@ public class SocialLogin { state = Utils.getPreviousPageByRequest(request).orElse("https://juick.com/"); } crosspostService.addFacebookState(fbstate, state); - OAuth20Service facebookAuthService = facebookBuilder - .apiSecret(FACEBOOK_SECRET) - .callback(facebookRedirectUri) - .scope("email") - .state(fbstate) - .build(FacebookApi.instance()); - return "redirect:" + facebookAuthService.getAuthorizationUrl(); + return "redirect:" + facebookAuthService.getAuthorizationUrl(fbstate); } String redirectUrl = crosspostService.verifyFacebookState(state); @@ -134,17 +139,10 @@ public class SocialLogin { logger.error("state is missing"); throw new HttpBadRequestException(); } - OAuth20Service facebookService = facebookBuilder - .apiKey(FACEBOOK_APPID) - .apiSecret(FACEBOOK_SECRET) - .callback(facebookRedirectUri) - .scope("email") - .state(state) - .build(FacebookApi.instance()); - OAuth2AccessToken token = facebookService.getAccessToken(code); + OAuth2AccessToken token = facebookAuthService.getAccessToken(code); final OAuthRequest meRequest = new OAuthRequest(Verb.GET, "https://graph.facebook.com/v3.2/me?fields=id,name,link,verified,email"); - facebookService.signRequest(token, meRequest); - String graph = facebookService.execute(meRequest).getBody(); + facebookAuthService.signRequest(token, meRequest); + String graph = facebookAuthService.execute(meRequest).getBody(); if (StringUtils.isBlank(graph)) { logger.error("FACEBOOK GRAPH ERROR"); throw new HttpBadRequestException(); @@ -240,13 +238,7 @@ public class SocialLogin { vkstate = UUID.randomUUID().toString(); Cookie c = new Cookie("vkstate", vkstate); response.addCookie(c); - OAuth20Service vkAuthService = vkBuilder - .apiSecret(VK_SECRET) - .scope("friends,wall,offline") - .state(vkstate) - .callback(VK_REDIRECT) - .build(VkontakteApi.instance()); - return "redirect:" + vkAuthService.getAuthorizationUrl(); + return "redirect:" + vkAuthService.getAuthorizationUrl(vkstate); } if (StringUtils.isBlank(vkstate) || !vkstate.equals(state)) { @@ -256,16 +248,11 @@ public class SocialLogin { c.setMaxAge(0); response.addCookie(c); } - - OAuth20Service vkService = vkBuilder - .apiKey(VK_APPID) - .apiSecret(VK_SECRET) - .build(VkontakteApi.instance()); - OAuth2AccessToken token = vkService.getAccessToken(code); + OAuth2AccessToken token = vkAuthService.getAccessToken(code); OAuthRequest meRequest = new OAuthRequest(Verb.GET, "https://api.vk.com/method/users.get?fields=screen_name&v=5.73"); - vkService.signRequest(token, meRequest); - String graph = vkService.execute(meRequest).getBody(); + vkAuthService.signRequest(token, meRequest); + String graph = vkAuthService.execute(meRequest).getBody(); com.juick.model.vk.User jsonUser = jsonMapper.readValue(graph, UsersResponse.class).getUsers().get(0); String vkName = jsonUser.getFirstName() + " " + jsonUser.getLastName(); -- cgit v1.2.3 From fc7ad5f5849e28c0f0d4d5eed695236d0576b4b3 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Tue, 19 Feb 2019 16:03:55 +0300 Subject: legacy avatar api --- src/main/java/com/juick/server/api/Users.java | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/api/Users.java b/src/main/java/com/juick/server/api/Users.java index 5c6efe0c..03e916d1 100644 --- a/src/main/java/com/juick/server/api/Users.java +++ b/src/main/java/com/juick/server/api/Users.java @@ -18,18 +18,23 @@ package com.juick.server.api; import com.juick.User; +import com.juick.model.AnonymousUser; import com.juick.model.ApplicationStatus; import com.juick.server.util.*; import com.juick.server.www.WebApp; import com.juick.service.*; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import javax.inject.Inject; import java.io.IOException; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -166,6 +171,24 @@ public class Users { throw new HttpNotFoundException(); } + @Deprecated + @GetMapping("/api/avatar") + public ResponseEntity getAvatarUrl(@RequestParam(required = false) String uname, + @RequestParam(required = false) String jid) { + User user = AnonymousUser.INSTANCE; + if (StringUtils.isNotEmpty(uname)) { + user = userService.getUserByName(uname); + } + if (user.isAnonymous() && StringUtils.isNotEmpty(jid)) { + user = userService.getUserByJID(jid); + } + if (!user.isAnonymous()) { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setLocation(URI.create(webApp.getAvatarUrl(user))); + return new ResponseEntity<>(httpHeaders, HttpStatus.MOVED_PERMANENTLY); + } + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } class SecureUser extends User { public String getHash() { return getAuthHash(); -- cgit v1.2.3 From 7f937bdffea9b0219f4fce079ccbad8d233a03b9 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Tue, 19 Feb 2019 16:09:06 +0300 Subject: allow avatar endpoint for anonymous --- src/main/java/com/juick/server/configuration/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/configuration/SecurityConfig.java b/src/main/java/com/juick/server/configuration/SecurityConfig.java index d2d3ab13..c5cb4a55 100644 --- a/src/main/java/com/juick/server/configuration/SecurityConfig.java +++ b/src/main/java/com/juick/server/configuration/SecurityConfig.java @@ -116,7 +116,7 @@ public class SecurityConfig { .addFilterBefore(new HTTPSignatureAuthenticationFilter(signatureManager, userService), BasicAuthenticationFilter.class) .authorizeRequests() .antMatchers(HttpMethod.OPTIONS).permitAll() - .antMatchers("/api/", "/api/messages", "/api/messages/discussions", "/api/users", "/api/thread", "/api/tags", "/api/tlgmbtwbhk", "/api/fbwbhk", + .antMatchers("/api/", "/api/messages", "/api/avatar", "/api/messages/discussions", "/api/users", "/api/thread", "/api/tags", "/api/tlgmbtwbhk", "/api/fbwbhk", "/api/skypebotendpoint", "/api/_fblogin", "/api/_vklogin", "/api/_tglogin", "/api/_google", "/api/signup", "/api/inbox", "/api/events", "/api/info/**").permitAll() .anyRequest().hasRole("USER") .and() -- cgit v1.2.3 From ae3da8bbc97ad839505dc14e3f524249b5802940 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Tue, 19 Feb 2019 19:11:25 +0300 Subject: Return legacy avatar url from legacy avatar endpoint --- src/main/java/com/juick/server/api/Users.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/api/Users.java b/src/main/java/com/juick/server/api/Users.java index 03e916d1..216cd68d 100644 --- a/src/main/java/com/juick/server/api/Users.java +++ b/src/main/java/com/juick/server/api/Users.java @@ -174,7 +174,8 @@ public class Users { @Deprecated @GetMapping("/api/avatar") public ResponseEntity getAvatarUrl(@RequestParam(required = false) String uname, - @RequestParam(required = false) String jid) { + @RequestParam(required = false) String jid, + @RequestParam(required = false, defaultValue = "48") Integer size) { User user = AnonymousUser.INSTANCE; if (StringUtils.isNotEmpty(uname)) { user = userService.getUserByName(uname); @@ -183,8 +184,9 @@ public class Users { user = userService.getUserByJID(jid); } if (!user.isAnonymous()) { + String imagePrefix = size == 32 ? "as" : "a"; HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setLocation(URI.create(webApp.getAvatarUrl(user))); + httpHeaders.setLocation(URI.create(String.format("http://i.juick.com/%s/%d.png", imagePrefix, user.getUid()))); return new ResponseEntity<>(httpHeaders, HttpStatus.MOVED_PERMANENTLY); } return new ResponseEntity<>(HttpStatus.NOT_FOUND); -- cgit v1.2.3 From d7db89b3dc22215152e508b9629632d7a604bbf0 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Wed, 20 Feb 2019 10:05:33 +0300 Subject: return raw image from legacy avatar endpoint --- src/main/java/com/juick/server/api/Users.java | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/api/Users.java b/src/main/java/com/juick/server/api/Users.java index 216cd68d..33b3704b 100644 --- a/src/main/java/com/juick/server/api/Users.java +++ b/src/main/java/com/juick/server/api/Users.java @@ -20,15 +20,16 @@ package com.juick.server.api; import com.juick.User; import com.juick.model.AnonymousUser; import com.juick.model.ApplicationStatus; -import com.juick.server.util.*; +import com.juick.server.util.HttpNotFoundException; +import com.juick.server.util.HttpUtils; +import com.juick.server.util.UserUtils; +import com.juick.server.util.WebUtils; import com.juick.server.www.WebApp; import com.juick.service.*; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -172,10 +173,11 @@ public class Users { } @Deprecated - @GetMapping("/api/avatar") - public ResponseEntity getAvatarUrl(@RequestParam(required = false) String uname, - @RequestParam(required = false) String jid, - @RequestParam(required = false, defaultValue = "48") Integer size) { + @GetMapping(value = "/api/avatar", produces = MediaType.IMAGE_PNG_VALUE) + public byte[] getAvatarUrl( + @RequestParam(required = false) String uname, + @RequestParam(required = false) String jid) + throws IOException { User user = AnonymousUser.INSTANCE; if (StringUtils.isNotEmpty(uname)) { user = userService.getUserByName(uname); @@ -183,13 +185,7 @@ public class Users { if (user.isAnonymous() && StringUtils.isNotEmpty(jid)) { user = userService.getUserByJID(jid); } - if (!user.isAnonymous()) { - String imagePrefix = size == 32 ? "as" : "a"; - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setLocation(URI.create(String.format("http://i.juick.com/%s/%d.png", imagePrefix, user.getUid()))); - return new ResponseEntity<>(httpHeaders, HttpStatus.MOVED_PERMANENTLY); - } - return new ResponseEntity<>(HttpStatus.NOT_FOUND); + return IOUtils.toByteArray(URI.create(webApp.getAvatarUrl(user))); } class SecureUser extends User { public String getHash() { -- cgit v1.2.3 From 42af5984ef2decf3763fab51f274fd9940ec23c7 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Sat, 23 Feb 2019 17:58:54 +0300 Subject: Do not post empty messages --- src/main/java/com/juick/server/CommandsManager.java | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/CommandsManager.java b/src/main/java/com/juick/server/CommandsManager.java index 004fbf4f..f6f29941 100644 --- a/src/main/java/com/juick/server/CommandsManager.java +++ b/src/main/java/com/juick/server/CommandsManager.java @@ -128,6 +128,9 @@ public class CommandsManager { : HttpUtils.downloadImage(attachment.toURL(), tmpDir).getHost(); attachmentType = attachmentFName.substring(attachmentFName.length() - 3); } + if (StringUtils.isEmpty(body) && !haveAttachment) { + return CommandResult.fromString("Empty message"); + } int mid = messagesService.createMessage(user.getUid(), body, attachmentType, tags.getRight()); if (haveAttachment) { String fname = String.format("%d.%s", mid, attachmentType); -- cgit v1.2.3 From 116d87c9473b8135d0466be149f070e067e8b810 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Sat, 23 Feb 2019 18:26:05 +0300 Subject: * Add text descriptions to ActivityPub error codes * Append ActivityPub attachments as urls * Added failing test for Hubzilla activity with Link as object --- .../com/juick/formatters/PlainTextFormatter.java | 8 + .../com/juick/server/api/activity/Profile.java | 67 +++++--- .../java/com/juick/server/tests/ServerTests.java | 21 +++ src/test/resources/hubzilla_activity.json | 185 +++++++++++++++++++++ 4 files changed, 254 insertions(+), 27 deletions(-) create mode 100644 src/test/resources/hubzilla_activity.json (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/formatters/PlainTextFormatter.java b/src/main/java/com/juick/formatters/PlainTextFormatter.java index 378a523f..4cb07950 100644 --- a/src/main/java/com/juick/formatters/PlainTextFormatter.java +++ b/src/main/java/com/juick/formatters/PlainTextFormatter.java @@ -84,6 +84,14 @@ public class PlainTextFormatter { return "https://juick.com/m/" + jmsg.getMid(); } + public static String markdownUrl(String url, String description) { + if (StringUtils.isNotBlank(description)) { + return String.format("[%s](%s)", description, url); + } else { + return url; + } + } + public static String formatPostNumber(com.juick.Message jmsg) { if (jmsg.getRid() > 0) { return String.format("%d/%d", jmsg.getMid(), jmsg.getRid()); diff --git a/src/main/java/com/juick/server/api/activity/Profile.java b/src/main/java/com/juick/server/api/activity/Profile.java index bd8b9ea8..c26941ef 100644 --- a/src/main/java/com/juick/server/api/activity/Profile.java +++ b/src/main/java/com/juick/server/api/activity/Profile.java @@ -3,6 +3,7 @@ package com.juick.server.api.activity; import com.fasterxml.jackson.databind.ObjectMapper; import com.juick.Message; import com.juick.User; +import com.juick.formatters.PlainTextFormatter; import com.juick.model.CommandResult; import com.juick.server.ActivityPubManager; import com.juick.server.CommandsManager; @@ -259,7 +260,7 @@ public class Profile { } @PostMapping(value = "/api/inbox", consumes = {Context.LD_JSON_MEDIA_TYPE, Context.ACTIVITYSTREAMS_PROFILE_MEDIA_TYPE}) - public ResponseEntity processInbox(InputStream inboxData) throws Exception { + public ResponseEntity processInbox(InputStream inboxData) throws Exception { String inbox = IOUtils.toString(inboxData, StandardCharsets.UTF_8); logger.info("Inbox: {}", inbox); Activity activity = jsonMapper.readValue(inbox, Activity.class); @@ -269,7 +270,7 @@ public class Profile { Follow followRequest = (Follow) activity; applicationEventPublisher.publishEvent( new FollowEvent(this, followRequest)); - return new ResponseEntity<>(HttpStatus.ACCEPTED); + return new ResponseEntity<>(CommandResult.fromString("Follow request accepted"), HttpStatus.ACCEPTED); } if (activity instanceof Undo) { @@ -278,10 +279,10 @@ public class Profile { String objectObject = (String) object.get("object"); if (objectType.equals("Follow")) { applicationEventPublisher.publishEvent(new UndoFollowEvent(this, activity.getActor(), objectObject)); - return new ResponseEntity<>(HttpStatus.OK); + return new ResponseEntity<>(CommandResult.fromString("Undo follow request accepted"), HttpStatus.OK); } else if (objectType.equals("Like") || objectType.equals("Announce")) { applicationEventPublisher.publishEvent(new UndoAnnounceEvent(this, activity.getActor(), objectObject)); - return new ResponseEntity<>(HttpStatus.OK); + return new ResponseEntity<>(CommandResult.fromString("Undo like/announce request accepted"), HttpStatus.OK); } } if (activity instanceof Create) { @@ -290,7 +291,7 @@ public class Profile { if (note.get("type").equals("Note")) { URI noteId = URI.create((String) note.get("id")); if (messagesService.replyExists(noteId)) { - return new ResponseEntity<>(HttpStatus.OK); + return new ResponseEntity<>(CommandResult.fromString("Reply already exists"), HttpStatus.OK); } else { String inReplyTo = (String) note.get("inReplyTo"); if (StringUtils.isNotBlank(inReplyTo)) { @@ -298,38 +299,50 @@ public class Profile { String postId = activityPubManager.postId(inReplyTo); User user = new User(); user.setUri(URI.create(activity.getActor())); - String attachment = StringUtils.EMPTY; - if (note.get("attachment") != null && ((List) note.get("attachment")).size() > 0) { - Map attachmentObj = (Map) ((List) note.get("attachment")).get(0); - attachment = (String) attachmentObj.get("url"); - } - String markdown = remarkConverter.convertFragment((String)note.get("content")); - CommandResult result = commandsManager.processCommand(user, String.format("#%s %s", postId, markdown), URI.create(attachment)); + String markdown = remarkConverter.convertFragment((String) note.get("content")); + String commandBody = note.get("attachment") == null ? markdown : + ((List) note.get("attachment")).stream().map(attachmentObj -> { + Map attachment = (Map) attachmentObj; + String attachmentUrl = attachment.get("url"); + String attachmentName = attachment.get("name"); + return PlainTextFormatter.markdownUrl(attachmentUrl, attachmentName); + }).reduce((source, url) -> String.format("%s\n%s", source, url)) + .orElse(markdown); + + CommandResult result = commandsManager.processCommand( + user, String.format("#%s %s", postId, commandBody), + URI.create(StringUtils.EMPTY)); logger.info(jsonMapper.writeValueAsString(result)); if (result.getNewMessage().isPresent()) { messagesService.updateReplyUri(result.getNewMessage().get(), noteId); - return new ResponseEntity<>(HttpStatus.OK); + return new ResponseEntity<>(result, HttpStatus.OK); } else { - return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST); } } else { Message reply = messagesService.getReplyByUri(inReplyTo); if (reply != null) { User user = new User(); user.setUri(URI.create(activity.getActor())); - String attachment = StringUtils.EMPTY; - if (note.get("attachment") != null && ((List) note.get("attachment")).size() > 0) { - Map attachmentObj = (Map) ((List) note.get("attachment")).get(0); - attachment = (String) attachmentObj.get("url"); - } String markdown = remarkConverter.convertFragment((String)note.get("content")); - CommandResult result = commandsManager.processCommand(user, String.format("#%d/%d %s", reply.getMid(), reply.getRid(), markdown), URI.create(attachment)); + String commandBody = note.get("attachment") == null ? markdown : + ((List) note.get("attachment")).stream().map(attachmentObj -> { + Map attachment = (Map) attachmentObj; + String attachmentUrl = attachment.get("url"); + String attachmentName = attachment.get("name"); + return PlainTextFormatter.markdownUrl(attachmentUrl, attachmentName); + }).reduce((source, url) -> String.format("%s\n%s", source, url)) + .orElse(markdown); + CommandResult result = commandsManager.processCommand( + user, + String.format("#%d/%d %s", reply.getMid(), reply.getRid(), commandBody), + URI.create(StringUtils.EMPTY)); logger.info(jsonMapper.writeValueAsString(result)); if (result.getNewMessage().isPresent()) { messagesService.updateReplyUri(result.getNewMessage().get(), noteId); - return new ResponseEntity<>(HttpStatus.OK); + return new ResponseEntity<>(result, HttpStatus.OK); } else { - return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST); } } } @@ -344,25 +357,25 @@ public class Profile { URI actor = URI.create(activity.getActor()); URI reply = URI.create((String)tombstone.get("id")); messagesService.deleteReply(actor, reply); - return new ResponseEntity<>(HttpStatus.OK); + return new ResponseEntity<>(CommandResult.fromString("Delete request accepted"), HttpStatus.OK); } } if (activity instanceof Like || activity instanceof Announce) { applicationEventPublisher.publishEvent(new AnnounceEvent(this, activity.getActor(), (String)activity.getObject())); - return new ResponseEntity<>(HttpStatus.OK); + return new ResponseEntity<>(CommandResult.fromString("Like/announce request accepted"), HttpStatus.OK); } logger.warn("Unknown activity: {}", jsonMapper.writeValueAsString(activity)); - return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + return new ResponseEntity<>(CommandResult.fromString("Unknown activity"), HttpStatus.NOT_IMPLEMENTED); } if (activity instanceof Delete) { if (activity.getObject() instanceof String) { // Delete gone user if (activity.getActor().equals(activity.getObject())) { - return new ResponseEntity<>(HttpStatus.ACCEPTED); + return new ResponseEntity<>(CommandResult.fromString("Delete request accepted"), HttpStatus.ACCEPTED); } } } - return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + return new ResponseEntity<>(CommandResult.fromString("Can not authenticate"), HttpStatus.UNAUTHORIZED); } @PostMapping(value = "/u/", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public User fetchUser(@RequestParam URI uri) { diff --git a/src/test/java/com/juick/server/tests/ServerTests.java b/src/test/java/com/juick/server/tests/ServerTests.java index 9e1e68ea..b1a71c65 100644 --- a/src/test/java/com/juick/server/tests/ServerTests.java +++ b/src/test/java/com/juick/server/tests/ServerTests.java @@ -44,6 +44,7 @@ import com.juick.server.util.ImageUtils; import com.juick.server.www.WebApp; import com.juick.service.*; import com.juick.service.component.MessageEvent; +import com.juick.test.util.MockUtils; import com.juick.util.DateFormattersHolder; import com.juick.util.MessageUtils; import com.mitchellbosecke.pebble.PebbleEngine; @@ -209,6 +210,8 @@ public class ServerTests { private Resource cmykJpeg; @Value("classpath:nojfif.jpg") private Resource nojfif; + @Value("classpath:hubzilla_activity.json") + private Resource hubzillaActivity; @Inject private KeystoreManager testKeystoreManager; @@ -1932,4 +1935,22 @@ public class ServerTests { .andExpect(status().isAccepted()); apClient.setRequestFactory(originalRequestFactory); } + + @Test + public void legacyAvatarEndpoint() throws Exception { + mockMvc.perform(get("/api/avatar") + .param("uname", "unknown")) + .andExpect(status().isOk()) + .andExpect(content().bytes(IOUtils.toByteArray(defaultAvatar.getInputStream()))); + } + @Test + public void federatedAttachmentsAsLinks() throws Exception { + int mid = messagesService.createMessage(ugnich.getUid(), "test", StringUtils.EMPTY, Collections.emptyList()); + Message testMessage = MockUtils.mockMessage(mid, freefd, "reply"); + } + @Test + public void hubzillaActor() throws Exception { + String activity = IOUtils.toString(hubzillaActivity.getInputStream(), StandardCharsets.UTF_8); + Create create = jsonMapper.readValue(activity, Create.class); + } } diff --git a/src/test/resources/hubzilla_activity.json b/src/test/resources/hubzilla_activity.json new file mode 100644 index 00000000..b25aca87 --- /dev/null +++ b/src/test/resources/hubzilla_activity.json @@ -0,0 +1,185 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + "https://ussr.win/apschema/v1.2" + ], + "type": "Create", + "id": "https://ussr.win/activity/e46a6100e4ba83d602eab5fe0e035405a32d7d36094c95dc85c5c9fcd3d93ab2%40ussr.win", + "published": "2019-02-14T11:23:29Z", + "updated": "2019-02-14T11:38:48Z", + "actor": { + "type": "Person", + "id": "https://ussr.win/channel/zlax", + "preferredUsername": "zlax", + "name": "ivan zlax", + "icon": { + "type": "Image", + "mediaType": "image/jpeg", + "url": "https://ussr.win/photo/profile/l/2", + "height": 300, + "width": 300 + }, + "url": { + "type": "Link", + "mediaType": "text/html", + "href": "https://ussr.win/channel/zlax" + }, + "inbox": "https://ussr.win/inbox/zlax", + "outbox": "https://ussr.win/outbox/zlax", + "followers": "https://ussr.win/followers/zlax", + "following": "https://ussr.win/following/zlax", + "endpoints": { + "sharedInbox": "https://ussr.win/inbox" + }, + "publicKey": { + "id": "https://ussr.win/channel/zlax/public_key_pem", + "owner": "https://ussr.win/channel/zlax", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtFSIKFVzo+9NzzY0xho9\nA8CqO0j12f3HEOZUDBDj1NBbQ6Cj1p1f5mB1AlRr+a05fqpraCWIJyCXVTtdyjem\niQ/ObT2PuZIhAY04/ptv78S9EM9ImZi2aDNWp2nWQu23dJajb2TMpIxGNS1M3DVR\ngXwRVEVIEEDzftWx7vF4335/2uJvPfwTzKI3pR972xp1iZCd/G3BtYzUW4swIFSu\n+5ZLKhgwO6A4Ge5+sn2k6x+K56YCgeEEQheOg+PbggIQ68xfvYCQyfuShqAXEadv\nOvQ1dN8WO+tgKqYDRQgILIONB8/2/XXMoQbSVRZs2GmLKXHhIeVKiSZ1vKwHsPx1\nCYUayQo/BB1JpbjkmyDQcHAX+KZ7PQUhpQn976f6Ycp5rznvBH5Zm96VKySaGMy7\nGWefafOfcCNfVZpq17gPPfSbC0XOHdvm6T1e1OPwFl/ho9HxatbA60DIDEd7xf4V\nq9aTSZ2MeQW5JXaxJXSgwucPZ9mhVfucFtCyLdlHZ7gA5yJ+pyYmMe8pjCSNWztD\nUOoJC46vv2l1RbMGTJy2AY9ZuPvyrmJHlsRsTYcmDYRFSk6sgGi1eswSDHlruE3u\nKG4E0nsA8qxzgzpCViQV4DIgXhMInBwo7ZhSyWD+tF2a4tpXorHqmg35x/aqJlaq\nE2xF+yrfh/Kx8O+7P6C1aGcCAwEAAQ==\n-----END PUBLIC KEY-----\n" + }, + "nomadicLocations": [ + { + "id": "https://ussr.win/locs/zlax", + "type": "nomadicLocation", + "locationAddress": "acct:zlax@ussr.win", + "locationPrimary": true, + "locationDeleted": false + } + ] + }, + "object": { + "type": "Note", + "id": "https://ussr.win/item/e46a6100e4ba83d602eab5fe0e035405a32d7d36094c95dc85c5c9fcd3d93ab2%40ussr.win", + "published": "2019-02-14T11:23:29Z", + "updated": "2019-02-14T11:38:48Z", + "url": { + "type": "text/html", + "rel": "alternate", + "href": "https://ussr.win/channel/zlax/?f=&mid=e46a6100e4ba83d602eab5fe0e035405a32d7d36094c95dc85c5c9fcd3d93ab2@ussr.win" + }, + "attributedTo": "https://ussr.win/channel/zlax", + "content": "#^https://twitter.com/Dalatrm/status/1095677403198906369
#^https://twitter.com/Dalatrm/status/1095677401433022466
\"Image/photo\"

\"Image/photo\"
Продюсер BBC признал постановочными сцены после химатаки в Сирии
#^https://www.vedomosti.ru/politics/news/2019/02/14/794104-bbc-himataki
Сцены в госпитале сирийского города Дума после предполагаемой химической атаки были постановочными для достижения «максимального эффекта». Об этом в своем твиттере написал продюсер BBC по Сирии Риам Далати. «Спустя почти шесть месяцев расследования я могу подтвердить без тени сомнения, что сцены в госпитале Думы были постановочные. Погибших в больнице не было», — признал он.

Сам факт химической атаки, якобы имевшей место 7 апреля 2018 г., пока не подтвержден Организацией по запрещению химического оружия (ОЗХО). «Атака была, зарин не использовался, но мы должны дождаться, когда ОЗХО подтвердит, использовался ли хлор или что-то еще. Однако все остальное вокруг атаки было создано для максимального эффекта», — написал Далати.

Информацию о применении в сирийской Думе химического оружия с использованием хлора и нервно-паралитического агента распространили общественные организации, в том числе «Белые каски». По данным организации, жертвами атаки стали около 70 человек.

#bbc #capitalism #conspiracy #history #hoax #metaprogramming #revision #syria #terrorism #uk #war #whitehelments", + "actor": { + "type": "Person", + "id": "https://ussr.win/channel/zlax", + "preferredUsername": "zlax", + "name": "ivan zlax", + "icon": { + "type": "Image", + "mediaType": "image/jpeg", + "url": "https://ussr.win/photo/profile/l/2", + "height": 300, + "width": 300 + }, + "url": { + "type": "Link", + "mediaType": "text/html", + "href": "https://ussr.win/channel/zlax" + }, + "inbox": "https://ussr.win/inbox/zlax", + "outbox": "https://ussr.win/outbox/zlax", + "followers": "https://ussr.win/followers/zlax", + "following": "https://ussr.win/following/zlax", + "endpoints": { + "sharedInbox": "https://ussr.win/inbox" + }, + "publicKey": { + "id": "https://ussr.win/channel/zlax/public_key_pem", + "owner": "https://ussr.win/channel/zlax", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtFSIKFVzo+9NzzY0xho9\nA8CqO0j12f3HEOZUDBDj1NBbQ6Cj1p1f5mB1AlRr+a05fqpraCWIJyCXVTtdyjem\niQ/ObT2PuZIhAY04/ptv78S9EM9ImZi2aDNWp2nWQu23dJajb2TMpIxGNS1M3DVR\ngXwRVEVIEEDzftWx7vF4335/2uJvPfwTzKI3pR972xp1iZCd/G3BtYzUW4swIFSu\n+5ZLKhgwO6A4Ge5+sn2k6x+K56YCgeEEQheOg+PbggIQ68xfvYCQyfuShqAXEadv\nOvQ1dN8WO+tgKqYDRQgILIONB8/2/XXMoQbSVRZs2GmLKXHhIeVKiSZ1vKwHsPx1\nCYUayQo/BB1JpbjkmyDQcHAX+KZ7PQUhpQn976f6Ycp5rznvBH5Zm96VKySaGMy7\nGWefafOfcCNfVZpq17gPPfSbC0XOHdvm6T1e1OPwFl/ho9HxatbA60DIDEd7xf4V\nq9aTSZ2MeQW5JXaxJXSgwucPZ9mhVfucFtCyLdlHZ7gA5yJ+pyYmMe8pjCSNWztD\nUOoJC46vv2l1RbMGTJy2AY9ZuPvyrmJHlsRsTYcmDYRFSk6sgGi1eswSDHlruE3u\nKG4E0nsA8qxzgzpCViQV4DIgXhMInBwo7ZhSyWD+tF2a4tpXorHqmg35x/aqJlaq\nE2xF+yrfh/Kx8O+7P6C1aGcCAwEAAQ==\n-----END PUBLIC KEY-----\n" + }, + "nomadicLocations": [ + { + "id": "https://ussr.win/locs/zlax", + "type": "nomadicLocation", + "locationAddress": "acct:zlax@ussr.win", + "locationPrimary": true, + "locationDeleted": false + } + ] + }, + "tag": [ + { + "id": "https://ussr.win/search?tag=metaprogramming", + "name": "#metaprogramming" + }, + { + "id": "https://ussr.win/search?tag=whitehelments", + "name": "#whitehelments" + }, + { + "id": "https://ussr.win/search?tag=capitalism", + "name": "#capitalism" + }, + { + "id": "https://ussr.win/search?tag=conspiracy", + "name": "#conspiracy" + }, + { + "id": "https://ussr.win/search?tag=terrorism", + "name": "#terrorism" + }, + { + "id": "https://ussr.win/search?tag=revision", + "name": "#revision" + }, + { + "id": "https://ussr.win/search?tag=history", + "name": "#history" + }, + { + "id": "https://ussr.win/search?tag=syria", + "name": "#syria" + }, + { + "id": "https://ussr.win/search?tag=hoax", + "name": "#hoax" + }, + { + "id": "https://ussr.win/search?tag=bbc", + "name": "#bbc" + }, + { + "id": "https://ussr.win/search?tag=war", + "name": "#war" + }, + { + "id": "https://ussr.win/search?tag=uk", + "name": "#uk" + } + ], + "attachment": [ + { + "type": "Image", + "url": "https://ussr.win/photo/6776e315a9692860af53c19518e524f50ab047a4317435403532c922a7c484d4-2.jpg" + }, + { + "type": "Image", + "url": "https://ussr.win/photo/afb21af40be39ad19589dec5dfa0161b2364b59331bba95aae823e68b3acfbf3-2.jpg" + } + ], + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://ussr.win/followers/zlax" + ] + }, + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://ussr.win/followers/zlax" + ], + "signature": { + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1" + ], + "type": "RsaSignature2017", + "nonce": "c4f8b31f4277d4322a528697d550aefd78a4a7709f0da25285032f7529d3f550", + "creator": "https://ussr.win/channel/zlax/public_key_pem", + "created": "2019-02-14T11:39:01Z", + "signatureValue": "WnXFcSm3nR1Su4QwSVJq6vQ7xdtPQTfs5iTMcAcxasoGHphJaAZRISV1QRPuMhw5jap4Lmb4ZaFRfqGm4M4HND+mI06dC2HlgCh0vnNvGsbcDa2RybSnIUo/xswEa/Mcpf6uy2dcLwaZY6tXOkkJmtZOseN54CUO3bQHbd3KmSiqkvREMXv2+qrVJhohZ4R9tLXamUTkoJmq1wSS2s8XSUlmO2v+Th3OAEoHeiS1SehglvIzua83IKlU2/iAs8akGVyYng5uLO7YeUsmiKHggef1ss8XKcNuhaAW8b3DUTnUwBZCPiiCoeXEQMfYdciCtTAKARHLr5RlHPE7etf1Fqk9Pozo/b2EDpfTxbZ26ZKY02pl4g82u95L43hTmCLbZ+wbKt5M0uHRld+/WkJrkIVS2Vj0MJ84Pp94Ij5A0MaXnBDiIj8YpvbRWWVEdXWKs/N8Dy0c11NiHCPhurZTMQDwv96bBphLQGlTp4USYjbKlS95JyyxbfjRbbubM4Q3MGsZFWbRi7C0exvZiw8zHBGn3hnbnZsDdlDax2weuXsSzHPlw8DxG6XEXZy3GWuK4LlQCVXaa9AcSd9LODVIrxaelr0pdW38a2rot1rlkGHdYPR8oD5zV3fj4kWs3n68mU9VJRTvubbX1zKV8EIX9UvVJ6wuRHqyVOFKJPkKloc=" + } +} \ No newline at end of file -- cgit v1.2.3 From b0898ebd93a6aabdb58be182c3f8e9ac4f0b44b7 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Sat, 23 Feb 2019 19:58:37 +0300 Subject: Fix hubzilla deserialization --- .../activity/helpers/ActivityIdDeserializer.java | 22 ++++++++++++++++++++++ .../activity/helpers/LinkValueDeserializer.java | 21 +++++++++++++++++++++ .../juick/server/api/activity/model/Activity.java | 4 ++++ .../juick/server/api/activity/model/Context.java | 3 +++ 4 files changed, 50 insertions(+) create mode 100644 src/main/java/com/juick/server/api/activity/helpers/ActivityIdDeserializer.java create mode 100644 src/main/java/com/juick/server/api/activity/helpers/LinkValueDeserializer.java (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/api/activity/helpers/ActivityIdDeserializer.java b/src/main/java/com/juick/server/api/activity/helpers/ActivityIdDeserializer.java new file mode 100644 index 00000000..de43dd5c --- /dev/null +++ b/src/main/java/com/juick/server/api/activity/helpers/ActivityIdDeserializer.java @@ -0,0 +1,22 @@ +package com.juick.server.api.activity.helpers; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; + +public class ActivityIdDeserializer extends JsonDeserializer { + @Override + public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { + JsonToken jsonToken = p.getCurrentToken(); + if (jsonToken == JsonToken.VALUE_EMBEDDED_OBJECT) { + JsonNode node = p.getCodec().readTree(p); + return node.get("id").textValue(); + } + return p.getValueAsString(); + } +} diff --git a/src/main/java/com/juick/server/api/activity/helpers/LinkValueDeserializer.java b/src/main/java/com/juick/server/api/activity/helpers/LinkValueDeserializer.java new file mode 100644 index 00000000..a635ea95 --- /dev/null +++ b/src/main/java/com/juick/server/api/activity/helpers/LinkValueDeserializer.java @@ -0,0 +1,21 @@ +package com.juick.server.api.activity.helpers; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; + +public class LinkValueDeserializer extends JsonDeserializer { + @Override + public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonToken jsonToken = p.getCurrentToken(); + if (jsonToken == JsonToken.VALUE_STRING) { + return p.getValueAsString(); + } + JsonNode node = p.getCodec().readTree(p); + return node.get("href").textValue(); + } +} diff --git a/src/main/java/com/juick/server/api/activity/model/Activity.java b/src/main/java/com/juick/server/api/activity/model/Activity.java index ec126b88..2af14479 100644 --- a/src/main/java/com/juick/server/api/activity/model/Activity.java +++ b/src/main/java/com/juick/server/api/activity/model/Activity.java @@ -1,7 +1,11 @@ package com.juick.server.api.activity.model; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.juick.server.api.activity.helpers.ActivityIdDeserializer; + public abstract class Activity extends Context { + @JsonDeserialize(using = ActivityIdDeserializer.class) private String actor; private Object object; diff --git a/src/main/java/com/juick/server/api/activity/model/Context.java b/src/main/java/com/juick/server/api/activity/model/Context.java index 515ee3da..2ba4606e 100644 --- a/src/main/java/com/juick/server/api/activity/model/Context.java +++ b/src/main/java/com/juick/server/api/activity/model/Context.java @@ -4,6 +4,8 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.juick.server.api.activity.helpers.LinkValueDeserializer; import com.juick.server.api.activity.model.activities.*; import com.juick.server.api.activity.model.objects.*; @@ -46,6 +48,7 @@ public abstract class Context { private Instant published; + @JsonDeserialize(using = LinkValueDeserializer.class) private String url; private List to; -- cgit v1.2.3 From 0682e5c1a3f71c3bbb23ac646d2a4e54a472ac06 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Sat, 23 Feb 2019 21:32:34 +0300 Subject: Announce object may be Context --- src/main/java/com/juick/server/api/activity/Profile.java | 4 +++- src/test/java/com/juick/server/tests/ServerTests.java | 2 +- src/test/resources/announce.json | 1 + src/test/resources/note_document.json | 1 - 4 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 src/test/resources/announce.json delete mode 100644 src/test/resources/note_document.json (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/api/activity/Profile.java b/src/main/java/com/juick/server/api/activity/Profile.java index c26941ef..f5d8025c 100644 --- a/src/main/java/com/juick/server/api/activity/Profile.java +++ b/src/main/java/com/juick/server/api/activity/Profile.java @@ -361,7 +361,9 @@ public class Profile { } } if (activity instanceof Like || activity instanceof Announce) { - applicationEventPublisher.publishEvent(new AnnounceEvent(this, activity.getActor(), (String)activity.getObject())); + String messageUri = activity.getObject() instanceof String ? (String) activity.getObject() + : ((Context) activity.getObject()).getId(); + applicationEventPublisher.publishEvent(new AnnounceEvent(this, activity.getActor(), messageUri)); return new ResponseEntity<>(CommandResult.fromString("Like/announce request accepted"), HttpStatus.OK); } logger.warn("Unknown activity: {}", jsonMapper.writeValueAsString(activity)); diff --git a/src/test/java/com/juick/server/tests/ServerTests.java b/src/test/java/com/juick/server/tests/ServerTests.java index a4b3f95c..9569c45d 100644 --- a/src/test/java/com/juick/server/tests/ServerTests.java +++ b/src/test/java/com/juick/server/tests/ServerTests.java @@ -211,7 +211,7 @@ public class ServerTests { private Resource nojfif; @Value("classpath:hubzilla_activity.json") private Resource hubzillaActivity; - @Value("classpath:note_document.json") + @Value("classpath:announce.json") private Resource noteWithDocument; @Inject diff --git a/src/test/resources/announce.json b/src/test/resources/announce.json new file mode 100644 index 00000000..4ae4a2ad --- /dev/null +++ b/src/test/resources/announce.json @@ -0,0 +1 @@ +{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","sensitive":"as:sensitive","movedTo":{"@id":"as:movedTo","@type":"@id"},"alsoKnownAs":{"@id":"as:alsoKnownAs","@type":"@id"},"Hashtag":"as:Hashtag","ostatus":"http://ostatus.org#","atomUri":"ostatus:atomUri","inReplyToAtomUri":"ostatus:inReplyToAtomUri","conversation":"ostatus:conversation","toot":"http://joinmastodon.org/ns#","Emoji":"toot:Emoji","focalPoint":{"@container":"@list","@id":"toot:focalPoint"},"featured":{"@id":"toot:featured","@type":"@id"},"schema":"http://schema.org#","PropertyValue":"schema:PropertyValue","value":"schema:value"}],"id":"https://mastodon.social/users/armitage/statuses/101641036945418885/activity","type":"Announce","actor":"https://mastodon.social/users/armitage","published":"2019-02-23T10:52:22Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://juick.com/u/netneladno","https://mastodon.social/users/armitage/followers"],"atomUri":"https://mastodon.social/users/armitage/statuses/101641036945418885/activity","object":{"id":"https://juick.com/n/2936427-0","type":"Note","summary":null,"inReplyTo":null,"published":"2019-02-23T10:13:59Z","url":"https://juick.com/m/2936427","attributedTo":"https://juick.com/u/netneladno","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://mastodon.social/users/netneladno/followers"],"sensitive":false,"atomUri":null,"inReplyToAtomUri":null,"conversation":"tag:mastodon.social,2019-02-23:objectId=86866293:objectType=Conversation","content":"\u003cp\u003e\u003c/p\u003e\n","attachment":[{"type":"Document","mediaType":"image/jpeg","url":"https://i.juick.com/photos-1024/2936427.jpg","name":null}],"tag":[]},"signature":{"type":"RsaSignature2017","creator":"https://mastodon.social/users/armitage#main-key","created":"2019-02-23T10:52:22Z","signatureValue":"hEbN5wumGY98T/t5ZbgBLxQNEHX6PJgO9dGwIdTq0T+GJ518J3duhHlhljBQkGgnH/68aSBWHxDQ+57YRVlR6lukvl0hKCg8Rx1NOYhtU+9OH//MH2uMt/XXh2pE1dv+RVA7GS4zkCMjKKMhY6WNzbJQS4FfvaCmmEn6gqiCSXbo1lLo4bkhBRyfpAH8Z9Z8/wEbRAl0qCVZlim8ELOZ8rWVfnmgF3gGDWwpgzSEqIbxEytpHtNeYC+ZC3FVlxMAomvxuyj2XCv6B854ol5UQ30O8MLVNiTCoUa/7w6k/I3pmN1etpJ53XD8Si0Qg6QKHO8R3Wp6S9LSoF0Hj/GiDQ=="}} \ No newline at end of file diff --git a/src/test/resources/note_document.json b/src/test/resources/note_document.json deleted file mode 100644 index 4ae4a2ad..00000000 --- a/src/test/resources/note_document.json +++ /dev/null @@ -1 +0,0 @@ -{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","sensitive":"as:sensitive","movedTo":{"@id":"as:movedTo","@type":"@id"},"alsoKnownAs":{"@id":"as:alsoKnownAs","@type":"@id"},"Hashtag":"as:Hashtag","ostatus":"http://ostatus.org#","atomUri":"ostatus:atomUri","inReplyToAtomUri":"ostatus:inReplyToAtomUri","conversation":"ostatus:conversation","toot":"http://joinmastodon.org/ns#","Emoji":"toot:Emoji","focalPoint":{"@container":"@list","@id":"toot:focalPoint"},"featured":{"@id":"toot:featured","@type":"@id"},"schema":"http://schema.org#","PropertyValue":"schema:PropertyValue","value":"schema:value"}],"id":"https://mastodon.social/users/armitage/statuses/101641036945418885/activity","type":"Announce","actor":"https://mastodon.social/users/armitage","published":"2019-02-23T10:52:22Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://juick.com/u/netneladno","https://mastodon.social/users/armitage/followers"],"atomUri":"https://mastodon.social/users/armitage/statuses/101641036945418885/activity","object":{"id":"https://juick.com/n/2936427-0","type":"Note","summary":null,"inReplyTo":null,"published":"2019-02-23T10:13:59Z","url":"https://juick.com/m/2936427","attributedTo":"https://juick.com/u/netneladno","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://mastodon.social/users/netneladno/followers"],"sensitive":false,"atomUri":null,"inReplyToAtomUri":null,"conversation":"tag:mastodon.social,2019-02-23:objectId=86866293:objectType=Conversation","content":"\u003cp\u003e\u003c/p\u003e\n","attachment":[{"type":"Document","mediaType":"image/jpeg","url":"https://i.juick.com/photos-1024/2936427.jpg","name":null}],"tag":[]},"signature":{"type":"RsaSignature2017","creator":"https://mastodon.social/users/armitage#main-key","created":"2019-02-23T10:52:22Z","signatureValue":"hEbN5wumGY98T/t5ZbgBLxQNEHX6PJgO9dGwIdTq0T+GJ518J3duhHlhljBQkGgnH/68aSBWHxDQ+57YRVlR6lukvl0hKCg8Rx1NOYhtU+9OH//MH2uMt/XXh2pE1dv+RVA7GS4zkCMjKKMhY6WNzbJQS4FfvaCmmEn6gqiCSXbo1lLo4bkhBRyfpAH8Z9Z8/wEbRAl0qCVZlim8ELOZ8rWVfnmgF3gGDWwpgzSEqIbxEytpHtNeYC+ZC3FVlxMAomvxuyj2XCv6B854ol5UQ30O8MLVNiTCoUa/7w6k/I3pmN1etpJ53XD8Si0Qg6QKHO8R3Wp6S9LSoF0Hj/GiDQ=="}} \ No newline at end of file -- cgit v1.2.3 From 32a92d0a8d3c99c3df4abf0aad147f868fd36775 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Sat, 23 Feb 2019 22:52:45 +0300 Subject: Fix activity id deserializer --- .../juick/server/api/activity/helpers/ActivityIdDeserializer.java | 2 +- src/test/java/com/juick/server/tests/ServerTests.java | 5 +++++ src/test/resources/hubzilla_follow.json | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/hubzilla_follow.json (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/api/activity/helpers/ActivityIdDeserializer.java b/src/main/java/com/juick/server/api/activity/helpers/ActivityIdDeserializer.java index de43dd5c..ba8cfb87 100644 --- a/src/main/java/com/juick/server/api/activity/helpers/ActivityIdDeserializer.java +++ b/src/main/java/com/juick/server/api/activity/helpers/ActivityIdDeserializer.java @@ -13,7 +13,7 @@ public class ActivityIdDeserializer extends JsonDeserializer { @Override public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { JsonToken jsonToken = p.getCurrentToken(); - if (jsonToken == JsonToken.VALUE_EMBEDDED_OBJECT) { + if (jsonToken == JsonToken.START_OBJECT) { JsonNode node = p.getCodec().readTree(p); return node.get("id").textValue(); } diff --git a/src/test/java/com/juick/server/tests/ServerTests.java b/src/test/java/com/juick/server/tests/ServerTests.java index 9569c45d..a509b36d 100644 --- a/src/test/java/com/juick/server/tests/ServerTests.java +++ b/src/test/java/com/juick/server/tests/ServerTests.java @@ -211,6 +211,8 @@ public class ServerTests { private Resource nojfif; @Value("classpath:hubzilla_activity.json") private Resource hubzillaActivity; + @Value("classpath:hubzilla_follow.json") + private Resource hubzillaFollow; @Value("classpath:announce.json") private Resource noteWithDocument; @@ -1955,5 +1957,8 @@ public class ServerTests { public void hubzillaActor() throws Exception { String activity = IOUtils.toString(hubzillaActivity.getInputStream(), StandardCharsets.UTF_8); Create create = jsonMapper.readValue(activity, Create.class); + String followData = IOUtils.toString(hubzillaFollow.getInputStream(), StandardCharsets.UTF_8); + Follow follow = jsonMapper.readValue(followData, Follow.class); + assertThat(follow.getActor(), is("https://ussr.win/channel/zlax")); } } diff --git a/src/test/resources/hubzilla_follow.json b/src/test/resources/hubzilla_follow.json new file mode 100644 index 00000000..fff33c36 --- /dev/null +++ b/src/test/resources/hubzilla_follow.json @@ -0,0 +1 @@ +{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1","https://ussr.win/apschema/v1.3"],"id":"https://ussr.win/follow/1216","type":"Follow","actor":{"type":"Person","id":"https://ussr.win/channel/zlax","preferredUsername":"zlax","name":"ivan zlax","icon":{"type":"Image","mediaType":"image/jpeg","url":"https://ussr.win/photo/profile/l/2","height":300,"width":300},"url":{"type":"Link","mediaType":"text/html","href":"https://ussr.win/channel/zlax"},"inbox":"https://ussr.win/inbox/zlax","outbox":"https://ussr.win/outbox/zlax","followers":"https://ussr.win/followers/zlax","following":"https://ussr.win/following/zlax","endpoints":{"sharedInbox":"https://ussr.win/inbox"},"publicKey":{"id":"https://ussr.win/channel/zlax/public_key_pem","owner":"https://ussr.win/channel/zlax","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtFSIKFVzo+9NzzY0xho9\nA8CqO0j12f3HEOZUDBDj1NBbQ6Cj1p1f5mB1AlRr+a05fqpraCWIJyCXVTtdyjem\niQ/ObT2PuZIhAY04/ptv78S9EM9ImZi2aDNWp2nWQu23dJajb2TMpIxGNS1M3DVR\ngXwRVEVIEEDzftWx7vF4335/2uJvPfwTzKI3pR972xp1iZCd/G3BtYzUW4swIFSu\n+5ZLKhgwO6A4Ge5+sn2k6x+K56YCgeEEQheOg+PbggIQ68xfvYCQyfuShqAXEadv\nOvQ1dN8WO+tgKqYDRQgILIONB8/2/XXMoQbSVRZs2GmLKXHhIeVKiSZ1vKwHsPx1\nCYUayQo/BB1JpbjkmyDQcHAX+KZ7PQUhpQn976f6Ycp5rznvBH5Zm96VKySaGMy7\nGWefafOfcCNfVZpq17gPPfSbC0XOHdvm6T1e1OPwFl/ho9HxatbA60DIDEd7xf4V\nq9aTSZ2MeQW5JXaxJXSgwucPZ9mhVfucFtCyLdlHZ7gA5yJ+pyYmMe8pjCSNWztD\nUOoJC46vv2l1RbMGTJy2AY9ZuPvyrmJHlsRsTYcmDYRFSk6sgGi1eswSDHlruE3u\nKG4E0nsA8qxzgzpCViQV4DIgXhMInBwo7ZhSyWD+tF2a4tpXorHqmg35x/aqJlaq\nE2xF+yrfh/Kx8O+7P6C1aGcCAwEAAQ==\n-----END PUBLIC KEY-----\n"},"nomadicLocations":[{"id":"https://ussr.win/locs/zlax","type":"nomadicLocation","locationAddress":"acct:zlax@ussr.win","locationPrimary":true,"locationDeleted":false}]},"object":"https://juick.com/u/netneladno","to":["https://juick.com/u/netneladno"],"signature":{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1"],"type":"RsaSignature2017","nonce":"1ee5df16edb30d1944fcb07859849ee4293da98c13322c13e2f929b822da91a7","creator":"https://ussr.win/channel/zlax/public_key_pem","created":"2019-02-23T11:09:56Z","signatureValue":"M6551dhNfk0vsSQ6hYvxXVURb7NviNk8ExYBLsax6uYr1oyV/zQJuUtoqufr47t/2NvfzLVn0criLAKbF586siIFd2YBO4wPWtSDPPAWgaQxDrCzRG4PimuavgT0ghIKzmmMnCG6BLC4O0+ohKPUFzaOHgsgdM++TY21OdLQUaauwYkvn9eWYGXzkS5LEnxEKuFpQarOtzChpP9btI9P3iDDRaOqTTlDw0w1xZ8/LqqPCqoA5W8NS+xSyhHsvFi1ApNinFQNM5+e1Gj09Lvtumn2rQE9Smn8sXG9jp5MSBKFdmuG20PDqidfQ3uvZm5qPdwfJnWjZ2VD00aOSji3ahcHJWkbxpNQki2TBb6u2FDABPlUsBvJ2Z23Ubdm+kcjNklmlZeAgcTvWoZhlzushlGjukRwMymRJZXQNIAgVoKbWhvm2w8NX1cQWs1XS211IPl44sT33T5ZoDNMiDPru+XtHWXrZxA2jpLrHec18bZ15y66KTi67s8QNoYn7cUSqETtch7Vuixw2oxQZNfDj0yhthbi7+lFoqB1Ilo37rJx/9wMY9O271n4rBmuSoHmMDVUSZBZxPjpzvm3zS/NjxczwNJ8a8hZSGcDvViHf2Lcqj+qt+DBbI1jtPGRTieR6pVRjxa7MjjZipDMhYczn13poKQ4KK3lYakyaNhjgHg="}} \ No newline at end of file -- cgit v1.2.3 From a7d29047bd49ae8afc887abba0317ba7d4aa49b1 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Sun, 24 Feb 2019 12:21:01 +0300 Subject: Activity object still able to be Map --- src/main/java/com/juick/server/api/activity/Profile.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/api/activity/Profile.java b/src/main/java/com/juick/server/api/activity/Profile.java index f5d8025c..4e375a54 100644 --- a/src/main/java/com/juick/server/api/activity/Profile.java +++ b/src/main/java/com/juick/server/api/activity/Profile.java @@ -362,7 +362,8 @@ public class Profile { } if (activity instanceof Like || activity instanceof Announce) { String messageUri = activity.getObject() instanceof String ? (String) activity.getObject() - : ((Context) activity.getObject()).getId(); + : activity.getObject() instanceof Context ? ((Context) activity.getObject()).getId() + : (String) ((Map)activity.getObject()).get("id"); applicationEventPublisher.publishEvent(new AnnounceEvent(this, activity.getActor(), messageUri)); return new ResponseEntity<>(CommandResult.fromString("Like/announce request accepted"), HttpStatus.OK); } -- cgit v1.2.3 From e9a3361b31ae49a801b03bd2950e38896b1f3863 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Sun, 24 Feb 2019 19:29:36 +0300 Subject: /.well-known/nodeinfo --- .../java/com/juick/server/api/xnodeinfo2/Info.java | 35 ++++++++++++++++++++-- .../server/api/xnodeinfo2/model/NodeInfo.java | 24 ++++++++++++++- .../juick/server/api/xnodeinfo2/model/Server.java | 6 ++++ .../server/api/xnodeinfo2/model/ServiceInfo.java | 3 ++ .../juick/server/api/xnodeinfo2/model/Usage.java | 3 ++ .../server/api/xnodeinfo2/model/UserStats.java | 3 ++ .../juick/server/configuration/SecurityConfig.java | 7 +++-- .../java/com/juick/server/tests/ServerTests.java | 25 ++++++++++++++++ 8 files changed, 101 insertions(+), 5 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/api/xnodeinfo2/Info.java b/src/main/java/com/juick/server/api/xnodeinfo2/Info.java index 36e36bdd..7ecd02cb 100644 --- a/src/main/java/com/juick/server/api/xnodeinfo2/Info.java +++ b/src/main/java/com/juick/server/api/xnodeinfo2/Info.java @@ -1,15 +1,22 @@ package com.juick.server.api.xnodeinfo2; +import com.cliqset.xrd.Link; +import com.cliqset.xrd.XRD; +import com.fasterxml.jackson.annotation.JsonView; import com.juick.server.api.xnodeinfo2.model.*; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.util.UriComponentsBuilder; import javax.inject.Inject; +import java.net.URI; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.util.Arrays; +import java.util.Collections; @RestController public class Info { @@ -18,9 +25,9 @@ public class Info { @Inject private JdbcTemplate jdbcTemplate; - @GetMapping("/.well-known/x-nodeinfo2") - public NodeInfo showNodeInfo() { + private NodeInfo getCurrentNodeInfo(String version) { NodeInfo nodeInfo = new NodeInfo(); + nodeInfo.setVersion(version); Server server = new Server(); server.setBaseUrl(baseUri); server.setName("Juick"); @@ -47,4 +54,28 @@ public class Info { nodeInfo.setUsage(usage); return nodeInfo; } + + @GetMapping(value = "/.well-known/x-nodeinfo2", consumes = MediaType.APPLICATION_JSON_VALUE) + @JsonView(NodeInfo.XNodeInfoView.class) + public NodeInfo showXNodeInfo() { + return getCurrentNodeInfo("1.0"); + } + + @GetMapping(value = "/.well-known/nodeinfo", consumes = MediaType.APPLICATION_JSON_VALUE) + public XRD getNodeInfoLinks() { + Link nodeinfo = new Link(); + nodeinfo.setRel(URI.create("http://nodeinfo.diaspora.software/ns/schema/2.0")); + UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(baseUri); + uriComponentsBuilder.replacePath("/api/nodeinfo/2.0"); + nodeinfo.setHref(uriComponentsBuilder.build().toUri()); + XRD xrd = new XRD(); + xrd.setLinks(Collections.singletonList(nodeinfo)); + return xrd; + } + + @GetMapping(value = "/api/nodeinfo/2.0", consumes = MediaType.APPLICATION_JSON_VALUE) + @JsonView(NodeInfo.NodeInfoView.class) + public NodeInfo showNodeInfo() { + return getCurrentNodeInfo("2.0"); + } } diff --git a/src/main/java/com/juick/server/api/xnodeinfo2/model/NodeInfo.java b/src/main/java/com/juick/server/api/xnodeinfo2/model/NodeInfo.java index 06fe354f..44bd4325 100644 --- a/src/main/java/com/juick/server/api/xnodeinfo2/model/NodeInfo.java +++ b/src/main/java/com/juick/server/api/xnodeinfo2/model/NodeInfo.java @@ -1,27 +1,43 @@ package com.juick.server.api.xnodeinfo2.model; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonView; + import java.util.List; public class NodeInfo { + private String version; + private Server server; private List protocols; private ServiceInfo services; + @JsonView({XNodeInfoView.class, NodeInfoView.class}) public String getVersion() { - return "1.0"; + return version; + } + + public void setVersion(String version) { + this.version = version; } + @JsonView(XNodeInfoView.class) public Server getServer() { return server; } + @JsonView(NodeInfoView.class) + public Server getSoftware() { + return server; + } public void setServer(Server server) { this.server = server; } + @JsonView({XNodeInfoView.class, NodeInfoView.class}) public List getProtocols() { return protocols; } @@ -30,6 +46,7 @@ public class NodeInfo { this.protocols = protocols; } + @JsonView({XNodeInfoView.class, NodeInfoView.class}) public ServiceInfo getServices() { return services; } @@ -38,12 +55,14 @@ public class NodeInfo { this.services = services; } + @JsonView({XNodeInfoView.class, NodeInfoView.class}) public boolean getOpenRegistrations() { return true; } private Usage usage; + @JsonView({XNodeInfoView.class, NodeInfoView.class}) public Usage getUsage() { return usage; } @@ -51,4 +70,7 @@ public class NodeInfo { public void setUsage(Usage usage) { this.usage = usage; } + + public interface NodeInfoView {} + public interface XNodeInfoView {} } diff --git a/src/main/java/com/juick/server/api/xnodeinfo2/model/Server.java b/src/main/java/com/juick/server/api/xnodeinfo2/model/Server.java index a772d268..08c1a696 100644 --- a/src/main/java/com/juick/server/api/xnodeinfo2/model/Server.java +++ b/src/main/java/com/juick/server/api/xnodeinfo2/model/Server.java @@ -1,11 +1,14 @@ package com.juick.server.api.xnodeinfo2.model; +import com.fasterxml.jackson.annotation.JsonView; + public class Server { private String baseUrl; private String name; private String software; private String version; + @JsonView(NodeInfo.XNodeInfoView.class) public String getBaseUrl() { return baseUrl; } @@ -14,6 +17,7 @@ public class Server { this.baseUrl = baseUrl; } + @JsonView({NodeInfo.NodeInfoView.class, NodeInfo.XNodeInfoView.class}) public String getName() { return name; } @@ -22,6 +26,7 @@ public class Server { this.name = name; } + @JsonView(NodeInfo.XNodeInfoView.class) public String getSoftware() { return software; } @@ -30,6 +35,7 @@ public class Server { this.software = software; } + @JsonView({NodeInfo.NodeInfoView.class, NodeInfo.XNodeInfoView.class}) public String getVersion() { return version; } diff --git a/src/main/java/com/juick/server/api/xnodeinfo2/model/ServiceInfo.java b/src/main/java/com/juick/server/api/xnodeinfo2/model/ServiceInfo.java index 5b6d2baa..0114488f 100644 --- a/src/main/java/com/juick/server/api/xnodeinfo2/model/ServiceInfo.java +++ b/src/main/java/com/juick/server/api/xnodeinfo2/model/ServiceInfo.java @@ -1,7 +1,10 @@ package com.juick.server.api.xnodeinfo2.model; +import com.fasterxml.jackson.annotation.JsonView; + import java.util.List; +@JsonView({NodeInfo.NodeInfoView.class, NodeInfo.XNodeInfoView.class}) public class ServiceInfo { private List inbound; private List outbound; diff --git a/src/main/java/com/juick/server/api/xnodeinfo2/model/Usage.java b/src/main/java/com/juick/server/api/xnodeinfo2/model/Usage.java index e04ea48b..f270296f 100644 --- a/src/main/java/com/juick/server/api/xnodeinfo2/model/Usage.java +++ b/src/main/java/com/juick/server/api/xnodeinfo2/model/Usage.java @@ -1,5 +1,8 @@ package com.juick.server.api.xnodeinfo2.model; +import com.fasterxml.jackson.annotation.JsonView; + +@JsonView({NodeInfo.NodeInfoView.class, NodeInfo.XNodeInfoView.class}) public class Usage { private UserStats users; private int localPosts; diff --git a/src/main/java/com/juick/server/api/xnodeinfo2/model/UserStats.java b/src/main/java/com/juick/server/api/xnodeinfo2/model/UserStats.java index 515661e3..cca31be6 100644 --- a/src/main/java/com/juick/server/api/xnodeinfo2/model/UserStats.java +++ b/src/main/java/com/juick/server/api/xnodeinfo2/model/UserStats.java @@ -1,5 +1,8 @@ package com.juick.server.api.xnodeinfo2.model; +import com.fasterxml.jackson.annotation.JsonView; + +@JsonView({NodeInfo.NodeInfoView.class, NodeInfo.XNodeInfoView.class}) public class UserStats { private int total; private int activeHalfyear; diff --git a/src/main/java/com/juick/server/configuration/SecurityConfig.java b/src/main/java/com/juick/server/configuration/SecurityConfig.java index c5cb4a55..df0da16e 100644 --- a/src/main/java/com/juick/server/configuration/SecurityConfig.java +++ b/src/main/java/com/juick/server/configuration/SecurityConfig.java @@ -116,8 +116,11 @@ public class SecurityConfig { .addFilterBefore(new HTTPSignatureAuthenticationFilter(signatureManager, userService), BasicAuthenticationFilter.class) .authorizeRequests() .antMatchers(HttpMethod.OPTIONS).permitAll() - .antMatchers("/api/", "/api/messages", "/api/avatar", "/api/messages/discussions", "/api/users", "/api/thread", "/api/tags", "/api/tlgmbtwbhk", "/api/fbwbhk", - "/api/skypebotendpoint", "/api/_fblogin", "/api/_vklogin", "/api/_tglogin", "/api/_google", "/api/signup", "/api/inbox", "/api/events", "/api/info/**").permitAll() + .antMatchers("/api/", "/api/messages", "/api/avatar", "/api/messages/discussions", + "/api/users", "/api/thread", "/api/tags", "/api/tlgmbtwbhk", "/api/fbwbhk", + "/api/skypebotendpoint", "/api/_fblogin", "/api/_vklogin", "/api/_tglogin", + "/api/_google", "/api/signup", "/api/inbox", "/api/events", "/api/info/**", + "/api/nodeinfo/2.0").permitAll() .anyRequest().hasRole("USER") .and() .anonymous().principal(JuickUser.ANONYMOUS_USER).authorities(JuickUser.ANONYMOUS_AUTHORITY) diff --git a/src/test/java/com/juick/server/tests/ServerTests.java b/src/test/java/com/juick/server/tests/ServerTests.java index a509b36d..5c421f07 100644 --- a/src/test/java/com/juick/server/tests/ServerTests.java +++ b/src/test/java/com/juick/server/tests/ServerTests.java @@ -19,6 +19,7 @@ package com.juick.server.tests; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.gargoylesoftware.htmlunit.CookieManager; import com.gargoylesoftware.htmlunit.WebClient; @@ -1961,4 +1962,28 @@ public class ServerTests { Follow follow = jsonMapper.readValue(followData, Follow.class); assertThat(follow.getActor(), is("https://ussr.win/channel/zlax")); } + @Test + public void nodeinfo() throws Exception { + MvcResult nodeinfoXRD = mockMvc.perform(get("/.well-known/nodeinfo") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn(); + JsonNode node = jsonMapper.readTree(nodeinfoXRD.getResponse().getContentAsString()); + assertThat(node.get("links"), notNullValue()); + String nodeinfoUrl = node.get("links").get(0).get("href").textValue(); + MvcResult nodeinfoData = mockMvc.perform(get(nodeinfoUrl) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn(); + JsonNode nodeinfo = jsonMapper.readTree(nodeinfoData.getResponse().getContentAsString()); + assertThat(nodeinfo.get("software"), notNullValue()); + assertThat(nodeinfo.get("server"), nullValue()); + MvcResult xnodeinfoData = mockMvc.perform(get("/.well-known/x-nodeinfo2") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn(); + JsonNode xnodeinfo = jsonMapper.readTree(xnodeinfoData.getResponse().getContentAsString()); + assertThat(xnodeinfo.get("server"), notNullValue()); + assertThat(xnodeinfo.get("software"), nullValue()); + } } -- cgit v1.2.3 From b077798327ea7d86d8dd4cb1357ce640c795b648 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Sun, 24 Feb 2019 20:18:14 +0300 Subject: Nodeinfo: fix content-type constraints --- src/main/java/com/juick/server/api/xnodeinfo2/Info.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/api/xnodeinfo2/Info.java b/src/main/java/com/juick/server/api/xnodeinfo2/Info.java index 7ecd02cb..b71637b9 100644 --- a/src/main/java/com/juick/server/api/xnodeinfo2/Info.java +++ b/src/main/java/com/juick/server/api/xnodeinfo2/Info.java @@ -55,13 +55,13 @@ public class Info { return nodeInfo; } - @GetMapping(value = "/.well-known/x-nodeinfo2", consumes = MediaType.APPLICATION_JSON_VALUE) + @GetMapping(value = "/.well-known/x-nodeinfo2", produces = MediaType.APPLICATION_JSON_VALUE) @JsonView(NodeInfo.XNodeInfoView.class) public NodeInfo showXNodeInfo() { return getCurrentNodeInfo("1.0"); } - @GetMapping(value = "/.well-known/nodeinfo", consumes = MediaType.APPLICATION_JSON_VALUE) + @GetMapping(value = "/.well-known/nodeinfo", produces = MediaType.APPLICATION_JSON_VALUE) public XRD getNodeInfoLinks() { Link nodeinfo = new Link(); nodeinfo.setRel(URI.create("http://nodeinfo.diaspora.software/ns/schema/2.0")); @@ -73,7 +73,7 @@ public class Info { return xrd; } - @GetMapping(value = "/api/nodeinfo/2.0", consumes = MediaType.APPLICATION_JSON_VALUE) + @GetMapping(value = "/api/nodeinfo/2.0", produces = MediaType.APPLICATION_JSON_VALUE) @JsonView(NodeInfo.NodeInfoView.class) public NodeInfo showNodeInfo() { return getCurrentNodeInfo("2.0"); -- cgit v1.2.3 From e1e416608c2ed864cb7d93fc582dd267aa5a0b5e Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Fri, 1 Mar 2019 10:08:00 +0300 Subject: ImageUtils catch IOException from apache-imaging --- src/main/java/com/juick/server/util/ImageUtils.java | 2 +- src/test/java/com/juick/server/tests/ServerTests.java | 9 +++++++++ src/test/resources/2936611-57.jpg | Bin 0 -> 101818 bytes 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/2936611-57.jpg (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/util/ImageUtils.java b/src/main/java/com/juick/server/util/ImageUtils.java index d16faf8f..2f5d3292 100644 --- a/src/main/java/com/juick/server/util/ImageUtils.java +++ b/src/main/java/com/juick/server/util/ImageUtils.java @@ -108,7 +108,7 @@ public class ImageUtils { } } } - } catch (ImageReadException e) { + } catch (ImageReadException | IOException e) { // failed to read metadata. // nothing to do here, return image as is. } diff --git a/src/test/java/com/juick/server/tests/ServerTests.java b/src/test/java/com/juick/server/tests/ServerTests.java index c8f07d06..a0d10988 100644 --- a/src/test/java/com/juick/server/tests/ServerTests.java +++ b/src/test/java/com/juick/server/tests/ServerTests.java @@ -215,6 +215,8 @@ public class ServerTests { private Resource hubzillaFollow; @Value("classpath:announce.json") private Resource noteWithDocument; + @Value("classpath:2936611-57.jpg") + private Resource jpegNoJfifTiff; @Inject private KeystoreManager testKeystoreManager; @@ -1177,6 +1179,13 @@ public class ServerTests { assertThat(postJpgiPhone.getNewMessage().get().getAttachment().getHeight(), is(1280)); assertThat(postJpgiPhone.getNewMessage().get().getAttachment().getMedium().getHeight(), is(1024)); assertThat(postJpgiPhone.getNewMessage().get().getAttachment().getSmall().getHeight(), is(512)); + CommandResult postNojfifTiff = commandsManager.processCommand(ugnich, "YO2", jpegNoJfifTiff.getURI()); + assertThat(postNojfifTiff.getNewMessage().isPresent(), is(true)); + int mid2 = postNojfifTiff.getNewMessage().get().getMid(); + File originalFile2 = Paths.get(imgDir, "p", String.format("%d.jpg", mid2)).toFile(); + assertThat(originalFile2.exists(), is(true)); + File mediumFile2 = Paths.get(imgDir, "photos-1024", String.format("%d.jpg", mid2)).toFile(); + assertThat(mediumFile2.exists(), is(true)); } @Test public void changeExtensionWhenReceiveFileWithWrongContentType() throws Exception { diff --git a/src/test/resources/2936611-57.jpg b/src/test/resources/2936611-57.jpg new file mode 100644 index 00000000..af4f9c91 Binary files /dev/null and b/src/test/resources/2936611-57.jpg differ -- cgit v1.2.3 From 441bc18c6f072332920cee2473217bbab8e28055 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Fri, 1 Mar 2019 10:46:06 +0300 Subject: api: return telegram and xmpp identifiers from service api endpoint --- .../java/com/juick/server/api/Notifications.java | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/api/Notifications.java b/src/main/java/com/juick/server/api/Notifications.java index 000a9f3b..ea1d5c54 100644 --- a/src/main/java/com/juick/server/api/Notifications.java +++ b/src/main/java/com/juick/server/api/Notifications.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2017, Juick + * Copyright (C) 2008-2019, 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 @@ -24,14 +24,19 @@ import com.juick.User; import com.juick.model.AnonymousUser; import com.juick.server.util.HttpBadRequestException; import com.juick.server.util.HttpForbiddenException; +import com.juick.server.util.UserUtils; import com.juick.service.MessagesService; import com.juick.service.PushQueriesService; import com.juick.service.SubscriptionService; -import com.juick.server.util.UserUtils; +import com.juick.service.TelegramService; import com.juick.service.UserService; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +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 springfox.documentation.annotations.ApiIgnore; import javax.inject.Inject; @@ -55,6 +60,8 @@ public class Notifications { private SubscriptionService subscriptionService; @Inject private UserService userService; + @Inject + private TelegramService telegramService; private User collectTokens(Integer uid) { @@ -63,6 +70,14 @@ public class Notifications { pushQueriesService.getGCMRegID(uid).forEach(t -> user.getTokens().add(new ExternalToken(null, "gcm", t, null))); pushQueriesService.getAPNSToken(uid).forEach(t -> user.getTokens().add(new ExternalToken(null, "apns", t, null))); pushQueriesService.getMPNSURL(uid).forEach(t -> user.getTokens().add(new ExternalToken(null, "mpns", t, null))); + List xmppJids = userService.getJIDsbyUID(uid).stream() + .map(jid -> new ExternalToken(null, "xmpp", jid, null)) + .collect(Collectors.toList()); + user.getTokens().addAll(xmppJids); + List tgIds = telegramService.getTelegramIdentifiers(Collections.singletonList(user)).stream() + .map(tgId -> new ExternalToken(null, "durov", String.valueOf(tgId), null)) + .collect(Collectors.toList()); + user.getTokens().addAll(tgIds); return user; } -- cgit v1.2.3 From ece568a4bcd0ff8a2f84c974a458a2f498fd135d Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Fri, 1 Mar 2019 19:49:10 +0300 Subject: Message::timestamp -> Message::created --- src/main/java/com/juick/Message.java | 17 +++++++---------- .../java/com/juick/formatters/PlainTextFormatter.java | 2 +- src/main/java/com/juick/server/ActivityPubManager.java | 2 +- .../java/com/juick/server/api/rss/MessagesView.java | 2 +- .../java/com/juick/service/MessagesServiceImpl.java | 4 ++-- .../java/com/juick/service/PMQueriesServiceImpl.java | 6 +++--- .../resources/templates/views/partial/message.html | 6 +++--- src/main/resources/templates/views/pm_inbox.html | 2 +- src/main/resources/templates/views/pm_sent.html | 2 +- src/main/resources/templates/views/thread.html | 12 ++++++------ src/test/java/com/juick/server/tests/ServerTests.java | 18 +++++++++--------- src/test/java/com/juick/test/util/MockUtils.java | 2 +- 12 files changed, 36 insertions(+), 39 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/Message.java b/src/main/java/com/juick/Message.java index fb6be67c..2035d4c6 100644 --- a/src/main/java/com/juick/Message.java +++ b/src/main/java/com/juick/Message.java @@ -21,7 +21,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.juick.adapters.SimpleDateAdapter; import com.juick.model.Entity; -import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.builder.ToStringBuilder; import javax.annotation.Nonnull; @@ -46,8 +45,8 @@ public class Message implements Comparable { private int replyto = 0; private String text = null; private User user = null; - private final Set tags; - private Instant ts; + private Set tags; + private Instant created; private Instant updated; private Instant updatedAt; private boolean unread; @@ -181,12 +180,12 @@ public class Message implements Comparable { @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "UTC") @XmlAttribute(name = "ts") @XmlJavaTypeAdapter(SimpleDateAdapter.class) - public Instant getTimestamp() { - return ts; + public Instant getCreated() { + return created; } - public void setTimestamp(Instant timestamp) { - this.ts = timestamp; + public void setCreated(Instant timestamp) { + this.created = timestamp; } @XmlTransient @@ -224,9 +223,7 @@ public class Message implements Comparable { } public void setTags(Set tags) { - this.tags.clear(); - if (CollectionUtils.isNotEmpty(tags)) - this.tags.addAll(tags); + this.tags = tags; } @XmlAttribute diff --git a/src/main/java/com/juick/formatters/PlainTextFormatter.java b/src/main/java/com/juick/formatters/PlainTextFormatter.java index 4cb07950..41d24e93 100644 --- a/src/main/java/com/juick/formatters/PlainTextFormatter.java +++ b/src/main/java/com/juick/formatters/PlainTextFormatter.java @@ -57,7 +57,7 @@ public class PlainTextFormatter { public static String formatPostSummary(Message m) { int cropLength = 384; - String timeAgo = pt.format(Date.from(m.getTimestamp())); + String timeAgo = pt.format(Date.from(m.getCreated())); String repliesCount = m.getReplies() == 1 ? "; 1 reply" : m.getReplies() == 0 ? "" : String.format("; %d replies", m.getReplies()); StringBuilder sb = new StringBuilder(); diff --git a/src/main/java/com/juick/server/ActivityPubManager.java b/src/main/java/com/juick/server/ActivityPubManager.java index 24cc2123..a5398325 100644 --- a/src/main/java/com/juick/server/ActivityPubManager.java +++ b/src/main/java/com/juick/server/ActivityPubManager.java @@ -269,7 +269,7 @@ public class ActivityPubManager implements ActivityListener, NotificationListene note.setTo(Collections.singletonList("https://www.w3.org/ns/activitystreams#Public")); note.setCc(Collections.singletonList(followersUri(msg.getUser()))); } - note.setPublished(msg.getTimestamp()); + note.setPublished(msg.getCreated()); if (StringUtils.isNotBlank(msg.getAttachmentType())) { Image attachment = new Image(); attachment.setId(msg.getAttachment().getMedium().getUrl()); diff --git a/src/main/java/com/juick/server/api/rss/MessagesView.java b/src/main/java/com/juick/server/api/rss/MessagesView.java index 05bc0bd6..2c05c8cb 100644 --- a/src/main/java/com/juick/server/api/rss/MessagesView.java +++ b/src/main/java/com/juick/server/api/rss/MessagesView.java @@ -126,7 +126,7 @@ public class MessagesView extends AbstractRssFeedView { description.setType("text/html"); description.setValue(messageDescription); item.setDescription(description); - item.setPubDate(Date.from(msg.getTimestamp())); + item.setPubDate(Date.from(msg.getCreated())); item.setComments(messageUrl); msg.getTags().stream().map(t -> { Category category = new Category(); diff --git a/src/main/java/com/juick/service/MessagesServiceImpl.java b/src/main/java/com/juick/service/MessagesServiceImpl.java index e95742ab..33c9cee1 100644 --- a/src/main/java/com/juick/service/MessagesServiceImpl.java +++ b/src/main/java/com/juick/service/MessagesServiceImpl.java @@ -90,7 +90,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ user.setBanned(rs.getBoolean(6)); user.setUri(URI.create(Optional.ofNullable(rs.getString(22)).orElse(StringUtils.EMPTY))); msg.setUser(user); - msg.setTimestamp(rs.getTimestamp(7).toInstant()); + msg.setCreated(rs.getTimestamp(7).toInstant()); msg.ReadOnly = rs.getBoolean(8); msg.setPrivacy(rs.getInt(9)); msg.FriendsOnly = msg.getPrivacy() < 0; @@ -426,7 +426,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ URI.create(Optional.ofNullable(rs.getString(11)).orElse(StringUtils.EMPTY))); } msg.setReplyto(rs.getInt(3)); - msg.setTimestamp(rs.getTimestamp(4).toInstant()); + msg.setCreated(rs.getTimestamp(4).toInstant()); msg.setAttachmentType(rs.getString(5)); msg.setText(rs.getString(6)); String quote = rs.getString(7); diff --git a/src/main/java/com/juick/service/PMQueriesServiceImpl.java b/src/main/java/com/juick/service/PMQueriesServiceImpl.java index 712e4b0e..f1f98024 100644 --- a/src/main/java/com/juick/service/PMQueriesServiceImpl.java +++ b/src/main/java/com/juick/service/PMQueriesServiceImpl.java @@ -105,7 +105,7 @@ public class PMQueriesServiceImpl extends BaseJdbcService implements PMQueriesSe user.setName(rs.getString(4)); msg.setUser(user); msg.setText(rs.getString(2).trim()); - msg.setTimestamp(rs.getTimestamp(3).toInstant()); + msg.setCreated(rs.getTimestamp(3).toInstant()); return msg; }); } @@ -122,7 +122,7 @@ public class PMQueriesServiceImpl extends BaseJdbcService implements PMQueriesSe msg.getUser().setUid(rs.getInt(1)); msg.getUser().setName(rs.getString(2)); msg.setText(rs.getString(3).trim()); - msg.setTimestamp(rs.getTimestamp(4).toInstant()); + msg.setCreated(rs.getTimestamp(4).toInstant()); return msg; }, uid); @@ -141,7 +141,7 @@ public class PMQueriesServiceImpl extends BaseJdbcService implements PMQueriesSe msg.getUser().setUid(rs.getInt(1)); msg.getUser().setName(rs.getString(2)); msg.setText(rs.getString(3).trim()); - msg.setTimestamp(rs.getTimestamp(4).toInstant()); + msg.setCreated(rs.getTimestamp(4).toInstant()); return msg; }, uid); diff --git a/src/main/resources/templates/views/partial/message.html b/src/main/resources/templates/views/partial/message.html index b1d27ae5..bb36b25e 100644 --- a/src/main/resources/templates/views/partial/message.html +++ b/src/main/resources/templates/views/partial/message.html @@ -8,9 +8,9 @@ diff --git a/src/main/resources/templates/views/pm_inbox.html b/src/main/resources/templates/views/pm_inbox.html index 4f97bc86..f89b2923 100644 --- a/src/main/resources/templates/views/pm_inbox.html +++ b/src/main/resources/templates/views/pm_inbox.html @@ -12,7 +12,7 @@ {{ msg.user.name }} -
{{ msg.timestamp | prettyTime }}
+
{{ msg.created | prettyTime }}
{{ msg | formatMessage }}
diff --git a/src/main/resources/templates/views/pm_sent.html b/src/main/resources/templates/views/pm_sent.html index ace25301..f0af71d3 100644 --- a/src/main/resources/templates/views/pm_sent.html +++ b/src/main/resources/templates/views/pm_sent.html @@ -19,7 +19,7 @@ {{ msg.user.name }} -
{{ msg.timestamp | prettyTime }}
+
{{ msg.created | prettyTime }}
{{ msg | formatMessage }}
diff --git a/src/main/resources/templates/views/thread.html b/src/main/resources/templates/views/thread.html index 90c9d4a0..2092cc1b 100644 --- a/src/main/resources/templates/views/thread.html +++ b/src/main/resources/templates/views/thread.html @@ -13,9 +13,9 @@ @@ -140,9 +140,9 @@ {% endif %} diff --git a/src/test/java/com/juick/server/tests/ServerTests.java b/src/test/java/com/juick/server/tests/ServerTests.java index ad375e99..7f46968f 100644 --- a/src/test/java/com/juick/server/tests/ServerTests.java +++ b/src/test/java/com/juick/server/tests/ServerTests.java @@ -433,7 +433,7 @@ public class ServerTests { Timestamp.class, mid).toInstant(); assertThat(rts, greaterThan(ts)); Message msg = messagesService.getReply(mid, rid); - assertThat(rts, equalTo(msg.getTimestamp())); + assertThat(rts, equalTo(msg.getCreated())); messagesService.deleteMessage(ugnich_id, mid); } @@ -470,7 +470,7 @@ public class ServerTests { .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(jsonPath("$[0].mid", is(msg.getMid()))) .andExpect(jsonPath("$[0].timestamp", - is(DateFormattersHolder.getMessageFormatterInstance().format(msg.getTimestamp())))) + is(DateFormattersHolder.getMessageFormatterInstance().format(msg.getCreated())))) .andExpect(jsonPath("$[0].body", is(msg.getText()))) .andExpect(jsonPath("$[0].attachment.url", is(String.format("https://i.juick.com/p/%d.png", msg.getMid())))) @@ -726,7 +726,7 @@ public class ServerTests { Message msg2 = yoyoMsg.getNewMessage().get(); int mid = msg2.getMid(); Timestamp last = jdbcTemplate.queryForObject("SELECT lastmessage FROM users WHERE id=?", Timestamp.class, user.getUid()); - assertThat(last.toInstant(), equalTo(yoyoMsg.getNewMessage().get().getTimestamp())); + assertThat(last.toInstant(), equalTo(yoyoMsg.getNewMessage().get().getCreated())); assertEquals("should be message", true, commandsManager.processCommand(user, String.format("#%d", mid), emptyUri).getText().startsWith("@me")); int readerUid = userService.createUser("dummyReader", "dummySecret"); @@ -771,7 +771,7 @@ public class ServerTests { Message reply = messagesService.getReplies(user, mid).stream().filter(m -> m.getRid() == 3).findFirst() .orElse(new Message()); Timestamp lastreply = jdbcTemplate.queryForObject("SELECT lastmessage FROM users WHERE id=?", Timestamp.class, user.getUid()); - assertThat(lastreply.toInstant(), equalTo(reply.getTimestamp())); + assertThat(lastreply.toInstant(), equalTo(reply.getCreated())); assertEquals("should be reply to second comment", 2, reply.getReplyto()); assertThat(commandsManager.processCommand(readerUser, "#" + mid + " *yo *there", emptyUri) .getText(), startsWith("Reply posted")); @@ -1038,7 +1038,7 @@ public class ServerTests { Message msg = new Message(); msg.setTags(tags); Instant currentDate = Instant.now(); - msg.setTimestamp(currentDate); + msg.setCreated(currentDate); String jsonMessage = jsonMapper.writeValueAsString(msg); assertEquals("date should be in timestamp field", DateFormattersHolder.getMessageFormatterInstance().format(currentDate), JsonPath.read(jsonMessage, "$.timestamp")); @@ -1210,7 +1210,7 @@ public class ServerTests { Message original = jsonMapper.readValue(result.getResponse().getContentAsString(), CommandResult.class) .getNewMessage().get(); assertThat(original.getText(), equalTo("YO")); - assertThat(original.getUpdatedAt(), equalTo(original.getTimestamp())); + assertThat(original.getUpdatedAt(), equalTo(original.getCreated())); // to have updated_at greater than ts Thread.sleep(1000); result = mockMvc.perform(post("/api/update").with(httpBasic(ugnichName, ugnichPassword)) @@ -1219,7 +1219,7 @@ public class ServerTests { Message edited = jsonMapper.readValue(result.getResponse().getContentAsString(), CommandResult.class) .getNewMessage().get(); assertThat(edited.getText(), equalTo("PEOPLE")); - assertThat(edited.getUpdatedAt(), greaterThan(edited.getTimestamp())); + assertThat(edited.getUpdatedAt(), greaterThan(edited.getCreated())); mockMvc.perform(post("/api/update").with(httpBasic(freefdName, freefdPassword)) .param("mid", String.valueOf(original.getMid())) .param("body", "PEOPLE")).andExpect(status().is(403)); @@ -1228,7 +1228,7 @@ public class ServerTests { .param("body", "HEY")).andExpect(status().is2xxSuccessful()).andReturn(); CommandResult comment = jsonMapper.readValue(result.getResponse().getContentAsString(), CommandResult.class); assertThat(comment.getNewMessage().get().getText(), is("HEY")); - assertThat(comment.getNewMessage().get().getUpdatedAt(), is(comment.getNewMessage().get().getTimestamp())); + assertThat(comment.getNewMessage().get().getUpdatedAt(), is(comment.getNewMessage().get().getCreated())); // to have updated_at greater than ts Thread.sleep(1000); result = mockMvc.perform(post("/api/update").with(httpBasic(freefdName, freefdPassword)) @@ -1238,7 +1238,7 @@ public class ServerTests { Message editedComment = jsonMapper.readValue(result.getResponse().getContentAsString(), CommandResult.class) .getNewMessage().get(); assertThat(editedComment.getText(), is("HEY, JOE")); - assertThat(editedComment.getUpdatedAt(), greaterThan(editedComment.getTimestamp())); + assertThat(editedComment.getUpdatedAt(), greaterThan(editedComment.getCreated())); messagesService.deleteMessage(ugnich.getUid(), original.getMid()); } @Test diff --git a/src/test/java/com/juick/test/util/MockUtils.java b/src/test/java/com/juick/test/util/MockUtils.java index 017af4d1..8f6b821d 100644 --- a/src/test/java/com/juick/test/util/MockUtils.java +++ b/src/test/java/com/juick/test/util/MockUtils.java @@ -34,7 +34,7 @@ public class MockUtils { msg.setMid(mid); msg.setUser(user); msg.setText(messageText == null ? generator.generate(24) : messageText); - msg.setTimestamp(Instant.now()); + msg.setCreated(Instant.now()); return msg; } -- cgit v1.2.3 From 9c645f58d9e0d3689333c8d53fb1d880b11f6c6a Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Fri, 1 Mar 2019 22:43:38 +0300 Subject: Avatars in recommendations --- src/main/java/com/juick/server/api/Messages.java | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/api/Messages.java b/src/main/java/com/juick/server/api/Messages.java index e105890f..5c791df4 100644 --- a/src/main/java/com/juick/server/api/Messages.java +++ b/src/main/java/com/juick/server/api/Messages.java @@ -179,6 +179,9 @@ public class Messages { } msg.getUser().setAvatar(webApp.getAvatarUrl(msg.getUser())); msg.setRecommendations(new HashSet<>(messagesService.getMessageRecommendations(msg.getMid()))); + msg.getRecommendations().forEach(r -> { + r.setAvatar(webApp.getAvatarUrl(r)); + }); List replies = messagesService.getReplies(visitor, mid); replies.forEach(m -> m.getUser().setAvatar(webApp.getAvatarUrl(m.getUser()))); if (!visitor.isAnonymous()) { -- cgit v1.2.3 From 294805a039dfd38646a47e11acba0d9dbb6c107f Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Tue, 12 Mar 2019 12:45:17 +0300 Subject: add email to nodeinfo metadata --- src/main/java/com/juick/server/api/xnodeinfo2/Info.java | 5 +++++ .../com/juick/server/api/xnodeinfo2/model/NodeInfo.java | 13 ++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/api/xnodeinfo2/Info.java b/src/main/java/com/juick/server/api/xnodeinfo2/Info.java index b71637b9..493bff6a 100644 --- a/src/main/java/com/juick/server/api/xnodeinfo2/Info.java +++ b/src/main/java/com/juick/server/api/xnodeinfo2/Info.java @@ -17,6 +17,8 @@ import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; @RestController public class Info { @@ -35,6 +37,9 @@ public class Info { server.setVersion("2.x"); nodeInfo.setServer(server); nodeInfo.setProtocols(Arrays.asList("xmpp", "activitypub", "smtp")); + Map metadata = new HashMap<>(); + metadata.put("email", "support@juick.com"); + nodeInfo.setMetadata(metadata); ServiceInfo serviceInfo = new ServiceInfo(); serviceInfo.setInbound(Arrays.asList("jabber", "mastodon", "email", "telegram")); serviceInfo.setOutbound(Arrays.asList("jabber", "mastodon", "telegram", "twitter", "email", "rss")); diff --git a/src/main/java/com/juick/server/api/xnodeinfo2/model/NodeInfo.java b/src/main/java/com/juick/server/api/xnodeinfo2/model/NodeInfo.java index 44bd4325..1ebe39a8 100644 --- a/src/main/java/com/juick/server/api/xnodeinfo2/model/NodeInfo.java +++ b/src/main/java/com/juick/server/api/xnodeinfo2/model/NodeInfo.java @@ -1,9 +1,9 @@ package com.juick.server.api.xnodeinfo2.model; -import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonView; import java.util.List; +import java.util.Map; public class NodeInfo { @@ -15,6 +15,8 @@ public class NodeInfo { private ServiceInfo services; + private Map metadata; + @JsonView({XNodeInfoView.class, NodeInfoView.class}) public String getVersion() { return version; @@ -71,6 +73,15 @@ public class NodeInfo { this.usage = usage; } + @JsonView(NodeInfoView.class) + public Map getMetadata() { + return metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + } + public interface NodeInfoView {} public interface XNodeInfoView {} } -- cgit v1.2.3 From 861580c3dae53fb8e57956fd44aafa79f5d2c145 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Fri, 15 Mar 2019 11:27:08 +0300 Subject: trying to remove okHttp --- build.gradle | 3 +-- .../java/com/juick/server/configuration/ActivityPubClientConfig.java | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/build.gradle b/build.gradle index a5dded2c..acaaf0f2 100644 --- a/build.gradle +++ b/build.gradle @@ -160,7 +160,7 @@ dependencies { runtime 'org.mariadb.jdbc:mariadb-java-client:2.3.0' runtime 'net.java.dev.jna:jna:5.2.0' runtime 'net.java.dev.jna:jna-platform:5.2.0' - runtime 'com.h2database:h2:1.4.198' + runtime 'com.h2database:h2:1.4.199' runtime "commons-fileupload:commons-fileupload:1.4" compile 'com.github.ooxi:serialized-php-parser:0.5.0' @@ -168,7 +168,6 @@ dependencies { compile 'com.atlassian.commonmark:commonmark:0.12.1' compile 'com.atlassian.commonmark:commonmark-ext-autolink:0.12.1' compile 'org.tomitribe:tomitribe-http-signatures:1.1' - compile 'com.squareup.okhttp3:okhttp:3.11.0' compile 'com.google.api-client:google-api-client:1.28.0' compile "com.kotcrab.remark:remark:1.0.0" diff --git a/src/main/java/com/juick/server/configuration/ActivityPubClientConfig.java b/src/main/java/com/juick/server/configuration/ActivityPubClientConfig.java index 6cb1126d..d7d49355 100644 --- a/src/main/java/com/juick/server/configuration/ActivityPubClientConfig.java +++ b/src/main/java/com/juick/server/configuration/ActivityPubClientConfig.java @@ -4,7 +4,6 @@ import com.juick.server.api.activity.model.Activity; import com.juick.server.helpers.HeaderRequestInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.client.OkHttp3ClientHttpRequestFactory; import org.springframework.web.client.RestTemplate; import javax.inject.Inject; @@ -16,7 +15,7 @@ public class ActivityPubClientConfig { ActivityPubClientErrorHandler activityPubClientErrorHandler; @Bean public RestTemplate apClient() { - RestTemplate restTemplate = new RestTemplate(new OkHttp3ClientHttpRequestFactory()); + RestTemplate restTemplate = new RestTemplate(); restTemplate.setErrorHandler(activityPubClientErrorHandler); restTemplate.setInterceptors(Collections.singletonList( new HeaderRequestInterceptor("Accept", Activity.ACTIVITY_MEDIA_TYPE))); -- cgit v1.2.3 From fb6d7f3b9d34032ec7ed32295b13dd556088989f Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Fri, 15 Mar 2019 14:11:47 +0300 Subject: Allow parameters in sape links --- .../juick/server/www/controllers/MessagesWWW.java | 40 ++++++++++------------ src/main/java/ru/sape/Sape.java | 4 ++- src/main/java/ru/sape/SapePageLinks.java | 15 ++++---- 3 files changed, 30 insertions(+), 29 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/www/controllers/MessagesWWW.java b/src/main/java/com/juick/server/www/controllers/MessagesWWW.java index 1c69db32..6de8d51d 100644 --- a/src/main/java/com/juick/server/www/controllers/MessagesWWW.java +++ b/src/main/java/com/juick/server/www/controllers/MessagesWWW.java @@ -43,7 +43,9 @@ import ru.sape.Sape; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; import java.io.IOException; +import java.net.URI; import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -182,10 +184,9 @@ public class MessagesWWW { } model.addAttribute("nextpage", nextpage); } - UriComponents builder = ServletUriComponentsBuilder.fromCurrentRequestUri().build(); - String queryString = builder.getQuery(); - String requestURI = builder.toUri().getPath(); - if (sape.isPresent() && visitor.isAnonymous() && queryString == null) { + UriComponents builder = ServletUriComponentsBuilder.fromCurrentRequest().build(); + URI requestURI = builder.toUri(); + if (sape.isPresent() && visitor.isAnonymous()) { String links = sape.get().getPageLinks(requestURI, sapeCookie).render(); model.addAttribute("links", links); } @@ -295,17 +296,16 @@ public class MessagesWWW { nextpage += "&show=" + paramShow; } if (paramSearch != null) { - nextpage += "&search=" + URLEncoder.encode(paramSearch, CharEncoding.UTF_8); + nextpage += "&search=" + URLEncoder.encode(paramSearch, StandardCharsets.UTF_8); } if (paramTag != null) { - nextpage += "&tag=" + URLEncoder.encode(paramTag.getName(), CharEncoding.UTF_8); + nextpage += "&tag=" + URLEncoder.encode(paramTag.getName(), StandardCharsets.UTF_8); } model.addAttribute("nextpage", nextpage); } - UriComponents builder = ServletUriComponentsBuilder.fromCurrentRequestUri().build(); - String queryString = builder.getQuery(); - String requestURI = builder.toUri().getPath(); - if (sape.isPresent() && visitor.isAnonymous() && queryString == null) { + UriComponents builder = ServletUriComponentsBuilder.fromCurrentRequest().build(); + URI requestURI = builder.toUri(); + if (sape.isPresent() && visitor.isAnonymous()) { String links = sape.get().getPageLinks(requestURI, sapeCookie).render(); model.addAttribute("links", links); } @@ -313,7 +313,7 @@ public class MessagesWWW { } @GetMapping("/{uname}/tags") - protected String doGetTags(@PathVariable String uname, ModelMap model) throws IOException { + protected String doGetTags(@PathVariable String uname, ModelMap model) { com.juick.User user = userService.getUserByName(uname); com.juick.User visitor = UserUtils.getCurrentUser(); if (visitor.isBanned()) { @@ -332,7 +332,7 @@ public class MessagesWWW { } @GetMapping("/{uname}/friends") - protected String doGetFriends(@PathVariable String uname, ModelMap model) throws IOException { + protected String doGetFriends(@PathVariable String uname, ModelMap model) { com.juick.User user = userService.getUserByName(uname); com.juick.User visitor = UserUtils.getCurrentUser(); if (visitor.isBanned()) { @@ -444,13 +444,12 @@ public class MessagesWWW { model.addAttribute("isSubscribed", tagService.isSubscribed(visitor, paramTag)); model.addAttribute("isInBL", tagService.isInBL(visitor, paramTag)); if (mids.size() >= 20) { - String nextpage = "/tag/" + URLEncoder.encode(paramTag.getName(), CharEncoding.UTF_8) + "?before=" + mids.get(mids.size() - 1); + String nextpage = "/tag/" + URLEncoder.encode(paramTag.getName(), StandardCharsets.UTF_8) + "?before=" + mids.get(mids.size() - 1); model.addAttribute("nextpage", nextpage); } - UriComponents builder = ServletUriComponentsBuilder.fromCurrentRequestUri().build(); - String queryString = builder.getQuery(); - String requestURI = builder.toUri().getPath(); - if (sape.isPresent() && visitor.isAnonymous() && queryString == null) { + UriComponents builder = ServletUriComponentsBuilder.fromCurrentRequest().build(); + URI requestURI = builder.toUri(); + if (sape.isPresent() && visitor.isAnonymous()) { String links = sape.get().getPageLinks(requestURI, sapeCookie).render(); model.addAttribute("links", links); } @@ -591,10 +590,9 @@ public class MessagesWWW { } model.addAttribute("replies", replies); model.addAttribute("showAdv", visitor.isAnonymous()); - UriComponents builder = ServletUriComponentsBuilder.fromCurrentRequestUri().build(); - String queryString = builder.getQuery(); - String requestURI = builder.toUri().getPath(); - if (sape.isPresent() && visitor.isAnonymous() && queryString == null) { + UriComponents builder = ServletUriComponentsBuilder.fromCurrentRequest().build(); + URI requestURI = builder.toUri(); + if (sape.isPresent() && visitor.isAnonymous()) { String links = sape.get().getPageLinks(requestURI, sapeCookie).render(); model.addAttribute("links", links); } diff --git a/src/main/java/ru/sape/Sape.java b/src/main/java/ru/sape/Sape.java index 38577c45..a94bcc62 100644 --- a/src/main/java/ru/sape/Sape.java +++ b/src/main/java/ru/sape/Sape.java @@ -3,6 +3,8 @@ */ package ru.sape; +import java.net.URI; + public class Sape { private final String sapeUser; @@ -17,7 +19,7 @@ public class Sape { } public boolean debug = false; - public SapePageLinks getPageLinks(String requestUri, String cookie) { + public SapePageLinks getPageLinks(URI requestUri, String cookie) { return new SapePageLinks(sapePageLinkConnection, sapeUser, requestUri, cookie, debug); } } diff --git a/src/main/java/ru/sape/SapePageLinks.java b/src/main/java/ru/sape/SapePageLinks.java index e89b4e71..77715aea 100644 --- a/src/main/java/ru/sape/SapePageLinks.java +++ b/src/main/java/ru/sape/SapePageLinks.java @@ -1,17 +1,18 @@ package ru.sape; +import org.apache.commons.lang3.StringUtils; + +import java.net.URI; import java.util.*; public class SapePageLinks { private boolean showCode; - public SapePageLinks(SapeConnection sapeConnection, String sapeUser, String requestUri, String sapeCookie) { - this(sapeConnection, sapeUser, requestUri, sapeCookie, false); - } - @SuppressWarnings("unchecked") - public SapePageLinks(SapeConnection sapeConnection, String sapeUser, String requestUri, String sapeCookie, boolean showCode) { + public SapePageLinks(SapeConnection sapeConnection, String sapeUser, URI request, String sapeCookie, boolean showCode) { + String req = StringUtils.isNotEmpty(request.getQuery()) ? request.getPath() + "?" + request.getQuery() + : request.getPath(); if (sapeUser.equals(sapeCookie)) { showCode = true; } @@ -22,8 +23,8 @@ public class SapePageLinks { linkDelimiter = (String) data.get("__sape_delimiter__"); } - if (data.containsKey(requestUri)) { - pageLinks = new ArrayList<>(((Map) data.get(requestUri)).values()); + if (data.containsKey(req)) { + pageLinks = new ArrayList<>(((Map) data.get(req)).values()); } if (data.containsKey("__sape_new_url__")) { -- cgit v1.2.3 From a49105285d0d7719d7f222a507af2d5ac5b4bdb1 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Fri, 15 Mar 2019 17:17:10 +0300 Subject: Correctly set webfinger content-type --- src/main/java/com/juick/server/api/webfinger/Resource.java | 2 +- src/main/java/com/juick/server/www/controllers/MessagesWWW.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/main/java/com/juick/server') diff --git a/src/main/java/com/juick/server/api/webfinger/Resource.java b/src/main/java/com/juick/server/api/webfinger/Resource.java index 71a0ca31..4e0447f7 100644 --- a/src/main/java/com/juick/server/api/webfinger/Resource.java +++ b/src/main/java/com/juick/server/api/webfinger/Resource.java @@ -26,7 +26,7 @@ public class Resource { @Value("${ap_base_uri:http://localhost:8080/}") private String baseUri; - @GetMapping("/.well-known/webfinger") + @GetMapping(value = "/.well-known/webfinger", produces = "application/jrd+json;charset=utf-8") public Account getWebResource(@RequestParam String resource) { if (resource.startsWith("acct:")) { Jid account = Jid.of(resource.substring(5)); diff --git a/src/main/java/com/juick/server/www/controllers/MessagesWWW.java b/src/main/java/com/juick/server/www/controllers/MessagesWWW.java index 6de8d51d..4410f591 100644 --- a/src/main/java/com/juick/server/www/controllers/MessagesWWW.java +++ b/src/main/java/com/juick/server/www/controllers/MessagesWWW.java @@ -99,7 +99,7 @@ public class MessagesWWW { @CookieValue(name = "sape_cookie", required = false, defaultValue = StringUtils.EMPTY) String sapeCookie, ModelMap model) throws IOException { if (tag != null) { - return "redirect:/tag/" + URLEncoder.encode(tag, CharEncoding.UTF_8); + return "redirect:/tag/" + URLEncoder.encode(tag, StandardCharsets.UTF_8); } com.juick.User visitor = UserUtils.getCurrentUser(); -- cgit v1.2.3