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 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) (limited to 'src/main/java/com/juick/server/api') 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( -- 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/api') 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/api') 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 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/api') 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 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/api') 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/api') 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/api') 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/api') 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/api') 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 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/api') 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 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/api') 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/api') 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 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/api') 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/api') 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 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/api') 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/api') 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 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/api') 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/api') 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 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/api') 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/api') 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/api') 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/api') 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/api') 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/api') 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/api') 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 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/api') 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/api') 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/api') 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/api') 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 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/api') 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