Browse Source

Merge branch 'master' into develop/3.0

pull/2744/head
Igor Kulikov 6 years ago
parent
commit
0ce5222072
  1. 13
      application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
  2. 7
      application/src/main/java/org/thingsboard/server/controller/AuthController.java
  3. 33
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  4. 7
      application/src/main/java/org/thingsboard/server/controller/UserController.java
  5. 8
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java
  6. 3
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java
  7. 2
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java
  8. 43
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java
  9. 4
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java
  10. 2
      application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationFailureHandler.java
  11. 34
      application/src/main/java/org/thingsboard/server/utils/MiscUtils.java
  12. 6
      application/src/main/resources/thingsboard.yml
  13. 3
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientMapperConfig.java
  14. 17
      ui/src/app/api/user.service.js
  15. 3
      ui/src/app/locale/locale.constant-en_US.json
  16. 2
      ui/src/app/login/login.tpl.html

13
application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java

@ -79,11 +79,18 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
@Qualifier("oauth2AuthenticationSuccessHandler")
private AuthenticationSuccessHandler oauth2AuthenticationSuccessHandler;
@Autowired(required = false)
@Qualifier("oauth2AuthenticationFailureHandler")
private AuthenticationFailureHandler oauth2AuthenticationFailureHandler;
@Autowired
@Qualifier("defaultAuthenticationSuccessHandler")
private AuthenticationSuccessHandler successHandler;
@Autowired private AuthenticationFailureHandler failureHandler;
@Autowired
@Qualifier("defaultAuthenticationFailureHandler")
private AuthenticationFailureHandler failureHandler;
@Autowired private RestAuthenticationProvider restAuthenticationProvider;
@Autowired private JwtAuthenticationProvider jwtAuthenticationProvider;
@Autowired private RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider;
@ -204,11 +211,11 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
http.oauth2Login()
.loginPage("/oauth2Login")
.loginProcessingUrl(oauth2Configuration.getLoginProcessingUrl())
.successHandler(oauth2AuthenticationSuccessHandler);
.successHandler(oauth2AuthenticationSuccessHandler)
.failureHandler(oauth2AuthenticationFailureHandler);
}
}
@Bean
@ConditionalOnMissingBean(CorsFilter.class)
public CorsFilter corsFilter(@Autowired MvcCorsProperties mvcCorsProperties) {

7
application/src/main/java/org/thingsboard/server/controller/AuthController.java

@ -52,6 +52,7 @@ import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.security.model.token.JwtToken;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import org.thingsboard.server.service.security.system.SystemSecurityService;
import org.thingsboard.server.utils.MiscUtils;
import ua_parser.Client;
import javax.servlet.http.HttpServletRequest;
@ -170,7 +171,7 @@ public class AuthController extends BaseController {
try {
String email = resetPasswordByEmailRequest.get("email").asText();
UserCredentials userCredentials = userService.requestPasswordReset(TenantId.SYS_TENANT_ID, email);
String baseUrl = constructBaseUrl(request);
String baseUrl = MiscUtils.constructBaseUrl(request);
String resetUrl = String.format("%s/api/noauth/resetPassword?resetToken=%s", baseUrl,
userCredentials.getResetToken());
@ -218,7 +219,7 @@ public class AuthController extends BaseController {
User user = userService.findUserById(TenantId.SYS_TENANT_ID, credentials.getUserId());
UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail());
SecurityUser securityUser = new SecurityUser(user, credentials.isEnabled(), principal);
String baseUrl = constructBaseUrl(request);
String baseUrl = MiscUtils.constructBaseUrl(request);
String loginUrl = String.format("%s/login", baseUrl);
String email = user.getEmail();
@ -265,7 +266,7 @@ public class AuthController extends BaseController {
User user = userService.findUserById(TenantId.SYS_TENANT_ID, userCredentials.getUserId());
UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail());
SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled(), principal);
String baseUrl = constructBaseUrl(request);
String baseUrl = MiscUtils.constructBaseUrl(request);
String loginUrl = String.format("%s/login", baseUrl);
String email = user.getEmail();
mailService.sendPasswordWasResetEmail(loginUrl, email);

33
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -567,39 +567,6 @@ public abstract class BaseController {
return ruleNode;
}
protected String constructBaseUrl(HttpServletRequest request) {
String scheme = request.getScheme();
String forwardedProto = request.getHeader("x-forwarded-proto");
if (forwardedProto != null) {
scheme = forwardedProto;
}
int serverPort = request.getServerPort();
if (request.getHeader("x-forwarded-port") != null) {
try {
serverPort = request.getIntHeader("x-forwarded-port");
} catch (NumberFormatException e) {
}
} else if (forwardedProto != null) {
switch (forwardedProto) {
case "http":
serverPort = 80;
break;
case "https":
serverPort = 443;
break;
}
}
String baseUrl = String.format("%s://%s:%d",
scheme,
request.getServerName(),
serverPort);
return baseUrl;
}
protected <I extends EntityId> I emptyId(EntityType entityType) {
return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID);
}

7
application/src/main/java/org/thingsboard/server/controller/UserController.java

@ -52,6 +52,7 @@ import org.thingsboard.server.service.security.model.token.JwtToken;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import org.thingsboard.server.utils.MiscUtils;
import javax.servlet.http.HttpServletRequest;
@ -148,7 +149,7 @@ public class UserController extends BaseController {
if (sendEmail) {
SecurityUser authUser = getCurrentUser();
UserCredentials userCredentials = userService.findUserCredentialsByUserId(authUser.getTenantId(), savedUser.getId());
String baseUrl = constructBaseUrl(request);
String baseUrl = MiscUtils.constructBaseUrl(request);
String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl,
userCredentials.getActivateToken());
String email = savedUser.getEmail();
@ -188,7 +189,7 @@ public class UserController extends BaseController {
UserCredentials userCredentials = userService.findUserCredentialsByUserId(getCurrentUser().getTenantId(), user.getId());
if (!userCredentials.isEnabled()) {
String baseUrl = constructBaseUrl(request);
String baseUrl = MiscUtils.constructBaseUrl(request);
String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl,
userCredentials.getActivateToken());
mailService.sendActivationEmail(activateUrl, email);
@ -213,7 +214,7 @@ public class UserController extends BaseController {
SecurityUser authUser = getCurrentUser();
UserCredentials userCredentials = userService.findUserCredentialsByUserId(authUser.getTenantId(), user.getId());
if (!userCredentials.isEnabled()) {
String baseUrl = constructBaseUrl(request);
String baseUrl = MiscUtils.constructBaseUrl(request);
String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl,
userCredentials.getActivateToken());
return activateUrl;

8
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java

@ -63,7 +63,7 @@ public abstract class AbstractOAuth2ClientMapper {
private final Lock userCreationLock = new ReentrantLock();
protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, boolean allowUserCreation) {
protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, boolean allowUserCreation, boolean activateUser) {
UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, oauth2User.getEmail());
User user = userService.findUserByEmail(TenantId.SYS_TENANT_ID, oauth2User.getEmail());
@ -93,8 +93,10 @@ public abstract class AbstractOAuth2ClientMapper {
user.setFirstName(oauth2User.getFirstName());
user.setLastName(oauth2User.getLastName());
user = userService.saveUser(user);
UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId());
userService.activateUserCredentials(user.getTenantId(), userCredentials.getActivateToken(), passwordEncoder.encode(""));
if (activateUser) {
UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId());
userService.activateUserCredentials(user.getTenantId(), userCredentials.getActivateToken(), passwordEncoder.encode(""));
}
}
} catch (Exception e) {
log.error("Can't get or create security user from oauth2 user", e);

3
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java

@ -56,7 +56,8 @@ public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implemen
String customerName = sub.replace(config.getBasic().getCustomerNamePattern());
oauth2User.setCustomerName(customerName);
}
return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.getBasic().isAllowUserCreation());
return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser());
}
private String getTenantName(Map<String, Object> attributes, OAuth2ClientMapperConfig config) {

2
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java

@ -38,7 +38,7 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme
@Override
public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, OAuth2ClientMapperConfig config) {
OAuth2User oauth2User = getOAuth2User(token, config.getCustom());
return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.getBasic().isAllowUserCreation());
return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser());
}
private synchronized OAuth2User getOAuth2User(OAuth2AuthenticationToken token, OAuth2ClientMapperConfig.CustomOAuth2ClientMapperConfig custom) {

43
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java

@ -0,0 +1,43 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.thingsboard.server.service.security.auth.oauth2;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import org.thingsboard.server.utils.MiscUtils;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@Component(value = "oauth2AuthenticationFailureHandler")
@ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true")
public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
String baseUrl = MiscUtils.constructBaseUrl(request);
getRedirectStrategy().sendRedirect(request, response, baseUrl + "/login?loginError=" +
URLEncoder.encode(exception.getMessage(), StandardCharsets.UTF_8.toString()));
}
}

