From 3890570bf190a63f8f34c47a7fd21e780a61b6b0 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Tue, 17 Jan 2017 11:57:50 +0300 Subject: juick-www: Facebook and VK login using scribejava --- juick-www/src/main/java/com/juick/www/Utils.java | 12 ++ .../com/juick/www/controllers/FacebookLogin.java | 125 ++++++++++----------- .../com/juick/www/controllers/VKontakteLogin.java | 116 +++++++++---------- .../main/java/com/juick/www/facebook/Graph.java | 101 ----------------- .../src/main/java/com/juick/www/facebook/User.java | 103 +++++++++++++++++ 5 files changed, 234 insertions(+), 223 deletions(-) delete mode 100644 juick-www/src/main/java/com/juick/www/facebook/Graph.java create mode 100644 juick-www/src/main/java/com/juick/www/facebook/User.java diff --git a/juick-www/src/main/java/com/juick/www/Utils.java b/juick-www/src/main/java/com/juick/www/Utils.java index 37016ed2..d7b5597d 100644 --- a/juick-www/src/main/java/com/juick/www/Utils.java +++ b/juick-www/src/main/java/com/juick/www/Utils.java @@ -32,6 +32,7 @@ import java.net.URL; import java.net.URLConnection; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.Optional; /** * @@ -108,4 +109,15 @@ public class Utils { return null; } } + /** + * Returns the viewName to return for coming back to the sender url + * + * @param request Instance of {@link HttpServletRequest} or use an injected instance + * @return Optional with the view name. Recomended to use an alternativa url with + * {@link Optional#orElse(java.lang.Object)} + */ + public static Optional getPreviousPageByRequest(HttpServletRequest request) + { + return Optional.ofNullable(request.getHeader("Referer")).map(requestUrl -> "redirect:" + requestUrl); + } } diff --git a/juick-www/src/main/java/com/juick/www/controllers/FacebookLogin.java b/juick-www/src/main/java/com/juick/www/controllers/FacebookLogin.java index cc11f99a..b1d275b6 100644 --- a/juick-www/src/main/java/com/juick/www/controllers/FacebookLogin.java +++ b/juick-www/src/main/java/com/juick/www/controllers/FacebookLogin.java @@ -19,27 +19,35 @@ package com.juick.www.controllers; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.scribejava.apis.FacebookApi; +import com.github.scribejava.core.builder.ServiceBuilder; +import com.github.scribejava.core.model.OAuth2AccessToken; +import com.github.scribejava.core.model.OAuthRequest; +import com.github.scribejava.core.model.Verb; +import com.github.scribejava.core.oauth.OAuth20Service; +import com.juick.server.util.HttpBadRequestException; import com.juick.service.CrosspostService; import com.juick.service.UserService; import com.juick.www.Utils; -import com.juick.www.facebook.Graph; -import org.apache.commons.lang3.CharEncoding; +import com.juick.www.facebook.User; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.env.Environment; import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; import javax.inject.Inject; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; +import java.io.IOException; import java.util.UUID; +import java.util.concurrent.ExecutionException; /** * @@ -54,6 +62,7 @@ public class FacebookLogin { private final String FACEBOOK_SECRET; private final String FACEBOOK_REDIRECT = "http://juick.com/_fblogin"; private final ObjectMapper mapper; + private ServiceBuilder serviceBuilder; @Inject CrosspostService crosspostService; @@ -64,7 +73,7 @@ public class FacebookLogin { public FacebookLogin(Environment env) { FACEBOOK_APPID = env.getProperty("facebook_appid"); FACEBOOK_SECRET = env.getProperty("facebook_secret"); - + serviceBuilder = new ServiceBuilder(); mapper = new ObjectMapper(); mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); @@ -72,82 +81,70 @@ public class FacebookLogin { } @RequestMapping(value = "/_fblogin", method = RequestMethod.GET) - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException { - String fbstate; - - String code = request.getParameter("code"); + protected String doGet(HttpServletRequest request, + @RequestParam(required = false) String code, + @RequestParam(required = false) String state, + @CookieValue(required = false) String fbstate, + HttpServletResponse response) throws IOException, ExecutionException, InterruptedException { if (StringUtils.isBlank(code)) { fbstate = UUID.randomUUID().toString(); Cookie c = new Cookie("fbstate", fbstate); response.addCookie(c); - - response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); - response.setHeader("Location", "https://www.facebook.com/dialog/oauth?scope=publish_stream&client_id=" + FACEBOOK_APPID + "&redirect_uri=" + URLEncoder.encode(FACEBOOK_REDIRECT, CharEncoding.UTF_8) + "&state=" + fbstate); - return; + OAuth20Service facebookAuthService = serviceBuilder + .apiKey(FACEBOOK_APPID) + .apiSecret(FACEBOOK_SECRET) + .callback(FACEBOOK_REDIRECT) + .scope("publish_actions") + .state(fbstate) + .build(FacebookApi.instance()); + return "redirect:" + facebookAuthService.getAuthorizationUrl(); } - fbstate = Utils.getCookie(request, "fbstate"); - if (fbstate == null || fbstate.isEmpty() || !fbstate.equals(request.getParameter("state"))) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - return; + if (StringUtils.isBlank(fbstate) || !fbstate.equals(state)) { + throw new HttpBadRequestException(); } else { Cookie c = new Cookie("fbstate", "-"); c.setMaxAge(0); response.addCookie(c); } - - String token = Utils.fetchURL("https://graph.facebook.com/oauth/access_token?client_id=" + FACEBOOK_APPID + "&redirect_uri=" + URLEncoder.encode(FACEBOOK_REDIRECT, CharEncoding.UTF_8) + "&client_secret=" + FACEBOOK_SECRET + "&code=" + URLEncoder.encode(code, CharEncoding.UTF_8)); - if (token == null || token.isEmpty() || !token.startsWith("access_token=")) { - logger.error("FACEBOOK TOKEN ERROR: {}", token); - response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - return; - } - token = token.substring(13); // access_token=... - int tokenamp = token.indexOf('&'); // &expires= - if (tokenamp > 0) { - token = token.substring(0, tokenamp); - } - - String graph = Utils.fetchURL("https://graph.facebook.com/me?access_token=" + token); - if (graph == null || graph.isEmpty()) { + OAuth20Service facebookService = serviceBuilder + .apiKey(FACEBOOK_APPID) + .apiSecret(FACEBOOK_SECRET) + .callback(FACEBOOK_REDIRECT) + .state(state) + .build(FacebookApi.instance()); + OAuth2AccessToken token = facebookService.getAccessToken(code); + final OAuthRequest meRequest = new OAuthRequest(Verb.GET, "https://graph.facebook.com/v2.8/me?fields=id,name,link,verified"); + facebookService.signRequest(token, meRequest); + String graph = facebookService.execute(meRequest).getBody(); + if (StringUtils.isBlank(graph)) { logger.error("FACEBOOK GRAPH ERROR"); - response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - return; + throw new HttpBadRequestException(); + } + User fb = mapper.readValue(graph, User.class); + long fbID = NumberUtils.toLong(fb.getId(), 0); + if (fbID == 0 || StringUtils.isBlank(fb.getName()) || StringUtils.isBlank(fb.getLink())) { + throw new HttpBadRequestException(); } - try { - Graph fb = mapper.readValue(graph, Graph.class); - - long fbID = NumberUtils.toLong(fb.getId(), 0); - if (fbID == 0 || StringUtils.isBlank(fb.getName()) || StringUtils.isBlank(fb.getLink())) { - throw new Exception(); + int uid = crosspostService.getUIDbyFBID(fbID); + if (uid > 0) { + if (!crosspostService.updateFacebookUser(fbID, token.getAccessToken(), fb.getName(), fb.getLink())) { + throw new HttpBadRequestException(); } - - int uid = crosspostService.getUIDbyFBID(fbID); - if (uid > 0) { - if (!crosspostService.updateFacebookUser(fbID, token, fb.getName(), fb.getLink())) { - throw new Exception(); - } - Cookie c = new Cookie("hash", userService.getHashByUID(uid)); - c.setMaxAge(50 * 24 * 60 * 60); - response.addCookie(c); - response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); - response.setHeader("Location", "/"); - } else if (fb.getVerified()) { - String loginhash = UUID.randomUUID().toString(); - if (!crosspostService.createFacebookUser(fbID, loginhash, token, fb.getName(), fb.getLink())) { - throw new Exception(); - } - response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); - response.setHeader("Location", "/signup?type=fb&hash=" + loginhash); - } else { - throw new Exception(); + Cookie c = new Cookie("hash", userService.getHashByUID(uid)); + c.setMaxAge(50 * 24 * 60 * 60); + response.addCookie(c); + return Utils.getPreviousPageByRequest(request).orElse("/"); + } else if (fb.getVerified()) { + String loginhash = UUID.randomUUID().toString(); + if (!crosspostService.createFacebookUser(fbID, loginhash, token.getAccessToken(), fb.getName(), fb.getLink())) { + throw new HttpBadRequestException(); } - } catch (Exception e) { - logger.error("fb error", e); - response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - return; + return "redirect:/signup?type=fb&hash=" + loginhash; + } else { + throw new HttpBadRequestException(); } } } diff --git a/juick-www/src/main/java/com/juick/www/controllers/VKontakteLogin.java b/juick-www/src/main/java/com/juick/www/controllers/VKontakteLogin.java index d860a7bc..6ecdfd4a 100644 --- a/juick-www/src/main/java/com/juick/www/controllers/VKontakteLogin.java +++ b/juick-www/src/main/java/com/juick/www/controllers/VKontakteLogin.java @@ -19,26 +19,34 @@ package com.juick.www.controllers; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.scribejava.apis.VkontakteApi; +import com.github.scribejava.core.builder.ServiceBuilder; +import com.github.scribejava.core.model.OAuth2AccessToken; +import com.github.scribejava.core.model.OAuthRequest; +import com.github.scribejava.core.model.Verb; +import com.github.scribejava.core.oauth.OAuth20Service; +import com.juick.server.util.HttpBadRequestException; import com.juick.service.CrosspostService; import com.juick.service.UserService; import com.juick.www.Utils; -import com.juick.www.vk.Token; import com.juick.www.vk.UsersResponse; -import org.apache.commons.lang3.CharEncoding; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.core.env.Environment; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; import javax.inject.Inject; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.net.URLEncoder; import java.util.UUID; +import java.util.concurrent.ExecutionException; /** * @author Ugnich Anton @@ -46,8 +54,8 @@ import java.util.UUID; @Controller public class VKontakteLogin { private static final Logger logger = LoggerFactory.getLogger(VKontakteLogin.class); - private static final String VK_APPID = "3544101"; - private static final String VK_SECRET = "z2afNI8jA5lIpZ2jsTm1"; + private final String VK_APPID; + private final String VK_SECRET; private static final String VK_REDIRECT = "http://juick.com/_vklogin"; @Inject @@ -55,7 +63,13 @@ public class VKontakteLogin { @Inject UserService userService; - public VKontakteLogin() { + private ServiceBuilder serviceBuilder; + + @Inject + public VKontakteLogin(Environment env) { + VK_APPID = env.getProperty("vk_appid"); + VK_SECRET = env.getProperty("vk_secret"); + serviceBuilder = new ServiceBuilder(); mapper = new ObjectMapper(); mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); @@ -65,66 +79,52 @@ public class VKontakteLogin { private final ObjectMapper mapper; @RequestMapping(value = "/_vklogin", method = RequestMethod.GET) - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { - String code = request.getParameter("code"); + protected String doGet(HttpServletRequest request, + @RequestParam(required = false) String code, + HttpServletResponse response) throws IOException, ExecutionException, InterruptedException { if (StringUtils.isBlank(code)) { - response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); - response.setHeader("Location", "https://oauth.vk.com/authorize?client_id=" + VK_APPID + "&redirect_uri=" + URLEncoder.encode(VK_REDIRECT, CharEncoding.UTF_8) + "&scope=friends,wall,offline&response_type=code"); - return; + OAuth20Service vkAuthService = serviceBuilder + .apiKey(VK_APPID) + .apiSecret(VK_SECRET) + .scope("friends,wall,offline") + .callback(VK_REDIRECT) + .build(VkontakteApi.instance()); + return "redirect:" + vkAuthService.getAuthorizationUrl(); } + OAuth20Service vkService = serviceBuilder + .apiKey(VK_APPID) + .apiSecret(VK_SECRET) + .build(VkontakteApi.instance()); + OAuth2AccessToken token = vkService.getAccessToken(code); - String tokenjson = Utils.fetchURL("https://oauth.vk.com/access_token?client_id=" + VK_APPID + "&redirect_uri=" + URLEncoder.encode(VK_REDIRECT, CharEncoding.UTF_8) + "&client_secret=" + VK_SECRET + "&code=" + URLEncoder.encode(code, CharEncoding.UTF_8)); - if (tokenjson == null || tokenjson.isEmpty()) { - logger.error("VK TOKEN EMPTY"); - response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - return; - } - String token = null; - long vkID = 0; - Token json = mapper.readValue(tokenjson, Token.class); - token = json.getAccessToken(); - vkID = json.getUserId(); - if (token == null || vkID == 0) { - logger.error("VK TOKEN EMPTY: {}", tokenjson); - response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - return; - } - - String graph = Utils.fetchURL("https://api.vk.com/method/users.get?uids=" + vkID + "&fields=screen_name&access_token=" + token); - if (graph == null || graph.isEmpty()) { - logger.error("VK GRAPH ERROR"); - response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - return; - } + OAuthRequest meRequest = new OAuthRequest(Verb.GET, "https://api.vk.com/method/users.get?fields=screen_name"); + vkService.signRequest(token, meRequest); + String graph = vkService.execute(meRequest).getBody(); - try { - com.juick.www.vk.User jsonUser = mapper.readValue(graph, UsersResponse.class).getUsers().get(0); - String vkName = jsonUser.getFirstName() + " " + jsonUser.getLastName(); - String vkLink = jsonUser.getScreenName(); + com.juick.www.vk.User jsonUser = mapper.readValue(graph, UsersResponse.class).getUsers().get(0); + String vkName = jsonUser.getFirstName() + " " + jsonUser.getLastName(); + String vkLink = jsonUser.getScreenName(); - if (vkName == null || vkLink == null || vkName.isEmpty() || vkName.length() == 1 || vkLink.isEmpty()) { - throw new Exception(); - } + if (vkName.length() == 1 || StringUtils.isBlank(vkLink)) { + logger.error("vk user error"); + throw new HttpBadRequestException(); + } - int uid = crosspostService.getUIDbyVKID(vkID); - if (uid > 0) { - Cookie c = new Cookie("hash", userService.getHashByUID(uid)); - c.setMaxAge(50 * 24 * 60 * 60); - response.addCookie(c); - response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); - response.setHeader("Location", "/"); - } else { - String loginhash = UUID.randomUUID().toString(); - if (!crosspostService.createVKUser(vkID, loginhash, token, vkName, vkLink)) { - throw new Exception(); - } - response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); - response.setHeader("Location", "/signup?type=vk&hash=" + loginhash); + Long vkID = NumberUtils.toLong(jsonUser.getUid(), 0); + int uid = crosspostService.getUIDbyVKID(vkID); + if (uid > 0) { + Cookie c = new Cookie("hash", userService.getHashByUID(uid)); + c.setMaxAge(50 * 24 * 60 * 60); + response.addCookie(c); + return Utils.getPreviousPageByRequest(request).orElse("/"); + } else { + String loginhash = UUID.randomUUID().toString(); + if (!crosspostService.createVKUser(vkID, loginhash, token.getAccessToken(), vkName, vkLink)) { + logger.error("create vk user error"); + throw new HttpBadRequestException(); } - } catch (Exception e) { - logger.error("JSON ERROR", e); - response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + return "redirect:/signup?type=vk&hash=" + loginhash; } } } diff --git a/juick-www/src/main/java/com/juick/www/facebook/Graph.java b/juick-www/src/main/java/com/juick/www/facebook/Graph.java deleted file mode 100644 index f4c6ab2a..00000000 --- a/juick-www/src/main/java/com/juick/www/facebook/Graph.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.juick.www.facebook; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Created by vitalyster on 28.11.2016. - */ -public class Graph { - 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; - - 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; - } -} diff --git a/juick-www/src/main/java/com/juick/www/facebook/User.java b/juick-www/src/main/java/com/juick/www/facebook/User.java new file mode 100644 index 00000000..763e98d7 --- /dev/null +++ b/juick-www/src/main/java/com/juick/www/facebook/User.java @@ -0,0 +1,103 @@ +package com.juick.www.facebook; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Created by vitalyster on 28.11.2016. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +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; + + 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; + } +} -- cgit v1.2.3