Google Authenticaition support
parent
d88e655eee
commit
2592d338bb
23
README.md
23
README.md
|
@ -58,6 +58,29 @@ Check out the [docker section](./docker/README.)
|
|||
Individual test result reports can be found in wisemapping-open-source/wise-webapp/target/failsafe-reports/index.html
|
||||
Test coverage report of unit and integration test can be found in wisemapping-open-source/wise-webapp/target/site/jacoco and wisemapping-open-source/wise-webapp/target/site/jacoco-it folders. Coverage report is generated in the verify phase of [lifecicle](https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html#introduction-to-the-build-lifecyclea) using [jacoco](https://www.jacoco.org/jacoco/trunk/doc/maven.html)
|
||||
|
||||
|
||||
## Google authorization
|
||||
|
||||
You must configure the following wisemapping properties (app.properties) in order to get google authorization working
|
||||
* `google.oauth2.callbackUrl`: url where google will redirect after user authentication, tipically {frontendBaseUrl}/c/registration-google. Also, this url must be defined in google app configuration
|
||||
* `google.oauth2.clientId`: client id from google app
|
||||
* `google.oauth2.clientSecret`: client secret from google app
|
||||
|
||||
You must create a Google Application in [Google Cloud](https://console.cloud.google.com) and complete all the information required by Google. Here are the most important properties.
|
||||
|
||||
Oauth consent screen
|
||||
* Authorized domains: wisemapping domain (ex: wisemapping.com), and you can add domains of other environments if needed
|
||||
* Permissions
|
||||
* `https://www.googleapis.com/auth/userinfo.profile`
|
||||
* `https://www.googleapis.com/auth/userinfo.email`
|
||||
* Test users: emails for testing, those can be used before the application is validated by Google
|
||||
|
||||
After that, in Credentials, you must create an `Oauth Client Id` credential
|
||||
* Authorized JavaScript origins: list of authorized domains from which to redirect to Google. Ex: `https://wisemaping.com`, `https://wisemapping-testing.com:8080`
|
||||
* Authorized redirect URIs: list of allowed urls to which google will redirect after authenticating . Ex: `https://wisemaping.com/c/registration-google`, `https://wisemapping-testing.com:8080/c/registration-google`
|
||||
|
||||
After credential was created, Google will show you the clientId and clientSecret to configure your application. For productive applications, you must **publish** your application, this is a validation process with Google.
|
||||
|
||||
## Members
|
||||
|
||||
### Founders
|
||||
|
|
|
@ -15,6 +15,9 @@ CREATE TABLE USER (
|
|||
activation_date DATE,
|
||||
allow_send_email CHAR(1) NOT NULL,
|
||||
locale VARCHAR(5),
|
||||
google_sync BOOLEAN,
|
||||
sync_code VARCHAR(255),
|
||||
google_token VARCHAR(255),
|
||||
FOREIGN KEY (colaborator_id) REFERENCES COLLABORATOR (id)
|
||||
);
|
||||
|
||||
|
|
|
@ -25,6 +25,9 @@ CREATE TABLE USER (
|
|||
activation_date DATE,
|
||||
allow_send_email CHAR(1) CHARACTER SET utf8 NOT NULL DEFAULT 0,
|
||||
locale VARCHAR(5),
|
||||
google_sync BOOL,
|
||||
sync_code VARCHAR(255),
|
||||
google_token VARCHAR(255),
|
||||
FOREIGN KEY (colaborator_id) REFERENCES COLLABORATOR (id)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE NO ACTION
|
||||
|
|
|
@ -15,6 +15,9 @@ CREATE TABLE "user" (
|
|||
activation_date DATE,
|
||||
allow_send_email TEXT NOT NULL DEFAULT 0,
|
||||
locale VARCHAR(5),
|
||||
google_sync BOOLEAN,
|
||||
sync_code VARCHAR(255),
|
||||
google_token VARCHAR(255),
|
||||
FOREIGN KEY (colaborator_id) REFERENCES COLLABORATOR (id) ON DELETE CASCADE ON UPDATE NO ACTION
|
||||
);
|
||||
|
||||
|
|
|
@ -25,6 +25,9 @@ CREATE TABLE USER (
|
|||
activation_date DATE,
|
||||
allow_send_email CHAR(1) CHARACTER SET utf8 NOT NULL DEFAULT 0,
|
||||
locale VARCHAR(5),
|
||||
google_sync BOOL,
|
||||
sync_code VARCHAR(255),
|
||||
google_token VARCHAR(255),
|
||||
FOREIGN KEY (colaborator_id) REFERENCES COLLABORATOR (id)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE NO ACTION
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
ALTER TABLE USER
|
||||
ADD COLUMN `google_sync` TINYINT(1) NULL,
|
||||
ADD COLUMN `sync_code` VARCHAR(255) NULL,
|
||||
ADD COLUMN `google_token` VARCHAR(255) NULL;
|
|
@ -0,0 +1,4 @@
|
|||
ALTER TABLE "user"
|
||||
ADD COLUMN `google_sync` BOOLEAN NULL,
|
||||
ADD COLUMN `sync_code` VARCHAR(255) NULL,
|
||||
ADD COLUMN `google_token` VARCHAR(255) NULL;
|
|
@ -19,6 +19,7 @@
|
|||
package com.wisemapping.dao;
|
||||
|
||||
import com.wisemapping.model.AccessAuditory;
|
||||
import com.wisemapping.model.AuthenticationType;
|
||||
import com.wisemapping.model.Collaboration;
|
||||
import com.wisemapping.model.Collaborator;
|
||||
import com.wisemapping.model.User;
|
||||
|
@ -31,7 +32,6 @@ import org.springframework.orm.hibernate5.HibernateTemplate;
|
|||
import org.springframework.orm.hibernate5.support.HibernateDaoSupport;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
@ -101,7 +101,11 @@ public class UserManagerImpl
|
|||
@Override
|
||||
public void createUser(User user) {
|
||||
assert user != null : "Trying to store a null user";
|
||||
user.setPassword(passwordEncoder.encode(user.getPassword()));
|
||||
if (!AuthenticationType.GOOGLE_OAUTH2.equals(user.getAuthenticationType())) {
|
||||
user.setPassword(passwordEncoder.encode(user.getPassword()));
|
||||
} else {
|
||||
user.setPassword("");
|
||||
}
|
||||
getHibernateTemplate().saveOrUpdate(user);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright [2022] [wisemapping]
|
||||
*
|
||||
* Licensed under WiseMapping Public License, Version 1.0 (the "License").
|
||||
* It is basically the Apache License, Version 2.0 (the "License") plus the
|
||||
* "powered by wisemapping" text requirement on every single page;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the license at
|
||||
*
|
||||
* http://www.wisemapping.org/license
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.wisemapping.filter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
*
|
||||
* If your wisemapping customization throws cross domain errores in browser, you can configure this filter in webdefault.xml
|
||||
* By default it will accept all domains, but you can restrict to the domain you need
|
||||
*
|
||||
* <filter>
|
||||
* <filter-name>cross-origin</filter-name>
|
||||
* <filter-class>com.wisemapping.filter.CorsFilter</filter-class>
|
||||
* <init-param>
|
||||
* <param-name>allowedOrigins</param-name>
|
||||
* <param-value>*</param-value>
|
||||
* </init-param>
|
||||
* <init-param>
|
||||
* <param-name>allowedMethods</param-name>
|
||||
* <param-value>GET,POST,HEAD</param-value>
|
||||
* </init-param>
|
||||
* <init-param>
|
||||
* <param-name>allowedHeaders</param-name>
|
||||
* <param-value>X-Requested-With,Content-Type,Accept,Origin</param-value>
|
||||
* </init-param>
|
||||
* </filter>
|
||||
* <filter-mapping>
|
||||
* <filter-name>cross-origin</filter-name>
|
||||
* <url-pattern>/*</url-pattern>
|
||||
* </filter-mapping>
|
||||
*
|
||||
*/
|
||||
public class CorsFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
if (servletResponse != null) {
|
||||
// Authorize (allow) all domains to consume the content
|
||||
((HttpServletResponse) servletResponse).addHeader("Access-Control-Allow-Origin", "*");
|
||||
((HttpServletResponse) servletResponse).addHeader("Access-Control-Allow-Methods","GET, OPTIONS, HEAD, PUT, POST");
|
||||
}
|
||||
|
||||
chain.doFilter(servletRequest, servletResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
}
|
||||
}
|
|
@ -54,6 +54,9 @@ public class RequestPropertiesInterceptor implements HandlerInterceptor {
|
|||
@Value("${security.type}")
|
||||
private String securityType;
|
||||
|
||||
@Value("${google.oauth2.url}")
|
||||
private String googleOauth2Url;
|
||||
|
||||
@Override
|
||||
public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, Object object) throws Exception {
|
||||
|
||||
|
@ -64,6 +67,8 @@ public class RequestPropertiesInterceptor implements HandlerInterceptor {
|
|||
request.setAttribute("google.recaptcha2.enabled", recaptcha2Enabled);
|
||||
request.setAttribute("google.recaptcha2.siteKey", recaptcha2SiteKey);
|
||||
|
||||
request.setAttribute("google.oauth2.url", googleOauth2Url);
|
||||
|
||||
request.setAttribute("site.homepage", siteHomepage);
|
||||
request.setAttribute("site.static.js.url", siteStaticUrl);
|
||||
|
||||
|
|
|
@ -23,7 +23,8 @@ import org.jetbrains.annotations.NotNull;
|
|||
public enum AuthenticationType {
|
||||
DATABASE('D'),
|
||||
LDAP('L'),
|
||||
OPENID('O');
|
||||
GOOGLE_OAUTH2('G');
|
||||
|
||||
private final char schemaCode;
|
||||
|
||||
AuthenticationType(char schemaCode) {
|
||||
|
|
|
@ -18,15 +18,12 @@
|
|||
|
||||
package com.wisemapping.model;
|
||||
|
||||
import org.hibernate.annotations.CacheConcurrencyStrategy;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.io.Serializable;
|
||||
import java.util.Calendar;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@Entity
|
||||
@Table(name = "USER")
|
||||
|
@ -39,21 +36,30 @@ public class User
|
|||
private String lastname;
|
||||
private String password;
|
||||
private String locale;
|
||||
|
||||
|
||||
@Column(name = "activation_code")
|
||||
private long activationCode;
|
||||
|
||||
|
||||
@Column(name = "activation_date")
|
||||
private Calendar activationDate;
|
||||
|
||||
|
||||
@Column(name = "allow_send_email")
|
||||
private boolean allowSendEmail = false;
|
||||
|
||||
|
||||
@Column(name = "authentication_type")
|
||||
private Character authenticationTypeCode = AuthenticationType.DATABASE.getCode();
|
||||
|
||||
|
||||
@Column(name = "authenticator_uri")
|
||||
private String authenticatorUri;
|
||||
|
||||
@Column(name = "google_sync")
|
||||
private Boolean googleSync;
|
||||
|
||||
@Column(name = "sync_code")
|
||||
private String syncCode;
|
||||
|
||||
@Column(name = "google_token")
|
||||
private String googleToken;
|
||||
|
||||
public User() {
|
||||
}
|
||||
|
@ -151,7 +157,35 @@ public class User
|
|||
this.authenticatorUri = authenticatorUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticationTypeCode(Character authenticationTypeCode) {
|
||||
this.authenticationTypeCode = authenticationTypeCode;
|
||||
}
|
||||
|
||||
public Boolean getGoogleSync() {
|
||||
return googleSync;
|
||||
}
|
||||
|
||||
public void setGoogleSync(Boolean googleSync) {
|
||||
this.googleSync = googleSync;
|
||||
}
|
||||
|
||||
public String getSyncCode() {
|
||||
return syncCode;
|
||||
}
|
||||
|
||||
public void setSyncCode(String syncCode) {
|
||||
this.syncCode = syncCode;
|
||||
}
|
||||
|
||||
public String getGoogleToken() {
|
||||
return googleToken;
|
||||
}
|
||||
|
||||
public void setGoogleToken(String googleToken) {
|
||||
this.googleToken = googleToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "User{" +
|
||||
"firstname='" + firstname + '\'' +
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
package com.wisemapping.rest;
|
||||
|
||||
import com.wisemapping.exceptions.WiseMappingException;
|
||||
import com.wisemapping.mail.NotificationService;
|
||||
import com.wisemapping.model.Collaboration;
|
||||
import com.wisemapping.model.Label;
|
||||
import com.wisemapping.model.Mindmap;
|
||||
|
@ -54,10 +53,6 @@ public class AccountController extends BaseController {
|
|||
@Autowired
|
||||
private LabelService labelService;
|
||||
|
||||
@Autowired
|
||||
private NotificationService notificationService;
|
||||
|
||||
|
||||
@RequestMapping(method = RequestMethod.PUT, value = "account/password", consumes = {"text/plain"})
|
||||
@ResponseStatus(value = HttpStatus.NO_CONTENT)
|
||||
public void changePassword(@RequestBody String password) {
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright [2022] [wisemapping]
|
||||
*
|
||||
* Licensed under WiseMapping Public License, Version 1.0 (the "License").
|
||||
* It is basically the Apache License, Version 2.0 (the "License") plus the
|
||||
* "powered by wisemapping" text requirement on every single page;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the license at
|
||||
*
|
||||
* http://www.wisemapping.org/license
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.wisemapping.rest;
|
||||
|
||||
import com.wisemapping.exceptions.WiseMappingException;
|
||||
import com.wisemapping.model.User;
|
||||
import com.wisemapping.rest.model.RestOath2CallbackResponse;
|
||||
import com.wisemapping.service.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
|
||||
@Controller
|
||||
@CrossOrigin
|
||||
public class Oauth2Controller extends BaseController {
|
||||
@Qualifier("userService")
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Qualifier("authenticationManager")
|
||||
@Autowired
|
||||
private AuthenticationManager authManager;
|
||||
|
||||
@Value("${google.recaptcha2.enabled}")
|
||||
private Boolean recatchaEnabled;
|
||||
|
||||
@Value("${accounts.exclusion.domain:''}")
|
||||
private String domainBanExclusion;
|
||||
|
||||
private void doLogin(HttpServletRequest request, String email) {
|
||||
PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(email,null);
|
||||
Authentication auth = authManager.authenticate(token);
|
||||
SecurityContextHolder.getContext().setAuthentication(auth);
|
||||
// update spring mvc session
|
||||
HttpSession session = request.getSession(true);
|
||||
session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, value = "/oauth2/googlecallback", produces = { "application/json" })
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
public RestOath2CallbackResponse processGoogleCallback(@NotNull @RequestParam String code, @NotNull HttpServletRequest request) throws WiseMappingException {
|
||||
User user = userService.createUserFromGoogle(code);
|
||||
if (user.getGoogleSync() != null && user.getGoogleSync().booleanValue()) {
|
||||
doLogin(request, user.getEmail());
|
||||
}
|
||||
RestOath2CallbackResponse response = new RestOath2CallbackResponse();
|
||||
response.setEmail(user.getEmail());
|
||||
response.setGoogleSync(user.getGoogleSync());
|
||||
response.setSyncCode(user.getSyncCode());
|
||||
return response;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.PUT, value = "/oauth2/confirmaccountsync", produces = { "application/json" })
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
public void confirmAccountSync(@NotNull @RequestParam String email, @NotNull @RequestParam String code, @NotNull HttpServletRequest request) throws WiseMappingException {
|
||||
userService.confirmAccountSync(email, code);
|
||||
doLogin(request, email);
|
||||
}
|
||||
|
||||
}
|
|
@ -22,6 +22,7 @@ import com.wisemapping.exceptions.EmailNotExistsException;
|
|||
import com.wisemapping.exceptions.WiseMappingException;
|
||||
import com.wisemapping.model.AuthenticationType;
|
||||
import com.wisemapping.model.User;
|
||||
import com.wisemapping.rest.model.RestResetPasswordResponse;
|
||||
import com.wisemapping.rest.model.RestUserRegistration;
|
||||
import com.wisemapping.service.*;
|
||||
import com.wisemapping.validator.Messages;
|
||||
|
@ -33,100 +34,110 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
@CrossOrigin
|
||||
public class UserController extends BaseController {
|
||||
@Qualifier("userService")
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
@Qualifier("userService")
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
private RecaptchaService captchaService;
|
||||
@Autowired
|
||||
private RecaptchaService captchaService;
|
||||
|
||||
@Value("${google.recaptcha2.enabled}")
|
||||
private Boolean recatchaEnabled;
|
||||
@Qualifier("authenticationManager")
|
||||
@Autowired
|
||||
private AuthenticationManager authManager;
|
||||
|
||||
@Value("${accounts.exclusion.domain:''}")
|
||||
private String domainBanExclusion;
|
||||
@Value("${google.recaptcha2.enabled}")
|
||||
private Boolean recatchaEnabled;
|
||||
|
||||
private static final Logger logger = LogManager.getLogger();
|
||||
private static final String REAL_IP_ADDRESS_HEADER = "X-Real-IP";
|
||||
@Value("${accounts.exclusion.domain:''}")
|
||||
private String domainBanExclusion;
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, value = "/users", produces = {"application/json"})
|
||||
@ResponseStatus(value = HttpStatus.CREATED)
|
||||
public void registerUser(@RequestBody RestUserRegistration registration, @NotNull HttpServletRequest request, @NotNull HttpServletResponse response) throws WiseMappingException, BindException {
|
||||
logger.debug("Register new user:" + registration.getEmail());
|
||||
private static final Logger logger = LogManager.getLogger();
|
||||
private static final String REAL_IP_ADDRESS_HEADER = "X-Real-IP";
|
||||
|
||||
// If tomcat is behind a reverse proxy, ip needs to be found in other header.
|
||||
String remoteIp = request.getHeader(REAL_IP_ADDRESS_HEADER);
|
||||
if (remoteIp == null || remoteIp.isEmpty()) {
|
||||
remoteIp = request.getRemoteAddr();
|
||||
}
|
||||
logger.debug("Remote address" + remoteIp);
|
||||
@RequestMapping(method = RequestMethod.POST, value = "/users", produces = { "application/json" })
|
||||
@ResponseStatus(value = HttpStatus.CREATED)
|
||||
public void registerUser(@RequestBody RestUserRegistration registration, @NotNull HttpServletRequest request,
|
||||
@NotNull HttpServletResponse response) throws WiseMappingException, BindException {
|
||||
logger.debug("Register new user:" + registration.getEmail());
|
||||
|
||||
verify(registration, remoteIp);
|
||||
// If tomcat is behind a reverse proxy, ip needs to be found in other header.
|
||||
String remoteIp = request.getHeader(REAL_IP_ADDRESS_HEADER);
|
||||
if (remoteIp == null || remoteIp.isEmpty()) {
|
||||
remoteIp = request.getRemoteAddr();
|
||||
}
|
||||
logger.debug("Remote address" + remoteIp);
|
||||
|
||||
final User user = new User();
|
||||
user.setEmail(registration.getEmail().trim());
|
||||
user.setFirstname(registration.getFirstname());
|
||||
user.setLastname(registration.getLastname());
|
||||
user.setPassword(registration.getPassword());
|
||||
verify(registration, remoteIp);
|
||||
|
||||
user.setAuthenticationType(AuthenticationType.DATABASE);
|
||||
userService.createUser(user, false, true);
|
||||
response.setHeader("Location", "/service/users/" + user.getId());
|
||||
}
|
||||
final User user = new User();
|
||||
user.setEmail(registration.getEmail().trim());
|
||||
user.setFirstname(registration.getFirstname());
|
||||
user.setLastname(registration.getLastname());
|
||||
user.setPassword(registration.getPassword());
|
||||
|
||||
@RequestMapping(method = RequestMethod.PUT, value = "/users/resetPassword", produces = {"application/json"})
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
public void resetPassword(@RequestParam String email) throws InvalidAuthSchemaException, EmailNotExistsException {
|
||||
try {
|
||||
userService.resetPassword(email);
|
||||
} catch (InvalidUserEmailException e) {
|
||||
throw new EmailNotExistsException(e);
|
||||
}
|
||||
}
|
||||
user.setAuthenticationType(AuthenticationType.DATABASE);
|
||||
userService.createUser(user, false, true);
|
||||
response.setHeader("Location", "/service/users/" + user.getId());
|
||||
}
|
||||
|
||||
private void verify(@NotNull final RestUserRegistration registration, @NotNull String remoteAddress) throws BindException {
|
||||
@RequestMapping(method = RequestMethod.PUT, value = "/users/resetPassword", produces = { "application/json" })
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
public RestResetPasswordResponse resetPassword(@RequestParam String email) throws InvalidAuthSchemaException, EmailNotExistsException {
|
||||
try {
|
||||
return userService.resetPassword(email);
|
||||
} catch (InvalidUserEmailException e) {
|
||||
throw new EmailNotExistsException(e);
|
||||
}
|
||||
}
|
||||
|
||||
final BindException errors = new RegistrationException(registration, "registration");
|
||||
final UserValidator validator = new UserValidator();
|
||||
validator.setUserService(userService);
|
||||
validator.validate(registration, errors);
|
||||
private void verify(@NotNull final RestUserRegistration registration, @NotNull String remoteAddress)
|
||||
throws BindException {
|
||||
|
||||
// If captcha is enabled, generate it ...
|
||||
if (recatchaEnabled) {
|
||||
final String recaptcha = registration.getRecaptcha();
|
||||
if (recaptcha != null) {
|
||||
final String reCaptchaResponse = captchaService.verifyRecaptcha(remoteAddress, recaptcha);
|
||||
if (reCaptchaResponse != null && !reCaptchaResponse.isEmpty()) {
|
||||
errors.rejectValue("recaptcha", reCaptchaResponse);
|
||||
}
|
||||
} else {
|
||||
errors.rejectValue("recaptcha", Messages.CAPTCHA_LOADING_ERROR);
|
||||
}
|
||||
} else {
|
||||
logger.warn("captchaEnabled is enabled.Recommend to enable it for production environments.");
|
||||
}
|
||||
final BindException errors = new RegistrationException(registration, "registration");
|
||||
final UserValidator validator = new UserValidator();
|
||||
validator.setUserService(userService);
|
||||
validator.validate(registration, errors);
|
||||
|
||||
if (errors.hasErrors()) {
|
||||
throw errors;
|
||||
}
|
||||
// If captcha is enabled, generate it ...
|
||||
if (recatchaEnabled) {
|
||||
final String recaptcha = registration.getRecaptcha();
|
||||
if (recaptcha != null) {
|
||||
final String reCaptchaResponse = captchaService.verifyRecaptcha(remoteAddress, recaptcha);
|
||||
if (reCaptchaResponse != null && !reCaptchaResponse.isEmpty()) {
|
||||
errors.rejectValue("recaptcha", reCaptchaResponse);
|
||||
}
|
||||
} else {
|
||||
errors.rejectValue("recaptcha", Messages.CAPTCHA_LOADING_ERROR);
|
||||
}
|
||||
} else {
|
||||
logger.warn("captchaEnabled is enabled.Recommend to enable it for production environments.");
|
||||
}
|
||||
|
||||
// Is excluded ?.
|
||||
final List<String> excludedDomains = Arrays.asList(domainBanExclusion.split(","));
|
||||
final String emailDomain = registration.getEmail().split("@")[1];
|
||||
if (excludedDomains.contains(emailDomain)) {
|
||||
throw new IllegalArgumentException("Email is part of ban exclusion list due to abuse. Please, contact site admin if you think this is an error." + emailDomain);
|
||||
}
|
||||
}
|
||||
if (errors.hasErrors()) {
|
||||
throw errors;
|
||||
}
|
||||
|
||||
// Is excluded ?.
|
||||
final List<String> excludedDomains = Arrays.asList(domainBanExclusion.split(","));
|
||||
final String emailDomain = registration.getEmail().split("@")[1];
|
||||
if (excludedDomains.contains(emailDomain)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Email is part of ban exclusion list due to abuse. Please, contact site admin if you think this is an error."
|
||||
+ emailDomain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
package com.wisemapping.rest.model;
|
||||
|
||||
public class RestOath2CallbackResponse {
|
||||
|
||||
private String email;
|
||||
private Boolean googleSync;
|
||||
private String syncCode;
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public Boolean getGoogleSync() {
|
||||
return googleSync;
|
||||
}
|
||||
|
||||
public void setGoogleSync(Boolean googleSync) {
|
||||
this.googleSync = googleSync;
|
||||
}
|
||||
|
||||
public String getSyncCode() {
|
||||
return syncCode;
|
||||
}
|
||||
|
||||
public void setSyncCode(String syncCode) {
|
||||
this.syncCode = syncCode;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package com.wisemapping.rest.model;
|
||||
|
||||
public enum RestResetPasswordAction {
|
||||
|
||||
EMAIL_SENT, OAUTH2_USER
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package com.wisemapping.rest.model;
|
||||
|
||||
public class RestResetPasswordResponse {
|
||||
|
||||
RestResetPasswordAction action;
|
||||
|
||||
public RestResetPasswordAction getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(RestResetPasswordAction action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
}
|
|
@ -23,6 +23,7 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
|||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.wisemapping.model.AuthenticationType;
|
||||
import com.wisemapping.model.User;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
@ -102,6 +103,10 @@ public class RestUser {
|
|||
return this.user;
|
||||
}
|
||||
|
||||
public AuthenticationType getAuthenticationType() {
|
||||
return user.getAuthenticationType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof RestUser)) {
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
package com.wisemapping.security;
|
||||
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
||||
|
||||
import com.wisemapping.model.User;
|
||||
|
||||
public class GoogleAuthenticationProvider implements org.springframework.security.authentication.AuthenticationProvider {
|
||||
|
||||
private UserDetailsService userDetailsService;
|
||||
|
||||
public UserDetailsService getUserDetailsService() {
|
||||
return userDetailsService;
|
||||
}
|
||||
|
||||
public void setUserDetailsService(UserDetailsService userDetailsService) {
|
||||
this.userDetailsService = userDetailsService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the given PreAuthenticatedAuthenticationToken.
|
||||
*
|
||||
* If the principal contained in the authentication object is null, the request will
|
||||
* be ignored to allow other providers to authenticate it.
|
||||
*/
|
||||
@Override
|
||||
public Authentication authenticate(Authentication inputToken) throws AuthenticationException {
|
||||
if (!supports(inputToken.getClass())) {
|
||||
return null;
|
||||
}
|
||||
if (inputToken.getPrincipal() == null) {
|
||||
throw new BadCredentialsException("No pre-authenticated principal found in request.");
|
||||
}
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(inputToken.getName());
|
||||
final User user = userDetails.getUser();
|
||||
|
||||
if (!user.isActive()) {
|
||||
throw new BadCredentialsException("User has been disabled for login " + inputToken.getName());
|
||||
}
|
||||
|
||||
PreAuthenticatedAuthenticationToken resultToken = new PreAuthenticatedAuthenticationToken(userDetails,
|
||||
inputToken.getCredentials(), userDetails.getAuthorities());
|
||||
resultToken.setDetails(userDetails);
|
||||
|
||||
userDetailsService.getUserService().auditLogin(user);
|
||||
|
||||
return resultToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that this provider only supports PreAuthenticatedAuthenticationToken
|
||||
* (sub)classes.
|
||||
*/
|
||||
@Override
|
||||
public final boolean supports(Class<?> authentication) {
|
||||
return PreAuthenticatedAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -23,7 +23,6 @@ import com.wisemapping.model.User;
|
|||
import com.wisemapping.service.UserService;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -39,6 +39,11 @@ public class UsersController {
|
|||
return new ModelAndView("forgot-password");
|
||||
}
|
||||
|
||||
@RequestMapping(value = "registration-google", method = RequestMethod.GET)
|
||||
public ModelAndView processGoogleCallback() {
|
||||
return new ModelAndView("registration-google");
|
||||
}
|
||||
|
||||
@RequestMapping(value = "registration", method = RequestMethod.GET)
|
||||
public ModelAndView showRegistrationPage() {
|
||||
return new ModelAndView("registration");
|
||||
|
|
|
@ -139,6 +139,17 @@ security.ldap.firstName.attribute=givenName
|
|||
# Coma separated list of domains and emails ban
|
||||
#accounts.exclusion.domain=
|
||||
|
||||
|
||||
|
||||
# google will redirect to this url, this url must be configured in the google app
|
||||
# {baseurl}/c/registration-google
|
||||
google.oauth2.callbackUrl=https://wisemapping.com/c/registration-google
|
||||
# google app client id
|
||||
google.oauth2.clientId=
|
||||
# google app client secret
|
||||
google.oauth2.clientSecret=
|
||||
# google service for finish registration process, ie. exchange temporal code for user token
|
||||
google.oauth2.confirmUrl=https://oauth2.googleapis.com/token
|
||||
# google service for get user data (name, email, etc)
|
||||
google.oauth2.userinfoUrl=https://www.googleapis.com/oauth2/v3/userinfo
|
||||
# url for starting auth process with google
|
||||
google.oauth2.url=https://accounts.google.com/o/oauth2/v2/auth?redirect_uri=${google.oauth2.callbackUrl}&prompt=consent&response_type=code&client_id=${google.oauth2.clientId}&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&access_type=offline&state=wisemapping&include_granted_scopes=true
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
<definition name="login" template="/jsp/reactInclude.jsp"/>
|
||||
<definition name="registration" template="/jsp/reactInclude.jsp"/>
|
||||
<definition name="registration-google" template="/jsp/reactInclude.jsp"/>
|
||||
<definition name="forgot-password" template="/jsp/reactInclude.jsp"/>
|
||||
<definition name="mindmapList" template="/jsp/reactInclude.jsp"/>
|
||||
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
<bean id="passwordEncoder" class="com.wisemapping.security.DefaultPasswordEncoderFactories" factory-method="createDelegatingPasswordEncoder"/>
|
||||
|
||||
<sec:authentication-manager alias="authenticationManager">
|
||||
<sec:authentication-provider ref="dbAuthenticationProvider"/>
|
||||
<sec:authentication-provider ref="dbAuthenticationProvider" />
|
||||
<sec:authentication-provider ref="googleAuthenticationProvider" />
|
||||
<sec:authentication-provider user-service-ref="userDetailsService"/>
|
||||
</sec:authentication-manager>
|
||||
|
||||
|
@ -19,4 +20,7 @@
|
|||
<property name="userDetailsService" ref="userDetailsService"/>
|
||||
<property name="encoder" ref="passwordEncoder"/>
|
||||
</bean>
|
||||
<bean id="googleAuthenticationProvider" class="com.wisemapping.security.GoogleAuthenticationProvider">
|
||||
<property name="userDetailsService" ref="userDetailsService"/>
|
||||
</bean>
|
||||
</beans>
|
|
@ -3,12 +3,12 @@
|
|||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:sec="http://www.springframework.org/schema/security"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/security
|
||||
http://www.springframework.org/schema/security/spring-security.xsd">
|
||||
|
||||
<bean id="custom-firewall" class="org.springframework.security.web.firewall.StrictHttpFirewall">
|
||||
<bean id="custom-firewall" class="org.springframework.security.web.firewall.StrictHttpFirewall">
|
||||
<property name="allowSemicolon" value="true"/>
|
||||
</bean>
|
||||
|
||||
|
@ -34,6 +34,9 @@
|
|||
|
||||
<sec:intercept-url pattern="/service/users/" method="POST" access="permitAll"/>
|
||||
<sec:intercept-url pattern="/service/users/resetPassword" method="PUT" access="permitAll"/>
|
||||
|
||||
<sec:intercept-url pattern="/service/oauth2/googlecallback" method="POST" access="permitAll"/>
|
||||
<sec:intercept-url pattern="/service/oauth2/confirmaccountsync" method="PUT" access="permitAll"/>
|
||||
|
||||
<sec:intercept-url pattern="/service/admin/users/**" access="isAuthenticated() and hasRole('ROLE_ADMIN')"/>
|
||||
<sec:intercept-url pattern="/service/admin/database/**" access="isAuthenticated() and hasRole('ROLE_ADMIN')"/>
|
||||
|
@ -47,6 +50,7 @@
|
|||
<sec:intercept-url pattern="/c/login" access="permitAll"/>
|
||||
<sec:intercept-url pattern="/c/registration" access="hasRole('ANONYMOUS')"/>
|
||||
<sec:intercept-url pattern="/c/registration-success" access="hasRole('ANONYMOUS')"/>
|
||||
<sec:intercept-url pattern="/c/registration-google" access="permitAll"/>
|
||||
<sec:intercept-url pattern="/c/forgot-password" access="hasRole('ANONYMOUS')"/>
|
||||
<sec:intercept-url pattern="/c/forgot-password-success" access="hasRole('ANONYMOUS')"/>
|
||||
|
||||
|
|
|
@ -18,12 +18,25 @@
|
|||
<property name="velocityEngineWrapper" ref="velocityEngineWrapper"/>
|
||||
</bean>
|
||||
|
||||
<bean id="httpInvoker" class="com.wisemapping.service.http.HttpInvoker">
|
||||
</bean>
|
||||
|
||||
<bean id="googleService" class="com.wisemapping.service.google.GoogleService">
|
||||
<property name="httpInvoker" ref="httpInvoker"/>
|
||||
<property name="optinConfirmUrl" value="${google.oauth2.confirmUrl}"/>
|
||||
<property name="accountBasicDataUrl" value="${google.oauth2.userinfoUrl}"/>
|
||||
<property name="clientId" value="${google.oauth2.clientId}"/>
|
||||
<property name="clientSecret" value="${google.oauth2.clientSecret}"/>
|
||||
<property name="callbackUrl" value="${google.oauth2.callbackUrl}"/>
|
||||
</bean>
|
||||
|
||||
<bean id="userServiceTarget" class="com.wisemapping.service.UserServiceImpl">
|
||||
<property name="userManager" ref="userManager"/>
|
||||
<property name="mindmapService" ref="mindMapServiceTarget"/>
|
||||
<property name="notificationService" ref="notificationService"/>
|
||||
<property name="messageSource" ref="messageSource"/>
|
||||
<property name="velocityEngineWrapper" ref="velocityEngineWrapper"/>
|
||||
<property name="googleService" ref="googleService"/>
|
||||
</bean>
|
||||
|
||||
<bean id="userService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
|
||||
|
|
|
@ -19,7 +19,8 @@
|
|||
analyticsAccount: '${requestScope['google.analytics.account']}',
|
||||
clientType: 'rest',
|
||||
recaptcha2Enabled: ${requestScope['google.recaptcha2.enabled']},
|
||||
recaptcha2SiteKey: '${requestScope['google.recaptcha2.siteKey']}'
|
||||
recaptcha2SiteKey: '${requestScope['google.recaptcha2.siteKey']}',
|
||||
googleOauth2Url: '${requestScope['google.oauth2.url']}'
|
||||
};
|
||||
|
||||
</script>
|
||||
|
|
Loading…
Reference in New Issue