Google Authenticaition support
This commit is contained in:
committed by
Paulo Veiga
parent
d88e655eee
commit
2592d338bb
@@ -20,6 +20,8 @@ package com.wisemapping.service;
|
||||
|
||||
import com.wisemapping.exceptions.WiseMappingException;
|
||||
import com.wisemapping.model.User;
|
||||
import com.wisemapping.rest.model.RestResetPasswordResponse;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface UserService {
|
||||
@@ -28,6 +30,10 @@ public interface UserService {
|
||||
|
||||
User createUser(@NotNull User user, boolean emailConfirmEnabled, boolean welcomeEmail) throws WiseMappingException;
|
||||
|
||||
User createUserFromGoogle(@NotNull String callbackCode) throws WiseMappingException;
|
||||
|
||||
User confirmAccountSync(@NotNull String email, @NotNull String code) throws WiseMappingException;
|
||||
|
||||
void changePassword(@NotNull User user);
|
||||
|
||||
User getUserBy(String email);
|
||||
@@ -36,7 +42,7 @@ public interface UserService {
|
||||
|
||||
void updateUser(User user);
|
||||
|
||||
void resetPassword(@NotNull String email) throws InvalidUserEmailException, InvalidAuthSchemaException;
|
||||
RestResetPasswordResponse resetPassword(@NotNull String email) throws InvalidUserEmailException, InvalidAuthSchemaException;
|
||||
|
||||
void removeUser(@NotNull User user);
|
||||
|
||||
|
@@ -23,6 +23,10 @@ import com.wisemapping.exceptions.InvalidMindmapException;
|
||||
import com.wisemapping.exceptions.WiseMappingException;
|
||||
import com.wisemapping.mail.NotificationService;
|
||||
import com.wisemapping.model.*;
|
||||
import com.wisemapping.rest.model.RestResetPasswordAction;
|
||||
import com.wisemapping.rest.model.RestResetPasswordResponse;
|
||||
import com.wisemapping.service.google.GoogleAccountBasicData;
|
||||
import com.wisemapping.service.google.GoogleService;
|
||||
import com.wisemapping.util.VelocityEngineUtils;
|
||||
import com.wisemapping.util.VelocityEngineWrapper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -39,7 +43,7 @@ public class UserServiceImpl
|
||||
private NotificationService notificationService;
|
||||
private MessageSource messageSource;
|
||||
private VelocityEngineWrapper velocityEngineWrapper;
|
||||
|
||||
private GoogleService googleService;
|
||||
|
||||
@Override
|
||||
public void activateAccount(long code)
|
||||
@@ -56,10 +60,15 @@ public class UserServiceImpl
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetPassword(@NotNull String email)
|
||||
public RestResetPasswordResponse resetPassword(@NotNull String email)
|
||||
throws InvalidUserEmailException, InvalidAuthSchemaException {
|
||||
final User user = userManager.getUserBy(email);
|
||||
if (user != null) {
|
||||
RestResetPasswordResponse response = new RestResetPasswordResponse();
|
||||
if (user.getAuthenticationType().equals(AuthenticationType.GOOGLE_OAUTH2)) {
|
||||
response.setAction(RestResetPasswordAction.OAUTH2_USER);
|
||||
return response;
|
||||
}
|
||||
|
||||
if (user.getAuthenticationType() != AuthenticationType.DATABASE) {
|
||||
throw new InvalidAuthSchemaException("Could not change password for " + user.getAuthenticationType().getCode());
|
||||
@@ -72,6 +81,9 @@ public class UserServiceImpl
|
||||
|
||||
// Send an email with the new temporal password ...
|
||||
notificationService.resetPassword(user, password);
|
||||
|
||||
response.setAction(RestResetPasswordAction.EMAIL_SENT);
|
||||
return response;
|
||||
} else {
|
||||
throw new InvalidUserEmailException("The email '" + email + "' does not exists.");
|
||||
}
|
||||
@@ -147,6 +159,55 @@ public class UserServiceImpl
|
||||
return user;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public User createUserFromGoogle(@NotNull String callbackCode) throws WiseMappingException {
|
||||
try {
|
||||
GoogleAccountBasicData data = googleService.processCallback(callbackCode);
|
||||
User existingUser = userManager.getUserBy(data.getEmail());
|
||||
if (existingUser == null) {
|
||||
User newUser = new User();
|
||||
// new registrations from google starts synched
|
||||
newUser.setGoogleSync(true);
|
||||
newUser.setEmail(data.getEmail());
|
||||
newUser.setFirstname(data.getName());
|
||||
newUser.setLastname(data.getLastName());
|
||||
newUser.setAuthenticationType(AuthenticationType.GOOGLE_OAUTH2);
|
||||
newUser.setGoogleToken(data.getAccessToken());
|
||||
existingUser = this.createUser(newUser, false, true);
|
||||
} else {
|
||||
// user exists and doesnt have confirmed account linking, I must wait for confirmation
|
||||
if (existingUser.getGoogleSync() == null) {
|
||||
existingUser.setGoogleSync(false);
|
||||
existingUser.setSyncCode(callbackCode);
|
||||
existingUser.setGoogleToken(data.getAccessToken());
|
||||
userManager.updateUser(existingUser);
|
||||
}
|
||||
|
||||
}
|
||||
return existingUser;
|
||||
} catch (Exception e) {
|
||||
throw new WiseMappingException("Cant create user", e);
|
||||
}
|
||||
}
|
||||
|
||||
public User confirmAccountSync(@NotNull String email, @NotNull String code) throws WiseMappingException {
|
||||
User existingUser = userManager.getUserBy(email);
|
||||
// additional security check
|
||||
if (existingUser == null || !existingUser.getSyncCode().equals(code)) {
|
||||
throw new WiseMappingException("User not found / incorrect code");
|
||||
}
|
||||
existingUser.setGoogleSync(true);
|
||||
existingUser.setSyncCode(null);
|
||||
// user will not be able to login again with usr/pwd schema
|
||||
existingUser.setAuthenticationType(AuthenticationType.GOOGLE_OAUTH2);
|
||||
existingUser.setPassword("");
|
||||
userManager.updateUser(existingUser);
|
||||
|
||||
return existingUser;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Mindmap buildTutorialMindmap(@NotNull String firstName) throws InvalidMindmapException {
|
||||
//To change body of created methods use File | Settings | File Templates.
|
||||
final Locale locale = LocaleContextHolder.getLocale();
|
||||
@@ -209,7 +270,11 @@ public class UserServiceImpl
|
||||
this.velocityEngineWrapper = velocityEngineWrapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGoogleService(GoogleService googleService) {
|
||||
this.googleService = googleService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getCasUserBy(String uid) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
|
@@ -0,0 +1,66 @@
|
||||
package com.wisemapping.service.google;
|
||||
|
||||
public class GoogleAccountBasicData {
|
||||
|
||||
private String email;
|
||||
private String accountId;
|
||||
private String name;
|
||||
private String lastName;
|
||||
private String accessToken;
|
||||
private String refreshToken;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public String getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public void setAccountId(String accountId) {
|
||||
this.accountId = accountId;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public void setAccessToken(String accessToken) {
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
public String getRefreshToken() {
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
public void setRefreshToken(String refreshToken) {
|
||||
this.refreshToken = refreshToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GoogleAccountBasicData [email=" + email + ", accountId=" + accountId + ", name=" + name + ", lastName="
|
||||
+ lastName + ", accessToken=" + accessToken + ", refreshToken=" + refreshToken + "]";
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,106 @@
|
||||
package com.wisemapping.service.google;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.wisemapping.service.http.HttpInvoker;
|
||||
import com.wisemapping.service.http.HttpInvokerContentType;
|
||||
import com.wisemapping.service.http.HttpInvokerException;
|
||||
import com.wisemapping.service.http.HttpMethod;
|
||||
|
||||
@Service
|
||||
public class GoogleService {
|
||||
private HttpInvoker httpInvoker;
|
||||
private String optinConfirmUrl;
|
||||
private String accountBasicDataUrl;
|
||||
private String clientId;
|
||||
private String clientSecret;
|
||||
private String callbackUrl;
|
||||
|
||||
public void setHttpInvoker(HttpInvoker httpInvoker) {
|
||||
this.httpInvoker = httpInvoker;
|
||||
}
|
||||
|
||||
public void setOptinConfirmUrl(String optinConfirmUrl) {
|
||||
this.optinConfirmUrl = optinConfirmUrl;
|
||||
}
|
||||
|
||||
public void setAccountBasicDataUrl(String accountBasicDataUrl) {
|
||||
this.accountBasicDataUrl = accountBasicDataUrl;
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public void setClientSecret(String clientSecret) {
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
public void setCallbackUrl(String callbackUrl) {
|
||||
this.callbackUrl = callbackUrl;
|
||||
}
|
||||
|
||||
private String getNodeAsString(JsonNode node, String fieldName) {
|
||||
return getNodeAsString(node, fieldName, null);
|
||||
}
|
||||
|
||||
private String getNodeAsString(JsonNode node, String fieldName, String defaultValue) {
|
||||
JsonNode subNode = node.get(fieldName);
|
||||
return subNode != null ? subNode.asText() : defaultValue;
|
||||
}
|
||||
|
||||
private Map<String, String> getHeaders(String token) {
|
||||
Map<String, String> headers = new HashMap<String, String>();
|
||||
headers.put("Content-type", "application/json");
|
||||
headers.put("Authorization", "Bearer " + token);
|
||||
return headers;
|
||||
}
|
||||
|
||||
private GoogleAccountBasicData getAccountBasicData(String token) throws HttpInvokerException {
|
||||
JsonNode response = httpInvoker.invoke(accountBasicDataUrl, null, HttpMethod.GET, this.getHeaders(token), null,
|
||||
null);
|
||||
GoogleAccountBasicData data = new GoogleAccountBasicData();
|
||||
data.setEmail(getNodeAsString(response, "email"));
|
||||
data.setAccountId(getNodeAsString(response, "id"));
|
||||
data.setName(getNodeAsString(response, "given_name", data.getEmail()));
|
||||
data.setLastName(getNodeAsString(response, "family_name"));
|
||||
return data;
|
||||
}
|
||||
|
||||
private Map<String, String> getOptinConfirmBody(String code) {
|
||||
Map<String, String> result = new HashMap<String, String>();
|
||||
result.put("client_id", clientId);
|
||||
result.put("client_secret", clientSecret);
|
||||
result.put("code", code);
|
||||
result.put("redirect_uri", callbackUrl);
|
||||
result.put("grant_type", "authorization_code");
|
||||
return result;
|
||||
}
|
||||
|
||||
public GoogleAccountBasicData processCallback(String code)
|
||||
throws HttpInvokerException, JsonMappingException, JsonProcessingException {
|
||||
Map<String, String> body = this.getOptinConfirmBody(code);
|
||||
JsonNode optinConfirmResponse = httpInvoker.invoke(
|
||||
optinConfirmUrl,
|
||||
HttpInvokerContentType.FORM_ENCODED,
|
||||
HttpMethod.POST,
|
||||
null,
|
||||
null,
|
||||
body);
|
||||
|
||||
String accessToken = getNodeAsString(optinConfirmResponse, "access_token");
|
||||
String refreshToken = getNodeAsString(optinConfirmResponse, "refresh_token");
|
||||
|
||||
GoogleAccountBasicData data = this.getAccountBasicData(accessToken);
|
||||
data.setAccessToken(accessToken);
|
||||
data.setRefreshToken(refreshToken);
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,147 @@
|
||||
package com.wisemapping.service.http;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpDelete;
|
||||
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.methods.HttpPut;
|
||||
import org.apache.http.client.methods.HttpRequestBase;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
@Service
|
||||
public class HttpInvoker {
|
||||
|
||||
protected static Logger logger = LogManager.getLogger(HttpInvoker.class);
|
||||
|
||||
private ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
public HttpInvoker() {
|
||||
super();
|
||||
}
|
||||
|
||||
public JsonNode invoke(
|
||||
String url,
|
||||
HttpInvokerContentType requestContentType,
|
||||
HttpMethod method,
|
||||
Map<String, String> headers,
|
||||
String jsonPayload,
|
||||
Map<String, String> formData)
|
||||
throws HttpInvokerException {
|
||||
String responseBody = null;
|
||||
try {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("finalUrl: " + url);
|
||||
logger.debug("method: " + method);
|
||||
logger.debug("payload: " + jsonPayload);
|
||||
logger.debug("header: " + headers);
|
||||
}
|
||||
|
||||
CloseableHttpClient httpClient = HttpClients.createDefault();
|
||||
HttpRequestBase httpRequst = null;
|
||||
|
||||
// build request
|
||||
if (method.equals(HttpMethod.POST))
|
||||
httpRequst = new HttpPost(url);
|
||||
else if (method.equals(HttpMethod.PUT))
|
||||
httpRequst = new HttpPut(url);
|
||||
else if (method.equals(HttpMethod.GET))
|
||||
httpRequst = new HttpGet(url);
|
||||
else if (method.equals(HttpMethod.DELETE))
|
||||
httpRequst = new HttpDelete(url);
|
||||
else
|
||||
throw new HttpInvokerException("Method " + method + " not suppoprted by http connector");
|
||||
|
||||
if (method.equals(HttpMethod.POST) || method.equals(HttpMethod.PUT)) {
|
||||
HttpEntity entity = null;
|
||||
if (requestContentType.equals(HttpInvokerContentType.JSON)) {
|
||||
if (jsonPayload == null)
|
||||
throw new HttpInvokerException("Json content is required");
|
||||
entity = new StringEntity(jsonPayload, Charset.forName("UTF-8"));
|
||||
((HttpEntityEnclosingRequestBase) httpRequst).setEntity(entity);
|
||||
}
|
||||
if (requestContentType.equals(HttpInvokerContentType.FORM_ENCODED)) {
|
||||
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
|
||||
Set<String> keys = formData.keySet();
|
||||
for (String key : keys) {
|
||||
nameValuePairs.add(new BasicNameValuePair(key, formData.get(key).toString()));
|
||||
}
|
||||
entity = new UrlEncodedFormEntity(nameValuePairs);
|
||||
((HttpEntityEnclosingRequestBase) httpRequst).setEntity(entity);
|
||||
}
|
||||
if (entity == null)
|
||||
throw new HttpInvokerException("Cant build entity to send");
|
||||
}
|
||||
|
||||
if (headers != null) {
|
||||
Set<String> keys = headers.keySet();
|
||||
for (String key : keys) {
|
||||
httpRequst.setHeader(key, headers.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
if (requestContentType != null)
|
||||
httpRequst.setHeader("Content-Type", requestContentType.getHttpContentType());
|
||||
|
||||
// invoke
|
||||
CloseableHttpResponse response = httpClient.execute(httpRequst);
|
||||
// response process
|
||||
JsonNode root = null;
|
||||
responseBody = response.getEntity() != null && response.getEntity().getContent() != null
|
||||
? IOUtils.toString(response.getEntity().getContent(), (String) null)
|
||||
: null;
|
||||
if (responseBody != null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("response plain: " + responseBody);
|
||||
}
|
||||
try {
|
||||
root = mapper.readTree(responseBody);
|
||||
} catch (Exception e) {
|
||||
int returnCode = response.getStatusLine().getStatusCode();
|
||||
throw new HttpInvokerException("cant transform response to JSON. RQ: " + jsonPayload + ", RS: "
|
||||
+ responseBody + ", status: " + returnCode, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (response.getStatusLine().getStatusCode() >= 400) {
|
||||
logger.error("error response: " + responseBody);
|
||||
throw new HttpInvokerException("error invoking " + url + ", response: " + responseBody + ", status: "
|
||||
+ response.getStatusLine().getStatusCode());
|
||||
}
|
||||
|
||||
httpRequst.releaseConnection();
|
||||
response.close();
|
||||
httpClient.close();
|
||||
|
||||
return root;
|
||||
} catch (HttpInvokerException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
logger.error("cant invoke service " + url);
|
||||
logger.error("response: " + responseBody, e);
|
||||
throw new HttpInvokerException("cant invoke service " + url, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
package com.wisemapping.service.http;
|
||||
|
||||
public enum HttpInvokerContentType {
|
||||
|
||||
JSON("application/json"),
|
||||
FORM_ENCODED("application/x-www-form-urlencoded");
|
||||
|
||||
private String httpContentType;
|
||||
|
||||
private HttpInvokerContentType(String type) {
|
||||
this.httpContentType = type;
|
||||
}
|
||||
|
||||
public String getHttpContentType() {
|
||||
return httpContentType;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
package com.wisemapping.service.http;
|
||||
|
||||
public class HttpInvokerException extends Exception {
|
||||
|
||||
public HttpInvokerException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public HttpInvokerException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
package com.wisemapping.service.http;
|
||||
|
||||
public enum HttpMethod {
|
||||
POST, GET, DELETE, PUT
|
||||
}
|
Reference in New Issue
Block a user