4
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java

@ -27,6 +27,7 @@ import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.token.JwtToken;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import org.thingsboard.server.utils.MiscUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -65,6 +66,7 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
getRedirectStrategy().sendRedirect(request, response, "/?accessToken=" + accessToken.getToken() + "&refreshToken=" + refreshToken.getToken());
String baseUrl = MiscUtils.constructBaseUrl(request);
getRedirectStrategy().sendRedirect(request, response, baseUrl + "/?accessToken=" + accessToken.getToken() + "&refreshToken=" + refreshToken.getToken());
}
}

2
application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationFailureHandler.java

@ -26,7 +26,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
@Component(value = "defaultAuthenticationFailureHandler")
public class RestAwareAuthenticationFailureHandler implements AuthenticationFailureHandler {
private final ThingsboardErrorResponseHandler errorResponseHandler;

34
application/src/main/java/org/thingsboard/server/utils/MiscUtils.java

@ -18,8 +18,8 @@ package org.thingsboard.server.utils;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.Charset;
import java.util.Random;
/**
@ -47,4 +47,36 @@ public class MiscUtils {
throw new IllegalArgumentException("Can't find hash function with name " + name);
}
}
public static String constructBaseUrl(HttpServletRequest request) {
String scheme = request.getScheme();
String forwardedProto = request.getHeader("x-forwarded-proto");
if (forwardedProto != null) {
scheme = forwardedProto;
}
int serverPort = request.getServerPort();
if (request.getHeader("x-forwarded-port") != null) {
try {
serverPort = request.getIntHeader("x-forwarded-port");
} catch (NumberFormatException e) {
}
} else if (forwardedProto != null) {
switch (forwardedProto) {
case "http":
serverPort = 80;
break;
case "https":
serverPort = 443;
break;
}
}
String baseUrl = String.format("%s://%s:%d",
scheme,
request.getServerName(),
serverPort);
return baseUrl;
}
}

6
application/src/main/resources/thingsboard.yml

@ -127,11 +127,13 @@ security:
userInfoUri: "${SECURITY_OAUTH2_DEFAULT_USER_INFO_URI:}"
userNameAttributeName: "${SECURITY_OAUTH2_DEFAULT_USER_NAME_ATTRIBUTE_NAME:email}"
mapperConfig:
# Allows to create user if it not exists
allowUserCreation: "${SECURITY_OAUTH2_DEFAULT_MAPPER_ALLOW_USER_CREATION:true}"
# Allows user to setup ThingsBoard internal password and login over default Login window
activateUser: "${SECURITY_OAUTH2_DEFAULT_MAPPER_ACTIVATE_USER:false}"
# Mapper type of converter from external user into internal - 'basic' or 'custom'
type: "${SECURITY_OAUTH2_DEFAULT_MAPPER_TYPE:basic}"
basic:
# Allows to create user if it not exists
allowUserCreation: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_ALLOW_USER_CREATION:true}"
# Key from attributes of external user object to use as email
emailAttributeKey: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_EMAIL_ATTRIBUTE_KEY:email}"
firstNameAttributeKey: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_FIRST_NAME_ATTRIBUTE_KEY:}"

3
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientMapperConfig.java

@ -20,13 +20,14 @@ import lombok.Data;
@Data
public class OAuth2ClientMapperConfig {
private boolean allowUserCreation;
private boolean activateUser;
private String type;
private BasicOAuth2ClientMapperConfig basic;
private CustomOAuth2ClientMapperConfig custom;
@Data
public static class BasicOAuth2ClientMapperConfig {
private boolean allowUserCreation;
private String emailAttributeKey;
private String firstNameAttributeKey;
private String lastNameAttributeKey;

17
ui/src/app/api/user.service.js

@ -22,7 +22,7 @@ export default angular.module('thingsboard.api.user', [thingsboardApiLogin,
.name;
/*@ngInject*/
function UserService($http, $q, $rootScope, adminService, dashboardService, timeService, loginService, toast, store, jwtHelper, $translate, $state, $location) {
function UserService($http, $q, $rootScope, adminService, dashboardService, timeService, loginService, toast, store, jwtHelper, $translate, $state, $location, $mdDialog) {
var currentUser = null,
currentUserDetails = null,
lastPublicDashboardId = null,
@ -406,6 +406,10 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, time
}, function fail() {
deferred.reject();
});
} else if (locationSearch.loginError) {
showLoginErrorDialog(locationSearch.loginError);
$location.search('loginError', null);
deferred.reject();
} else {
procceedJwtTokenValidate();
}
@ -415,6 +419,17 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, time
return deferred.promise;
}
function showLoginErrorDialog(loginError) {
$translate(['login.error',
'action.close']).then(function (translations) {
var alert = $mdDialog.alert()
.title(translations['login.error'])
.htmlContent(loginError)
.ok(translations['action.close']);
$mdDialog.show(alert);
});
}
function loadIsUserTokenAccessEnabled() {
var deferred = $q.defer();
if (currentUser.authority === 'SYS_ADMIN' || currentUser.authority === 'TENANT_ADMIN') {

3
ui/src/app/locale/locale.constant-en_US.json

@ -1334,7 +1334,8 @@
"password-link-sent-message": "Password reset link was successfully sent!",
"email": "Email",
"login-with": "Login with {{name}}",
"or": "or"
"or": "or",
"error": "Login error"
},
"position": {
"top": "Top",

2
ui/src/app/login/login.tpl.html

@ -51,7 +51,7 @@
<div class="text mat-typography">{{ "login.or" | translate | uppercase }}</div>
<div class="line"><md-divider></md-divider></div>
</div>
<md-button ng-repeat="oauth2Client in oauth2Clients" class="md-raised"
<md-button ng-repeat="oauth2Client in oauth2Clients" class="md-raised md-accent md-hue-2"
layout="row" layout-align="center center" ng-href="{{ oauth2Client.url }}" target="_self">
<md-icon class="material-icons md-18" md-svg-icon="{{ oauth2Client.icon }}"></md-icon>
{{ 'login.login-with' | translate: {name: oauth2Client.name} }}

Loading…
Cancel
Save