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); } 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:}" 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;