From 7b4ebc75ff720f10a7e58a2f7e78e331ad47c824 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Mon, 4 May 2020 11:41:37 +0300 Subject: [PATCH 01/14] Renamed env variabled. Added sync --- .../security/auth/oauth2/BasicOAuth2ClientMapper.java | 7 +++---- .../security/auth/oauth2/CustomOAuth2ClientMapper.java | 2 +- application/src/main/resources/thingsboard.yml | 4 ++-- .../server/dao/oauth2/OAuth2ClientMapperConfig.java | 4 ++-- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java index 935f7f5e3a..1d44a772aa 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java @@ -51,9 +51,9 @@ public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implemen String firstName = getStringAttributeByKey(attributes, config.getBasic().getFirstNameAttributeKey()); oauth2User.setFirstName(firstName); } - if (!StringUtils.isEmpty(config.getBasic().getCustomerNameStrategyPattern())) { + if (!StringUtils.isEmpty(config.getBasic().getCustomerNamePattern())) { StrSubstitutor sub = new StrSubstitutor(attributes, START_PLACEHOLDER_PREFIX, END_PLACEHOLDER_PREFIX); - String customerName = sub.replace(config.getBasic().getCustomerNameStrategyPattern()); + String customerName = sub.replace(config.getBasic().getCustomerNamePattern()); oauth2User.setCustomerName(customerName); } return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.getBasic().isAllowUserCreation()); @@ -68,7 +68,7 @@ public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implemen return email.substring(email .indexOf("@") + 1); case CUSTOM_TENANT_STRATEGY: StrSubstitutor sub = new StrSubstitutor(attributes, START_PLACEHOLDER_PREFIX, END_PLACEHOLDER_PREFIX); - return sub.replace(config.getBasic().getTenantNameStrategyPattern()); + return sub.replace(config.getBasic().getTenantNamePattern()); default: throw new RuntimeException("Tenant Name Strategy with type " + config.getBasic().getTenantNameStrategy() + " is not supported!"); } @@ -78,7 +78,6 @@ public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implemen String result = null; try { result = (String) attributes.get(key); - } catch (Exception e) { log.warn("Can't convert attribute to String by key " + key); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java index 31e2c2bbec..832a1cd39b 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java @@ -41,7 +41,7 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.getBasic().isAllowUserCreation()); } - public OAuth2User getOAuth2User(OAuth2AuthenticationToken token, OAuth2ClientMapperConfig.CustomOAuth2ClientMapperConfig custom) { + private synchronized OAuth2User getOAuth2User(OAuth2AuthenticationToken token, OAuth2ClientMapperConfig.CustomOAuth2ClientMapperConfig custom) { if (!StringUtils.isEmpty(custom.getUsername()) && !StringUtils.isEmpty(custom.getPassword())) { restTemplateBuilder = restTemplateBuilder.basicAuthentication(custom.getUsername(), custom.getPassword()); } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 1aada9dd9d..254162b56b 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -126,8 +126,8 @@ security: firstNameAttributeKey: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_FIRST_NAME_ATTRIBUTE_KEY:}" lastNameAttributeKey: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_LAST_NAME_ATTRIBUTE_KEY:}" tenantNameStrategy: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_TENANT_NAME_STRATEGY:domain}" # domain, email or custom - tenantNameStrategyPattern: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_TENANT_NAME_STRATEGY_PATTERN:}" - customerNameStrategyPattern: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_CUSTOMER_NAME_STRATEGY_PATTERN:}" + tenantNamePattern: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_TENANT_NAME_PATTERN:}" # %{attribute_key} as placeholder for attributes value by key + customerNamePattern: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_CUSTOMER_NAME_PATTERN:}" # %{attribute_key} as placeholder for attributes value by key custom: url: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_URL:}" username: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_USERNAME:}" diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientMapperConfig.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientMapperConfig.java index ec4f199549..695bb0aa0e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientMapperConfig.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientMapperConfig.java @@ -31,8 +31,8 @@ public class OAuth2ClientMapperConfig { private String firstNameAttributeKey; private String lastNameAttributeKey; private String tenantNameStrategy; - private String tenantNameStrategyPattern; - private String customerNameStrategyPattern; + private String tenantNamePattern; + private String customerNamePattern; } @Data From 294b0e9e689abbb126fa14d75f578212fcc3b8e4 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 5 May 2020 16:49:40 +0300 Subject: [PATCH 02/14] Fix entity admin wudgets aliases --- application/src/main/data/json/demo/dashboards/gateways.json | 4 ++-- .../data/json/system/widget_bundles/entity_admin_widgets.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/application/src/main/data/json/demo/dashboards/gateways.json b/application/src/main/data/json/demo/dashboards/gateways.json index f6370696c1..7d75908229 100644 --- a/application/src/main/data/json/demo/dashboards/gateways.json +++ b/application/src/main/data/json/demo/dashboards/gateways.json @@ -5,7 +5,7 @@ "94715984-ae74-76e4-20b7-2f956b01ed80": { "isSystemType": true, "bundleAlias": "entity_admin_widgets", - "typeAlias": "device_admin_table2", + "typeAlias": "device_admin_table", "type": "latest", "title": "New widget", "sizeX": 24, @@ -1271,4 +1271,4 @@ } }, "name": "Gateways" -} \ No newline at end of file +} diff --git a/application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json b/application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json index c0d0fa7cdf..b65445f3ca 100644 --- a/application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json @@ -6,7 +6,7 @@ }, "widgetTypes": [ { - "alias": "device_admin_table2", + "alias": "device_admin_table", "name": "Device admin table", "descriptor": { "type": "latest", @@ -22,7 +22,7 @@ } }, { - "alias": "device_admin_table", + "alias": "asset_admin_table", "name": "Asset admin table", "descriptor": { "type": "latest", From 69ca93e4581dfbd5f70c40d9f40a1f9b81897131 Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Tue, 5 May 2020 18:07:18 +0300 Subject: [PATCH 03/14] Updated URL for package markerClusterGoogle (#2732) --- ui/src/app/widget/lib/google-map.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/app/widget/lib/google-map.js b/ui/src/app/widget/lib/google-map.js index 04a8b752c6..dd39efb048 100644 --- a/ui/src/app/widget/lib/google-map.js +++ b/ui/src/app/widget/lib/google-map.js @@ -94,7 +94,7 @@ export default class TbGoogleMap { window[this.initMapFunctionName] = function() { // eslint-disable-line no-undef, angular/window-service lazyLoad.load([ // eslint-disable-line no-undef { type: 'js', path: 'https://unpkg.com/@google/markerwithlabel@1.2.3/src/markerwithlabel.js' }, - { type: 'js', path: 'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/markerclusterer.js' } + { type: 'js', path: 'https://unpkg.com/@google/markerclustererplus@4.0.1/dist/markerclustererplus.min.js' } ]).then( function success() { gmGlobals.gmApiKeys[tbMap.apiKey].loaded = true; From 8d0607d54c75007c137dc5221d665c933b57a04a Mon Sep 17 00:00:00 2001 From: VoBa Date: Tue, 5 May 2020 18:18:01 +0300 Subject: [PATCH 04/14] Added/improved comments for OAuth 2 configuration (#2731) * Renamed env variabled. Added sync * Added/improved comments for OAuth 2 configuration --- .../src/main/resources/thingsboard.yml | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 03e461015f..8010987a43 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -100,34 +100,52 @@ security: basic: enabled: "${SECURITY_BASIC_ENABLED:false}" oauth2: + # Enable/disable OAuth 2 login functionality + # For details please refer to https://thingsboard.io/docs/user-guide/oauth-2-support/ enabled: "${SECURITY_OAUTH2_ENABLED:false}" + # Redirect URL where access code from external user management system will be processed loginProcessingUrl: "${SECURITY_OAUTH2_LOGIN_PROCESSING_URL:/login/oauth2/code/}" + # List of SSO clients clients: default: - loginButtonLabel: "${SECURITY_OAUTH2_DEFAULT_LOGIN_BUTTON_LABEL:Default}" # Label that going to be show on login screen - loginButtonIcon: "${SECURITY_OAUTH2_DEFAULT_LOGIN_BUTTON_ICON:}" # Icon that going to be show on login screen. Material design icon ID (https://material.angularjs.org/latest/api/directive/mdIcon) + # Label that going to be show on login button - 'Login with {loginButtonLabel}' + loginButtonLabel: "${SECURITY_OAUTH2_DEFAULT_LOGIN_BUTTON_LABEL:Default}" + # Icon that going to be show on login button. Material design icon ID (https://material.angularjs.org/latest/api/directive/mdIcon) + loginButtonIcon: "${SECURITY_OAUTH2_DEFAULT_LOGIN_BUTTON_ICON:}" clientName: "${SECURITY_OAUTH2_DEFAULT_CLIENT_NAME:ClientName}" clientId: "${SECURITY_OAUTH2_DEFAULT_CLIENT_ID:}" clientSecret: "${SECURITY_OAUTH2_DEFAULT_CLIENT_SECRET:}" accessTokenUri: "${SECURITY_OAUTH2_DEFAULT_ACCESS_TOKEN_URI:}" authorizationUri: "${SECURITY_OAUTH2_DEFAULT_AUTHORIZATION_URI:}" scope: "${SECURITY_OAUTH2_DEFAULT_SCOPE:}" - redirectUriTemplate: "${SECURITY_OAUTH2_DEFAULT_REDIRECT_URI_TEMPLATE:http://localhost:8080/login/oauth2/code/}" # Must be in sync with security.oauth2.loginProcessingUrl + # Redirect URL that must be in sync with 'security.oauth2.loginProcessingUrl', but domain name added + redirectUriTemplate: "${SECURITY_OAUTH2_DEFAULT_REDIRECT_URI_TEMPLATE:http://localhost:8080/login/oauth2/code/}" jwkSetUri: "${SECURITY_OAUTH2_DEFAULT_JWK_SET_URI:}" - authorizationGrantType: "${SECURITY_OAUTH2_DEFAULT_AUTHORIZATION_GRANT_TYPE:authorization_code}" # authorization_code, implicit, refresh_token or client_credentials + # 'authorization_code', 'implicit', 'refresh_token' or 'client_credentials' + authorizationGrantType: "${SECURITY_OAUTH2_DEFAULT_AUTHORIZATION_GRANT_TYPE:authorization_code}" clientAuthenticationMethod: "${SECURITY_OAUTH2_DEFAULT_CLIENT_AUTHENTICATION_METHOD:post}" # basic or post userInfoUri: "${SECURITY_OAUTH2_DEFAULT_USER_INFO_URI:}" userNameAttributeName: "${SECURITY_OAUTH2_DEFAULT_USER_NAME_ATTRIBUTE_NAME:email}" mapperConfig: - type: "${SECURITY_OAUTH2_DEFAULT_MAPPER_TYPE:basic}" # basic or custom + # Mapper type of converter from external user into internal - 'basic' or 'custom' + type: "${SECURITY_OAUTH2_DEFAULT_MAPPER_TYPE:basic}" basic: - allowUserCreation: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_ALLOW_USER_CREATION:true}" # Allows to create user if it not exists - emailAttributeKey: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_EMAIL_ATTRIBUTE_KEY:email}" # Attribute key to use as email for the user + # 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:}" lastNameAttributeKey: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_LAST_NAME_ATTRIBUTE_KEY:}" - tenantNameStrategy: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_TENANT_NAME_STRATEGY:domain}" # domain, email or custom - tenantNamePattern: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_TENANT_NAME_PATTERN:}" # %{attribute_key} as placeholder for attributes value by key - customerNamePattern: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_CUSTOMER_NAME_PATTERN:}" # %{attribute_key} as placeholder for attributes value by key + # Strategy for generating Tenant from external user object - 'domain', 'email' or 'custom' + # 'domain' - name of the Tenant will be extracted as domain from the email of the user + # 'email' - name of the Tenant will email of the user + # 'custom' - please configure 'tenantNamePattern' for custom mapping + tenantNameStrategy: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_TENANT_NAME_STRATEGY:domain}" + # %{attribute_key} as placeholder for attribute value of attributes of external user object + tenantNamePattern: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_TENANT_NAME_PATTERN:}" + # If this field is not empty, user will be created as a user under defined Customer + # %{attribute_key} as placeholder for attribute value of attributes of external user object + customerNamePattern: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_CUSTOMER_NAME_PATTERN:}" custom: url: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_URL:}" username: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_USERNAME:}" From 1c2361fedd0bcbcb4936580daa34c9f2af6401ab Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Tue, 5 May 2020 19:16:02 +0300 Subject: [PATCH 05/14] Exclude old spring-core version --- pom.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pom.xml b/pom.xml index 524fb8ec3e..0fb32edc10 100755 --- a/pom.xml +++ b/pom.xml @@ -468,11 +468,23 @@ org.springframework.security spring-security-oauth2-client ${spring-security.version} + + + org.springframework + spring-core + + org.springframework.security spring-security-oauth2-jose ${spring-security.version} + + + org.springframework + spring-core + + org.springframework.boot From 549ab8addf4be5b5e0e7139a4c087e50d20c2e17 Mon Sep 17 00:00:00 2001 From: VoBa Date: Tue, 5 May 2020 19:24:35 +0300 Subject: [PATCH 06/14] Exclude old spring-core version (#2733) * Renamed env variabled. Added sync * Exclude old spring-core version --- pom.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pom.xml b/pom.xml index 524fb8ec3e..0fb32edc10 100755 --- a/pom.xml +++ b/pom.xml @@ -468,11 +468,23 @@ org.springframework.security spring-security-oauth2-client ${spring-security.version} + + + org.springframework + spring-core + + org.springframework.security spring-security-oauth2-jose ${spring-security.version} + + + org.springframework + spring-core + + org.springframework.boot From 92c9bc0ae569e01cdfc47cf70085ef9953a2be3d Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 6 May 2020 09:58:52 +0300 Subject: [PATCH 07/14] Improve oauth mapper - create default rule chains for tenant --- .../auth/oauth2/AbstractOAuth2ClientMapper.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java index a5c53f6cff..53acdd5e85 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java @@ -31,9 +31,11 @@ import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.user.UserService; +import org.thingsboard.server.service.install.InstallScripts; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.UserPrincipal; +import java.io.IOException; import java.util.List; import java.util.Optional; import java.util.concurrent.locks.Lock; @@ -51,6 +53,9 @@ public abstract class AbstractOAuth2ClientMapper { @Autowired private CustomerService customerService; + @Autowired + private InstallScripts installScripts; + private final Lock userCreationLock = new ReentrantLock(); protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, boolean allowUserCreation) { @@ -84,6 +89,9 @@ public abstract class AbstractOAuth2ClientMapper { user.setLastName(oauth2User.getLastName()); user = userService.saveUser(user); } + } catch (Exception e) { + log.error("Can't get or create security user from oauth2 user", e); + throw new RuntimeException("Can't get or create security user from oauth2 user", e); } finally { userCreationLock.unlock(); } @@ -98,13 +106,14 @@ public abstract class AbstractOAuth2ClientMapper { } } - private TenantId getTenantId(String tenantName) { + private TenantId getTenantId(String tenantName) throws IOException { List tenants = tenantService.findTenants(new TextPageLink(1, tenantName)).getData(); Tenant tenant; if (tenants == null || tenants.isEmpty()) { tenant = new Tenant(); tenant.setTitle(tenantName); tenant = tenantService.saveTenant(tenant); + installScripts.createDefaultRuleChains(tenant.getId()); } else { tenant = tenants.get(0); } From 7a633ed60a710113de91fb47a7d3e110d1373c91 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Wed, 6 May 2020 11:10:54 +0300 Subject: [PATCH 08/14] Fixed issue with cretion/delete of relation because of concurrent access to class variable --- .../action/TbAbstractRelationActionNode.java | 15 ++--- .../engine/action/TbCreateRelationNode.java | 57 +++++++++---------- .../engine/action/TbDeleteRelationNode.java | 21 +++---- 3 files changed, 44 insertions(+), 49 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java index 5ec91bdf0e..dd23c794f4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java @@ -78,7 +78,8 @@ public abstract class TbAbstractRelationActionNode ctx.tellNext(filterResult.getMsg(), filterResult.isResult() ? SUCCESS : FAILURE), t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor()); } @@ -86,13 +87,13 @@ public abstract class TbAbstractRelationActionNode processEntityRelationAction(TbContext ctx, TbMsg msg) { - return Futures.transformAsync(getEntity(ctx, msg), entityContainer -> doProcessEntityRelationAction(ctx, msg, entityContainer), MoreExecutors.directExecutor()); + protected ListenableFuture processEntityRelationAction(TbContext ctx, TbMsg msg, String relationType) { + return Futures.transformAsync(getEntity(ctx, msg), entityContainer -> doProcessEntityRelationAction(ctx, msg, entityContainer, relationType), MoreExecutors.directExecutor()); } protected abstract boolean createEntityIfNotExists(); - protected abstract ListenableFuture doProcessEntityRelationAction(TbContext ctx, TbMsg msg, EntityContainer entityContainer); + protected abstract ListenableFuture doProcessEntityRelationAction(TbContext ctx, TbMsg msg, EntityContainer entityContainer, String relationType); protected abstract C loadEntityNodeActionConfig(TbNodeConfiguration configuration) throws TbNodeException; @@ -120,11 +121,11 @@ public abstract class TbAbstractRelationActionNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java index de74551c22..3c2e0bc405 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java @@ -57,8 +57,6 @@ import java.util.List; ) public class TbCreateRelationNode extends TbAbstractRelationActionNode { - private String relationType; - @Override protected TbCreateRelationNodeConfiguration loadEntityNodeActionConfig(TbNodeConfiguration configuration) throws TbNodeException { return TbNodeUtils.convert(configuration, TbCreateRelationNodeConfiguration.class); @@ -70,8 +68,8 @@ public class TbCreateRelationNode extends TbAbstractRelationActionNode doProcessEntityRelationAction(TbContext ctx, TbMsg msg, EntityContainer entity) { - ListenableFuture future = createIfAbsent(ctx, msg, entity); + protected ListenableFuture doProcessEntityRelationAction(TbContext ctx, TbMsg msg, EntityContainer entity, String relationType) { + ListenableFuture future = createIfAbsent(ctx, msg, entity, relationType); return Futures.transform(future, result -> { RelationContainer container = new RelationContainer(); if (result && config.isChangeOriginatorToRelatedEntity()) { @@ -85,13 +83,12 @@ public class TbCreateRelationNode extends TbAbstractRelationActionNode createIfAbsent(TbContext ctx, TbMsg msg, EntityContainer entityContainer) { - relationType = processPattern(msg, config.getRelationType()); + private ListenableFuture createIfAbsent(TbContext ctx, TbMsg msg, EntityContainer entityContainer, String relationType) { SearchDirectionIds sdId = processSingleSearchDirection(msg, entityContainer); ListenableFuture checkRelationFuture = Futures.transformAsync(ctx.getRelationService().checkRelation(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), relationType, RelationTypeGroup.COMMON), result -> { if (!result) { if (config.isRemoveCurrentRelations()) { - return processDeleteRelations(ctx, processFindRelations(ctx, msg, sdId)); + return processDeleteRelations(ctx, processFindRelations(ctx, msg, sdId, relationType)); } return Futures.immediateFuture(false); } @@ -100,14 +97,14 @@ public class TbCreateRelationNode extends TbAbstractRelationActionNode { if (!result) { - return processCreateRelation(ctx, entityContainer, sdId); + return processCreateRelation(ctx, entityContainer, sdId, relationType); } return Futures.immediateFuture(true); }, ctx.getDbCallbackExecutor()); } - private ListenableFuture> processFindRelations(TbContext ctx, TbMsg msg, SearchDirectionIds sdId) { - if (sdId.isOrignatorDirectionFrom()) { + private ListenableFuture> processFindRelations(TbContext ctx, TbMsg msg, SearchDirectionIds sdId, String relationType) { + if (sdId.isOriginatorDirectionFrom()) { return ctx.getRelationService().findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), relationType, RelationTypeGroup.COMMON); } else { return ctx.getRelationService().findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), relationType, RelationTypeGroup.COMMON); @@ -127,85 +124,85 @@ public class TbCreateRelationNode extends TbAbstractRelationActionNode processCreateRelation(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) { + private ListenableFuture processCreateRelation(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId, String relationType) { switch (entityContainer.getEntityType()) { case ASSET: - return processAsset(ctx, entityContainer, sdId); + return processAsset(ctx, entityContainer, sdId, relationType); case DEVICE: - return processDevice(ctx, entityContainer, sdId); + return processDevice(ctx, entityContainer, sdId, relationType); case CUSTOMER: - return processCustomer(ctx, entityContainer, sdId); + return processCustomer(ctx, entityContainer, sdId, relationType); case DASHBOARD: - return processDashboard(ctx, entityContainer, sdId); + return processDashboard(ctx, entityContainer, sdId, relationType); case ENTITY_VIEW: - return processView(ctx, entityContainer, sdId); + return processView(ctx, entityContainer, sdId, relationType); case TENANT: - return processTenant(ctx, entityContainer, sdId); + return processTenant(ctx, entityContainer, sdId, relationType); } return Futures.immediateFuture(true); } - private ListenableFuture processView(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) { + private ListenableFuture processView(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId, String relationType) { return Futures.transformAsync(ctx.getEntityViewService().findEntityViewByIdAsync(ctx.getTenantId(), new EntityViewId(entityContainer.getEntityId().getId())), entityView -> { if (entityView != null) { - return processSave(ctx, sdId); + return processSave(ctx, sdId, relationType); } else { return Futures.immediateFuture(true); } }, ctx.getDbCallbackExecutor()); } - private ListenableFuture processDevice(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) { + private ListenableFuture processDevice(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId, String relationType) { return Futures.transformAsync(ctx.getDeviceService().findDeviceByIdAsync(ctx.getTenantId(), new DeviceId(entityContainer.getEntityId().getId())), device -> { if (device != null) { - return processSave(ctx, sdId); + return processSave(ctx, sdId, relationType); } else { return Futures.immediateFuture(true); } }, MoreExecutors.directExecutor()); } - private ListenableFuture processAsset(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) { + private ListenableFuture processAsset(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId, String relationType) { return Futures.transformAsync(ctx.getAssetService().findAssetByIdAsync(ctx.getTenantId(), new AssetId(entityContainer.getEntityId().getId())), asset -> { if (asset != null) { - return processSave(ctx, sdId); + return processSave(ctx, sdId, relationType); } else { return Futures.immediateFuture(true); } }, ctx.getDbCallbackExecutor()); } - private ListenableFuture processCustomer(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) { + private ListenableFuture processCustomer(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId, String relationType) { return Futures.transformAsync(ctx.getCustomerService().findCustomerByIdAsync(ctx.getTenantId(), new CustomerId(entityContainer.getEntityId().getId())), customer -> { if (customer != null) { - return processSave(ctx, sdId); + return processSave(ctx, sdId, relationType); } else { return Futures.immediateFuture(true); } }, ctx.getDbCallbackExecutor()); } - private ListenableFuture processDashboard(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) { + private ListenableFuture processDashboard(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId, String relationType) { return Futures.transformAsync(ctx.getDashboardService().findDashboardByIdAsync(ctx.getTenantId(), new DashboardId(entityContainer.getEntityId().getId())), dashboard -> { if (dashboard != null) { - return processSave(ctx, sdId); + return processSave(ctx, sdId, relationType); } else { return Futures.immediateFuture(true); } }, ctx.getDbCallbackExecutor()); } - private ListenableFuture processTenant(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) { + private ListenableFuture processTenant(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId, String relationType) { return Futures.transformAsync(ctx.getTenantService().findTenantByIdAsync(ctx.getTenantId(), new TenantId(entityContainer.getEntityId().getId())), tenant -> { if (tenant != null) { - return processSave(ctx, sdId); + return processSave(ctx, sdId, relationType); } else { return Futures.immediateFuture(true); } }, ctx.getDbCallbackExecutor()); } - private ListenableFuture processSave(TbContext ctx, SearchDirectionIds sdId) { + private ListenableFuture processSave(TbContext ctx, SearchDirectionIds sdId, String relationType) { return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), relationType, RelationTypeGroup.COMMON)); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java index 9af3708fcd..7758845329 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java @@ -48,8 +48,6 @@ import java.util.List; ) public class TbDeleteRelationNode extends TbAbstractRelationActionNode { - private String relationType; - @Override protected TbDeleteRelationNodeConfiguration loadEntityNodeActionConfig(TbNodeConfiguration configuration) throws TbNodeException { return TbNodeUtils.convert(configuration, TbDeleteRelationNodeConfiguration.class); @@ -61,19 +59,18 @@ public class TbDeleteRelationNode extends TbAbstractRelationActionNode processEntityRelationAction(TbContext ctx, TbMsg msg) { - return getRelationContainerListenableFuture(ctx, msg); + protected ListenableFuture processEntityRelationAction(TbContext ctx, TbMsg msg, String relationType) { + return getRelationContainerListenableFuture(ctx, msg, relationType); } @Override - protected ListenableFuture doProcessEntityRelationAction(TbContext ctx, TbMsg msg, EntityContainer entityContainer) { - return Futures.transform(processSingle(ctx, msg, entityContainer), result -> new RelationContainer(msg, result), MoreExecutors.directExecutor()); + protected ListenableFuture doProcessEntityRelationAction(TbContext ctx, TbMsg msg, EntityContainer entityContainer, String relationType) { + return Futures.transform(processSingle(ctx, msg, entityContainer, relationType), result -> new RelationContainer(msg, result), MoreExecutors.directExecutor()); } - private ListenableFuture getRelationContainerListenableFuture(TbContext ctx, TbMsg msg) { - relationType = processPattern(msg, config.getRelationType()); + private ListenableFuture getRelationContainerListenableFuture(TbContext ctx, TbMsg msg, String relationType) { if (config.isDeleteForSingleEntity()) { - return Futures.transformAsync(getEntity(ctx, msg), entityContainer -> doProcessEntityRelationAction(ctx, msg, entityContainer), MoreExecutors.directExecutor()); + return Futures.transformAsync(getEntity(ctx, msg), entityContainer -> doProcessEntityRelationAction(ctx, msg, entityContainer, relationType), MoreExecutors.directExecutor()); } else { return Futures.transform(processList(ctx, msg), result -> new RelationContainer(msg, result), MoreExecutors.directExecutor()); } @@ -100,18 +97,18 @@ public class TbDeleteRelationNode extends TbAbstractRelationActionNode processSingle(TbContext ctx, TbMsg msg, EntityContainer entityContainer) { + private ListenableFuture processSingle(TbContext ctx, TbMsg msg, EntityContainer entityContainer, String relationType) { SearchDirectionIds sdId = processSingleSearchDirection(msg, entityContainer); return Futures.transformAsync(ctx.getRelationService().checkRelation(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), relationType, RelationTypeGroup.COMMON), result -> { if (result) { - return processSingleDeleteRelation(ctx, sdId); + return processSingleDeleteRelation(ctx, sdId, relationType); } return Futures.immediateFuture(true); }, MoreExecutors.directExecutor()); } - private ListenableFuture processSingleDeleteRelation(TbContext ctx, SearchDirectionIds sdId) { + private ListenableFuture processSingleDeleteRelation(TbContext ctx, SearchDirectionIds sdId, String relationType) { return ctx.getRelationService().deleteRelationAsync(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), relationType, RelationTypeGroup.COMMON); } From d67ab523097052eb541b64214f0e51703ef5a7e4 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Wed, 6 May 2020 12:17:41 +0300 Subject: [PATCH 09/14] Changed direct executors to db executors --- .../engine/action/TbAbstractRelationActionNode.java | 3 +-- .../rule/engine/action/TbCreateRelationNode.java | 7 +++---- .../rule/engine/action/TbDeleteRelationNode.java | 13 ++++++------- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java index dd23c794f4..46e4e3e396 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java @@ -20,7 +20,6 @@ import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -88,7 +87,7 @@ public abstract class TbAbstractRelationActionNode processEntityRelationAction(TbContext ctx, TbMsg msg, String relationType) { - return Futures.transformAsync(getEntity(ctx, msg), entityContainer -> doProcessEntityRelationAction(ctx, msg, entityContainer, relationType), MoreExecutors.directExecutor()); + return Futures.transformAsync(getEntity(ctx, msg), entityContainer -> doProcessEntityRelationAction(ctx, msg, entityContainer, relationType), ctx.getDbCallbackExecutor()); } protected abstract boolean createEntityIfNotExists(); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java index 3c2e0bc405..ea4b801a4f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java @@ -17,7 +17,6 @@ package org.thingsboard.rule.engine.action; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; @@ -80,7 +79,7 @@ public class TbCreateRelationNode extends TbAbstractRelationActionNode createIfAbsent(TbContext ctx, TbMsg msg, EntityContainer entityContainer, String relationType) { @@ -118,7 +117,7 @@ public class TbCreateRelationNode extends TbAbstractRelationActionNode false, MoreExecutors.directExecutor()); + return Futures.transform(Futures.allAsList(list), result -> false, ctx.getDbCallbackExecutor()); } return Futures.immediateFuture(false); }, ctx.getDbCallbackExecutor()); @@ -159,7 +158,7 @@ public class TbCreateRelationNode extends TbAbstractRelationActionNode processAsset(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId, String relationType) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java index 7758845329..b27dde1140 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java @@ -17,7 +17,6 @@ package org.thingsboard.rule.engine.action; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; @@ -65,14 +64,14 @@ public class TbDeleteRelationNode extends TbAbstractRelationActionNode doProcessEntityRelationAction(TbContext ctx, TbMsg msg, EntityContainer entityContainer, String relationType) { - return Futures.transform(processSingle(ctx, msg, entityContainer, relationType), result -> new RelationContainer(msg, result), MoreExecutors.directExecutor()); + return Futures.transform(processSingle(ctx, msg, entityContainer, relationType), result -> new RelationContainer(msg, result), ctx.getDbCallbackExecutor()); } private ListenableFuture getRelationContainerListenableFuture(TbContext ctx, TbMsg msg, String relationType) { if (config.isDeleteForSingleEntity()) { - return Futures.transformAsync(getEntity(ctx, msg), entityContainer -> doProcessEntityRelationAction(ctx, msg, entityContainer, relationType), MoreExecutors.directExecutor()); + return Futures.transformAsync(getEntity(ctx, msg), entityContainer -> doProcessEntityRelationAction(ctx, msg, entityContainer, relationType), ctx.getDbCallbackExecutor()); } else { - return Futures.transform(processList(ctx, msg), result -> new RelationContainer(msg, result), MoreExecutors.directExecutor()); + return Futures.transform(processList(ctx, msg), result -> new RelationContainer(msg, result), ctx.getDbCallbackExecutor()); } } @@ -92,9 +91,9 @@ public class TbDeleteRelationNode extends TbAbstractRelationActionNode processSingle(TbContext ctx, TbMsg msg, EntityContainer entityContainer, String relationType) { @@ -105,7 +104,7 @@ public class TbDeleteRelationNode extends TbAbstractRelationActionNode processSingleDeleteRelation(TbContext ctx, SearchDirectionIds sdId, String relationType) { From 175a9c903db751f0c391faa4d3df8f1d4f935f8d Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 6 May 2020 12:29:26 +0300 Subject: [PATCH 10/14] OAuth: activate user with empty password - to be able to set up it later --- .../security/auth/oauth2/AbstractOAuth2ClientMapper.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java index 53acdd5e85..f620c34436 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Tenant; @@ -27,6 +28,7 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.dao.tenant.TenantService; @@ -47,6 +49,9 @@ public abstract class AbstractOAuth2ClientMapper { @Autowired private UserService userService; + @Autowired + private BCryptPasswordEncoder passwordEncoder; + @Autowired private TenantService tenantService; @@ -88,6 +93,8 @@ 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("")); } } catch (Exception e) { log.error("Can't get or create security user from oauth2 user", e); From ab27084e7ea85f17c173f8c2b2802d72ee800dbf Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 6 May 2020 15:43:08 +0300 Subject: [PATCH 11/14] Improve OAuth button style --- ui/src/app/login/login.tpl.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/app/login/login.tpl.html b/ui/src/app/login/login.tpl.html index e4f7f2559e..77308e464a 100644 --- a/ui/src/app/login/login.tpl.html +++ b/ui/src/app/login/login.tpl.html @@ -51,7 +51,7 @@
{{ "login.or" | translate | uppercase }}
- {{ 'login.login-with' | translate: {name: oauth2Client.name} }} From f29c9c9452bb310637c5f53e186f6dc63f69ae2b Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 6 May 2020 16:22:18 +0300 Subject: [PATCH 12/14] Improve OAuth button style --- ui/src/app/login/login.tpl.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/app/login/login.tpl.html b/ui/src/app/login/login.tpl.html index 77308e464a..f12a207856 100644 --- a/ui/src/app/login/login.tpl.html +++ b/ui/src/app/login/login.tpl.html @@ -51,7 +51,7 @@
{{ "login.or" | translate | uppercase }}
- {{ 'login.login-with' | translate: {name: oauth2Client.name} }} From 9b18c408b2e2104d55df8d0c36ec9b68b2870641 Mon Sep 17 00:00:00 2001 From: VoBa Date: Wed, 6 May 2020 19:20:18 +0300 Subject: [PATCH 13/14] Added activate user config. Fixed https issue (#2737) * Added activate user config. Fixed https issue * Update OAuth2User.java Co-authored-by: Igor Kulikov --- .../ThingsboardSecurityConfiguration.java | 4 +-- .../server/controller/AuthController.java | 7 ++-- .../server/controller/BaseController.java | 33 ------------------ .../server/controller/UserController.java | 7 ++-- .../oauth2/AbstractOAuth2ClientMapper.java | 8 +++-- .../auth/oauth2/BasicOAuth2ClientMapper.java | 3 +- .../auth/oauth2/CustomOAuth2ClientMapper.java | 2 +- .../Oauth2AuthenticationSuccessHandler.java | 4 ++- .../thingsboard/server/utils/MiscUtils.java | 34 ++++++++++++++++++- .../src/main/resources/thingsboard.yml | 6 ++-- .../dao/oauth2/OAuth2ClientMapperConfig.java | 3 +- 11 files changed, 60 insertions(+), 51 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java index 9e967158b4..8a21fd229f 100644 --- a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java @@ -204,11 +204,11 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt http.oauth2Login() .loginPage("/oauth2Login") .loginProcessingUrl(oauth2Configuration.getLoginProcessingUrl()) - .successHandler(oauth2AuthenticationSuccessHandler); + .successHandler(oauth2AuthenticationSuccessHandler) + .failureHandler(failureHandler); } } - @Bean @ConditionalOnMissingBean(CorsFilter.class) public CorsFilter corsFilter(@Autowired MvcCorsProperties mvcCorsProperties) { diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java index 49cfe482df..9f6c6f3b44 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java +++ b/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); diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 8385e61a20..a8d6098c4a 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -521,39 +521,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 emptyId(EntityType entityType) { return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID); } diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java index a37d9c29b6..bf64ad7d6f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/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; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java index f620c34436..dd49418204 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java +++ b/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); diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java index 1d44a772aa..c6b6aeaae3 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java +++ b/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 attributes, OAuth2ClientMapperConfig config) { diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java index 832a1cd39b..0fb4563987 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java +++ b/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) { diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index be8f7ca7c2..8702661196 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java +++ b/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()); } } \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java b/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java index bec28a3bdb..ed13ca603d 100644 --- a/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java +++ b/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; + } } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 8010987a43..aa1d03d92c 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/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:}" diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientMapperConfig.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientMapperConfig.java index 695bb0aa0e..47f3746980 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientMapperConfig.java +++ b/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; From d9bfd829260df7e313e92472e83a4f87e95d93b9 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 6 May 2020 20:04:09 +0300 Subject: [PATCH 14/14] Introduce OAuth failure handling --- .../ThingsboardSecurityConfiguration.java | 11 ++++- .../Oauth2AuthenticationFailureHandler.java | 43 +++++++++++++++++++ ...RestAwareAuthenticationFailureHandler.java | 2 +- ui/src/app/api/user.service.js | 17 +++++++- ui/src/app/locale/locale.constant-en_US.json | 3 +- 5 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java diff --git a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java index 8a21fd229f..683345fe7f 100644 --- a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java +++ b/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; @@ -205,7 +212,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt .loginPage("/oauth2Login") .loginProcessingUrl(oauth2Configuration.getLoginProcessingUrl()) .successHandler(oauth2AuthenticationSuccessHandler) - .failureHandler(failureHandler); + .failureHandler(oauth2AuthenticationFailureHandler); } } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java new file mode 100644 index 0000000000..653be85f72 --- /dev/null +++ b/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())); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationFailureHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationFailureHandler.java index 726ee76a2d..38486e9a48 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationFailureHandler.java +++ b/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; diff --git a/ui/src/app/api/user.service.js b/ui/src/app/api/user.service.js index 9ac8270403..b84bcf8428 100644 --- a/ui/src/app/api/user.service.js +++ b/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') { diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index 6693875edf..5a8a588446 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/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",