Browse Source

Merge branch 'vzikratyi-tb-feature/dynamic-oauth2-new-table'

pull/3576/head
Igor Kulikov 6 years ago
parent
commit
628ce7991d
  1. 20
      application/src/main/data/json/system/oauth2_config_templates/facebook_config.json
  2. 17
      application/src/main/data/json/system/oauth2_config_templates/github_config.json
  3. 21
      application/src/main/data/json/system/oauth2_config_templates/google_config.json
  4. 69
      application/src/main/data/upgrade/3.1.1/schema_update_before.sql
  5. 267
      application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java
  6. 8
      application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
  7. 15
      application/src/main/java/org/thingsboard/server/controller/AuthController.java
  8. 26
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  9. 77
      application/src/main/java/org/thingsboard/server/controller/OAuth2ConfigTemplateController.java
  10. 73
      application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java
  11. 2
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  12. 5
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  13. 24
      application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
  14. 2
      application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java
  15. 3
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java
  16. 78
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicMapperUtils.java
  17. 60
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java
  18. 14
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java
  19. 91
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java
  20. 4
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java
  21. 17
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperProvider.java
  22. 28
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java
  23. 2
      application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java
  24. 6
      application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java
  25. 3
      application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java
  26. 24
      application/src/main/java/org/thingsboard/server/utils/MiscUtils.java
  27. 56
      application/src/main/resources/thingsboard.yml
  28. 31
      common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateService.java
  29. 12
      common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java
  30. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsService.java
  31. 10
      common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationId.java
  32. 33
      common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationInfoId.java
  33. 33
      common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationTemplateId.java
  34. 35
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java
  35. 29
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/DomainInfo.java
  36. 39
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ExtendedOAuth2ClientRegistrationInfo.java
  37. 20
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/MapperType.java
  38. 33
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2BasicMapperConfig.java
  39. 19
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientInfo.java
  40. 42
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java
  41. 76
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationInfo.java
  42. 78
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java
  43. 32
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java
  44. 30
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java
  45. 29
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2CustomMapperConfig.java
  46. 33
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java
  47. 20
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/SchemeType.java
  48. 20
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/TenantNameStrategyType.java
  49. 47
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  50. 231
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractOAuth2ClientRegistrationInfoEntity.java
  51. 48
      dao/src/main/java/org/thingsboard/server/dao/model/sql/ExtendedOAuth2ClientRegistrationInfoEntity.java
  52. 75
      dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java
  53. 51
      dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationInfoEntity.java
  54. 160
      dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationTemplateEntity.java
  55. 59
      dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java
  56. 47
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientMapperConfig.java
  57. 23
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java
  58. 34
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationInfoDao.java
  59. 25
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationTemplateDao.java
  60. 103
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java
  61. 53
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Configuration.java
  62. 203
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java
  63. 102
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java
  64. 48
      dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java
  65. 76
      dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationInfoDao.java
  66. 53
      dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationTemplateDao.java
  67. 41
      dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationInfoRepository.java
  68. 24
      dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationRepository.java
  69. 24
      dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationTemplateRepository.java
  70. 70
      dao/src/main/resources/sql/schema-entities-hsql.sql
  71. 69
      dao/src/main/resources/sql/schema-entities.sql
  72. 133
      dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java
  73. 524
      dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java
  74. 23
      dao/src/test/java/org/thingsboard/server/dao/service/sql/OAuth2ConfigTemplateServiceSqlTest.java
  75. 23
      dao/src/test/java/org/thingsboard/server/dao/service/sql/OAuth2ServiceSqlTest.java
  76. 3
      dao/src/test/resources/sql/hsql/drop-all-tables.sql
  77. 3
      dao/src/test/resources/sql/psql/drop-all-tables.sql
  78. 3
      dao/src/test/resources/sql/timescale/drop-all-tables.sql
  79. 2
      ui-ngx/src/app/core/auth/auth.service.ts
  80. 22
      ui-ngx/src/app/core/http/admin.service.ts
  81. 9
      ui-ngx/src/app/core/services/menu.service.ts
  82. 14
      ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts
  83. 4
      ui-ngx/src/app/modules/home/pages/admin/admin.module.ts
  84. 478
      ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html
  85. 66
      ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss
  86. 500
      ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts
  87. 3
      ui-ngx/src/app/shared/models/constants.ts
  88. 99
      ui-ngx/src/app/shared/models/settings.models.ts
  89. 70
      ui-ngx/src/assets/locale/locale.constant-en_US.json

20
application/src/main/data/json/system/oauth2_config_templates/facebook_config.json

@ -0,0 +1,20 @@
{
"providerId": "Facebook",
"accessTokenUri": "https://graph.facebook.com/v2.8/oauth/access_token",
"authorizationUri": "https://www.facebook.com/v2.8/dialog/oauth",
"scope": ["email","public_profile"],
"jwkSetUri": null,
"userInfoUri": "https://graph.facebook.com/me?fields=id,name,first_name,last_name,email",
"clientAuthenticationMethod": "BASIC",
"userNameAttributeName": "email",
"basic": {
"emailAttributeKey": "email",
"firstNameAttributeKey": "first_name",
"lastNameAttributeKey": "last_name",
"tenantNameStrategy": "DOMAIN"
},
"comment": null,
"loginButtonIcon": "mdi:facebook",
"loginButtonLabel": "Facebook",
"helpLink": "https://developers.facebook.com/docs/facebook-login/web#logindialog"
}

17
application/src/main/data/json/system/oauth2_config_templates/github_config.json

@ -0,0 +1,17 @@
{
"providerId": "Github",
"accessTokenUri": "https://github.com/login/oauth/access_token",
"authorizationUri": "https://github.com/login/oauth/authorize",
"scope": ["read:user","user:email"],
"jwkSetUri": null,
"userInfoUri": "https://api.github.com/user",
"clientAuthenticationMethod": "BASIC",
"userNameAttributeName": "login",
"basic": {
"tenantNameStrategy": "DOMAIN"
},
"comment": "In order to log into ThingsBoard you need to have user's email. You may configure and use Custom OAuth2 Mapper to get email information. Please refer to <a href=\"https://docs.github.com/en/rest/reference/users#list-email-addresses-for-the-authenticated-user\">Github Documentation</a>",
"loginButtonIcon": "mdi:github",
"loginButtonLabel": "Github",
"helpLink": "https://docs.github.com/en/developers/apps/creating-an-oauth-app"
}

21
application/src/main/data/json/system/oauth2_config_templates/google_config.json

@ -0,0 +1,21 @@
{
"providerId": "Google",
"additionalInfo": null,
"accessTokenUri": "https://oauth2.googleapis.com/token",
"authorizationUri": "https://accounts.google.com/o/oauth2/v2/auth",
"scope": ["email","openid","profile"],
"jwkSetUri": "https://www.googleapis.com/oauth2/v3/certs",
"userInfoUri": "https://openidconnect.googleapis.com/v1/userinfo",
"clientAuthenticationMethod": "BASIC",
"userNameAttributeName": "email",
"basic": {
"emailAttributeKey": "email",
"firstNameAttributeKey": "given_name",
"lastNameAttributeKey": "family_name",
"tenantNameStrategy": "DOMAIN"
},
"comment": null,
"loginButtonIcon": "mdi:google",
"loginButtonLabel": "Google",
"helpLink": "https://developers.google.com/adwords/api/docs/guides/authentication"
}

69
application/src/main/data/upgrade/3.1.1/schema_update_before.sql

@ -14,6 +14,75 @@
-- limitations under the License.
--
CREATE TABLE IF NOT EXISTS oauth2_client_registration_info (
id uuid NOT NULL CONSTRAINT oauth2_client_registration_info_pkey PRIMARY KEY,
enabled boolean,
created_time bigint NOT NULL,
additional_info varchar,
client_id varchar(255),
client_secret varchar(255),
authorization_uri varchar(255),
token_uri varchar(255),
scope varchar(255),
user_info_uri varchar(255),
user_name_attribute_name varchar(255),
jwk_set_uri varchar(255),
client_authentication_method varchar(255),
login_button_label varchar(255),
login_button_icon varchar(255),
allow_user_creation boolean,
activate_user boolean,
type varchar(31),
basic_email_attribute_key varchar(31),
basic_first_name_attribute_key varchar(31),
basic_last_name_attribute_key varchar(31),
basic_tenant_name_strategy varchar(31),
basic_tenant_name_pattern varchar(255),
basic_customer_name_pattern varchar(255),
basic_default_dashboard_name varchar(255),
basic_always_full_screen boolean,
custom_url varchar(255),
custom_username varchar(255),
custom_password varchar(255),
custom_send_token boolean
);
CREATE TABLE IF NOT EXISTS oauth2_client_registration (
id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY,
created_time bigint NOT NULL,
domain_name varchar(255),
domain_scheme varchar(31),
client_registration_info_id uuid
);
CREATE TABLE IF NOT EXISTS oauth2_client_registration_template (
id uuid NOT NULL CONSTRAINT oauth2_client_registration_template_pkey PRIMARY KEY,
created_time bigint NOT NULL,
additional_info varchar,
provider_id varchar(255),
authorization_uri varchar(255),
token_uri varchar(255),
scope varchar(255),
user_info_uri varchar(255),
user_name_attribute_name varchar(255),
jwk_set_uri varchar(255),
client_authentication_method varchar(255),
type varchar(31),
basic_email_attribute_key varchar(31),
basic_first_name_attribute_key varchar(31),
basic_last_name_attribute_key varchar(31),
basic_tenant_name_strategy varchar(31),
basic_tenant_name_pattern varchar(255),
basic_customer_name_pattern varchar(255),
basic_default_dashboard_name varchar(255),
basic_always_full_screen boolean,
comment varchar,
login_button_icon varchar(255),
login_button_label varchar(255),
help_link varchar(255),
CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id)
);
CREATE TABLE IF NOT EXISTS device_profile (
id uuid NOT NULL CONSTRAINT device_profile_pkey PRIMARY KEY,
created_time bigint NOT NULL,

267
application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java

@ -0,0 +1,267 @@
/**
* 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.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
import org.springframework.security.crypto.keygen.StringKeyGenerator;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import org.thingsboard.server.dao.oauth2.OAuth2Configuration;
import org.thingsboard.server.utils.MiscUtils;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
@Service
@Slf4j
public class CustomOAuth2AuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver {
public static final String DEFAULT_AUTHORIZATION_REQUEST_BASE_URI = "/oauth2/authorization";
public static final String DEFAULT_LOGIN_PROCESSING_URI = "/login/oauth2/code/";
private static final String REGISTRATION_ID_URI_VARIABLE_NAME = "registrationId";
private static final char PATH_DELIMITER = '/';
private final AntPathRequestMatcher authorizationRequestMatcher = new AntPathRequestMatcher(
DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/{" + REGISTRATION_ID_URI_VARIABLE_NAME + "}");
private final StringKeyGenerator stateGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder());
private final StringKeyGenerator secureKeyGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
@Autowired
private ClientRegistrationRepository clientRegistrationRepository;
@Autowired(required = false)
private OAuth2Configuration oauth2Configuration;
@Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
String registrationId = this.resolveRegistrationId(request);
String redirectUriAction = getAction(request, "login");
return resolve(request, registrationId, redirectUriAction);
}
@Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId) {
if (registrationId == null) {
return null;
}
String redirectUriAction = getAction(request, "authorize");
return resolve(request, registrationId, redirectUriAction);
}
private String getAction(HttpServletRequest request, String defaultAction) {
String action = request.getParameter("action");
if (action == null) {
return defaultAction;
}
return action;
}
private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId, String redirectUriAction) {
if (registrationId == null) {
return null;
}
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
if (clientRegistration == null) {
throw new IllegalArgumentException("Invalid Client Registration with Id: " + registrationId);
}
Map<String, Object> attributes = new HashMap<>();
attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId());
OAuth2AuthorizationRequest.Builder builder;
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
builder = OAuth2AuthorizationRequest.authorizationCode();
Map<String, Object> additionalParameters = new HashMap<>();
if (!CollectionUtils.isEmpty(clientRegistration.getScopes()) &&
clientRegistration.getScopes().contains(OidcScopes.OPENID)) {
// Section 3.1.2.1 Authentication Request - https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
// scope
// REQUIRED. OpenID Connect requests MUST contain the "openid" scope value.
addNonceParameters(attributes, additionalParameters);
}
if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())) {
addPkceParameters(attributes, additionalParameters);
}
builder.additionalParameters(additionalParameters);
} else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) {
builder = OAuth2AuthorizationRequest.implicit();
} else {
throw new IllegalArgumentException("Invalid Authorization Grant Type (" +
clientRegistration.getAuthorizationGrantType().getValue() +
") for Client Registration with Id: " + clientRegistration.getRegistrationId());
}
String redirectUriStr = expandRedirectUri(request, clientRegistration, redirectUriAction);
return builder
.clientId(clientRegistration.getClientId())
.authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
.redirectUri(redirectUriStr)
.scopes(clientRegistration.getScopes())
.state(this.stateGenerator.generateKey())
.attributes(attributes)
.build();
}
private String resolveRegistrationId(HttpServletRequest request) {
if (this.authorizationRequestMatcher.matches(request)) {
return this.authorizationRequestMatcher
.matcher(request).getVariables().get(REGISTRATION_ID_URI_VARIABLE_NAME);
}
return null;
}
/**
* Expands the {@link ClientRegistration#getRedirectUriTemplate()} with following provided variables:<br/>
* - baseUrl (e.g. https://localhost/app) <br/>
* - baseScheme (e.g. https) <br/>
* - baseHost (e.g. localhost) <br/>
* - basePort (e.g. :8080) <br/>
* - basePath (e.g. /app) <br/>
* - registrationId (e.g. google) <br/>
* - action (e.g. login) <br/>
* <p/>
* Null variables are provided as empty strings.
* <p/>
* Default redirectUriTemplate is: {@link org.springframework.security.config.oauth2.client}.CommonOAuth2Provider#DEFAULT_REDIRECT_URL
*
* @return expanded URI
*/
private String expandRedirectUri(HttpServletRequest request, ClientRegistration clientRegistration, String action) {
Map<String, String> uriVariables = new HashMap<>();
uriVariables.put("registrationId", clientRegistration.getRegistrationId());
UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
.replacePath(request.getContextPath())
.replaceQuery(null)
.fragment(null)
.build();
String scheme = uriComponents.getScheme();
uriVariables.put("baseScheme", scheme == null ? "" : scheme);
String host = uriComponents.getHost();
uriVariables.put("baseHost", host == null ? "" : host);
// following logic is based on HierarchicalUriComponents#toUriString()
int port = uriComponents.getPort();
uriVariables.put("basePort", port == -1 ? "" : ":" + port);
String path = uriComponents.getPath();
if (StringUtils.hasLength(path)) {
if (path.charAt(0) != PATH_DELIMITER) {
path = PATH_DELIMITER + path;
}
}
uriVariables.put("basePath", path == null ? "" : path);
uriVariables.put("baseUrl", uriComponents.toUriString());
uriVariables.put("action", action == null ? "" : action);
String redirectUri = getRedirectUri(request);
log.trace("Redirect URI - {}.", redirectUri);
return UriComponentsBuilder.fromUriString(redirectUri)
.buildAndExpand(uriVariables)
.toUriString();
}
private String getRedirectUri(HttpServletRequest request) {
String loginProcessingUri = oauth2Configuration != null ? oauth2Configuration.getLoginProcessingUrl() : DEFAULT_LOGIN_PROCESSING_URI;
String scheme = MiscUtils.getScheme(request);
String domainName = MiscUtils.getDomainName(request);
int port = MiscUtils.getPort(request);
String baseUrl = scheme + "://" + domainName;
if (needsPort(scheme, port)){
baseUrl += ":" + port;
}
return baseUrl + loginProcessingUri;
}
private boolean needsPort(String scheme, int port) {
boolean isHttpDefault = "http".equals(scheme.toLowerCase()) && port == 80;
boolean isHttpsDefault = "https".equals(scheme.toLowerCase()) && port == 443;
return !isHttpDefault && !isHttpsDefault;
}
/**
* Creates nonce and its hash for use in OpenID Connect 1.0 Authentication Requests.
*
* @param attributes where the {@link OidcParameterNames#NONCE} is stored for the authentication request
* @param additionalParameters where the {@link OidcParameterNames#NONCE} hash is added for the authentication request
*
* @since 5.2
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest">3.1.2.1. Authentication Request</a>
*/
private void addNonceParameters(Map<String, Object> attributes, Map<String, Object> additionalParameters) {
try {
String nonce = this.secureKeyGenerator.generateKey();
String nonceHash = createHash(nonce);
attributes.put(OidcParameterNames.NONCE, nonce);
additionalParameters.put(OidcParameterNames.NONCE, nonceHash);
} catch (NoSuchAlgorithmException e) { }
}
/**
* Creates and adds additional PKCE parameters for use in the OAuth 2.0 Authorization and Access Token Requests
*
* @param attributes where {@link PkceParameterNames#CODE_VERIFIER} is stored for the token request
* @param additionalParameters where {@link PkceParameterNames#CODE_CHALLENGE} and, usually,
* {@link PkceParameterNames#CODE_CHALLENGE_METHOD} are added to be used in the authorization request.
*
* @since 5.2
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-1.1">1.1. Protocol Flow</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-4.1">4.1. Client Creates a Code Verifier</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-4.2">4.2. Client Creates the Code Challenge</a>
*/
private void addPkceParameters(Map<String, Object> attributes, Map<String, Object> additionalParameters) {
String codeVerifier = this.secureKeyGenerator.generateKey();
attributes.put(PkceParameterNames.CODE_VERIFIER, codeVerifier);
try {
String codeChallenge = createHash(codeVerifier);
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, codeChallenge);
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256");
} catch (NoSuchAlgorithmException e) {
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, codeVerifier);
}
}
private static String createHash(String value) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] digest = md.digest(value.getBytes(StandardCharsets.US_ASCII));
return Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
}
}

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

@ -32,6 +32,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@ -175,6 +176,9 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
web.ignoring().antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**");
}
@Autowired
private OAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().cacheControl().and().frameOptions().disable()
@ -207,8 +211,10 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
.addFilterBefore(buildRefreshTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildWsJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(rateLimitProcessingFilter, UsernamePasswordAuthenticationFilter.class);
if (oauth2Configuration != null && oauth2Configuration.isEnabled()) {
if (oauth2Configuration != null) {
http.oauth2Login()
.authorizationEndpoint().authorizationRequestResolver(oAuth2AuthorizationRequestResolver)
.and()
.loginPage("/oauth2Login")
.loginProcessingUrl(oauth2Configuration.getLoginProcessingUrl())
.successHandler(oauth2AuthenticationSuccessHandler)

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

@ -38,10 +38,8 @@ import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.oauth2.OAuth2Service;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails;
@ -84,9 +82,6 @@ public class AuthController extends BaseController {
@Autowired
private AuditLogService auditLogService;
@Autowired
private OAuth2Service oauth2Service;
@PreAuthorize("isAuthenticated()")
@RequestMapping(value = "/auth/user", method = RequestMethod.GET)
public @ResponseBody User getUser() throws ThingsboardException {
@ -336,14 +331,4 @@ public class AuthController extends BaseController {
throw handleException(e);
}
}
@RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST)
@ResponseBody
public List<OAuth2ClientInfo> getOAuth2Clients() throws ThingsboardException {
try {
return oauth2Service.getOAuth2Clients();
} catch (Exception e) {
throw handleException(e);
}
}
}

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

@ -27,22 +27,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceInfo;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.EntityViewInfo;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantInfo;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.asset.Asset;
@ -50,6 +35,7 @@ import org.thingsboard.server.common.data.asset.AssetInfo;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.*;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
@ -93,6 +79,8 @@ import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
import org.thingsboard.server.dao.oauth2.OAuth2Service;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TenantProfileService;
@ -175,6 +163,12 @@ public abstract class BaseController {
@Autowired
protected DashboardService dashboardService;
@Autowired
protected OAuth2Service oAuth2Service;
@Autowired
protected OAuth2ConfigTemplateService oAuth2ConfigTemplateService;
@Autowired
protected ComponentDiscoveryService componentDescriptorService;

77
application/src/main/java/org/thingsboard/server/controller/OAuth2ConfigTemplateController.java

@ -0,0 +1,77 @@
/**
* 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.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import java.util.List;
@RestController
@TbCoreComponent
@RequestMapping("/api/oauth2/config/template")
@Slf4j
public class OAuth2ConfigTemplateController extends BaseController {
private static final String CLIENT_REGISTRATION_TEMPLATE_ID = "clientRegistrationTemplateId";
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@RequestMapping(method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
public OAuth2ClientRegistrationTemplate saveClientRegistrationTemplate(@RequestBody OAuth2ClientRegistrationTemplate clientRegistrationTemplate) throws ThingsboardException {
try {
accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_TEMPLATE, Operation.WRITE);
return oAuth2ConfigTemplateService.saveClientRegistrationTemplate(clientRegistrationTemplate);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@RequestMapping(value = "/{clientRegistrationTemplateId}", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK)
public void deleteClientRegistrationTemplate(@PathVariable(CLIENT_REGISTRATION_TEMPLATE_ID) String strClientRegistrationTemplateId) throws ThingsboardException {
checkParameter(CLIENT_REGISTRATION_TEMPLATE_ID, strClientRegistrationTemplateId);
try {
accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_TEMPLATE, Operation.DELETE);
OAuth2ClientRegistrationTemplateId clientRegistrationTemplateId = new OAuth2ClientRegistrationTemplateId(toUUID(strClientRegistrationTemplateId));
oAuth2ConfigTemplateService.deleteClientRegistrationTemplateById(clientRegistrationTemplateId);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(method = RequestMethod.GET, produces = "application/json")
@ResponseBody
public List<OAuth2ClientRegistrationTemplate> getClientRegistrationTemplates() throws ThingsboardException {
try {
accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_TEMPLATE, Operation.READ);
return oAuth2ConfigTemplateService.findAllClientRegistrationTemplates();
} catch (Exception e) {
throw handleException(e);
}
}
}

73
application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java

@ -0,0 +1,73 @@
/**
* 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.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams;
import org.thingsboard.server.common.data.oauth2.SchemeType;
import org.thingsboard.server.queue.util.TbCoreComponent;
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;
import java.util.List;
@RestController
@TbCoreComponent
@RequestMapping("/api")
@Slf4j
public class OAuth2Controller extends BaseController {
@RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST)
@ResponseBody
public List<OAuth2ClientInfo> getOAuth2Clients(HttpServletRequest request) throws ThingsboardException {
try {
return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainName(request));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@RequestMapping(value = "/oauth2/config", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
public OAuth2ClientsParams getCurrentOAuth2Params() throws ThingsboardException {
try {
accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.READ);
return oAuth2Service.findOAuth2Params();
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@RequestMapping(value = "/oauth2/config", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
public OAuth2ClientsParams saveOAuth2Params(@RequestBody OAuth2ClientsParams oauth2Params) throws ThingsboardException {
try {
accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.WRITE);
oAuth2Service.saveOAuth2Params(oauth2Params);
return oAuth2Service.findOAuth2Params();
} catch (Exception e) {
throw handleException(e);
}
}
}

2
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -184,6 +184,7 @@ public class ThingsboardInstallService {
dataUpdateService.updateData("3.1.1");
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
systemDataLoaderService.createOAuth2Templates();
break;
default:
throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion);
@ -216,6 +217,7 @@ public class ThingsboardInstallService {
systemDataLoaderService.createDefaultTenantProfiles();
systemDataLoaderService.createAdminSettings();
systemDataLoaderService.loadSystemWidgets();
systemDataLoaderService.createOAuth2Templates();
// systemDataLoaderService.loadSystemPlugins();
// systemDataLoaderService.loadSystemRules();

5
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java

@ -193,6 +193,11 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, mailSettings);
}
@Override
public void createOAuth2Templates() throws Exception {
installScripts.createOAuth2Templates();
}
@Override
public void loadDemoData() throws Exception {
Tenant demoTenant = new Tenant();

24
application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java

@ -26,11 +26,13 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
@ -61,6 +63,7 @@ public class InstallScripts {
public static final String DEMO_DIR = "demo";
public static final String RULE_CHAINS_DIR = "rule_chains";
public static final String WIDGET_BUNDLES_DIR = "widget_bundles";
public static final String OAUTH2_CONFIG_TEMPLATES_DIR = "oauth2_config_templates";
public static final String DASHBOARDS_DIR = "dashboards";
public static final String JSON_EXT = ".json";
@ -80,6 +83,9 @@ public class InstallScripts {
@Autowired
private WidgetsBundleService widgetsBundleService;
@Autowired
private OAuth2ConfigTemplateService oAuth2TemplateService;
public Path getTenantRuleChainsDir() {
return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR);
}
@ -228,4 +234,22 @@ public class InstallScripts {
throw new RuntimeException("Unable to load dashboard from json", e);
}
}
public void createOAuth2Templates() throws Exception {
Path oauth2ConfigTemplatesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, OAUTH2_CONFIG_TEMPLATES_DIR);
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(oauth2ConfigTemplatesDir, path -> path.toString().endsWith(JSON_EXT))) {
dirStream.forEach(
path -> {
try {
JsonNode oauth2ConfigTemplateJson = objectMapper.readTree(path.toFile());
OAuth2ClientRegistrationTemplate clientRegistrationTemplate = objectMapper.treeToValue(oauth2ConfigTemplateJson, OAuth2ClientRegistrationTemplate.class);
oAuth2TemplateService.saveClientRegistrationTemplate(clientRegistrationTemplate);
} catch (Exception e) {
log.error("Unable to load oauth2 config templates from json: [{}]", path.toString());
throw new RuntimeException("Unable to load oauth2 config templates from json", e);
}
}
);
}
}
}

2
application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java

@ -23,6 +23,8 @@ public interface SystemDataLoaderService {
void createAdminSettings() throws Exception;
void createOAuth2Templates() throws Exception;
void loadSystemWidgets() throws Exception;
void updateSystemWidgets() throws Exception;

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

@ -17,7 +17,6 @@ package org.thingsboard.server.service.security.auth.oauth2;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Strings;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@ -34,7 +33,6 @@ import org.thingsboard.server.common.data.id.IdBased;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.dao.customer.CustomerService;
@ -49,7 +47,6 @@ import org.thingsboard.server.service.security.model.UserPrincipal;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

78
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicMapperUtils.java

@ -0,0 +1,78 @@
/**
* 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 lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.text.StrSubstitutor;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
import org.thingsboard.server.dao.oauth2.OAuth2User;
import java.util.Map;
@Slf4j
public class BasicMapperUtils {
private static final String START_PLACEHOLDER_PREFIX = "%{";
private static final String END_PLACEHOLDER_PREFIX = "}";
public static OAuth2User getOAuth2User(String email, Map<String, Object> attributes, OAuth2MapperConfig config) {
OAuth2User oauth2User = new OAuth2User();
oauth2User.setEmail(email);
oauth2User.setTenantName(getTenantName(email, attributes, config));
if (!StringUtils.isEmpty(config.getBasic().getLastNameAttributeKey())) {
String lastName = getStringAttributeByKey(attributes, config.getBasic().getLastNameAttributeKey());
oauth2User.setLastName(lastName);
}
if (!StringUtils.isEmpty(config.getBasic().getFirstNameAttributeKey())) {
String firstName = getStringAttributeByKey(attributes, config.getBasic().getFirstNameAttributeKey());
oauth2User.setFirstName(firstName);
}
if (!StringUtils.isEmpty(config.getBasic().getCustomerNamePattern())) {
StrSubstitutor sub = new StrSubstitutor(attributes, START_PLACEHOLDER_PREFIX, END_PLACEHOLDER_PREFIX);
String customerName = sub.replace(config.getBasic().getCustomerNamePattern());
oauth2User.setCustomerName(customerName);
}
oauth2User.setAlwaysFullScreen(config.getBasic().isAlwaysFullScreen());
if (!StringUtils.isEmpty(config.getBasic().getDefaultDashboardName())) {
oauth2User.setDefaultDashboardName(config.getBasic().getDefaultDashboardName());
}
return oauth2User;
}
public static String getTenantName(String email, Map<String, Object> attributes, OAuth2MapperConfig config) {
switch (config.getBasic().getTenantNameStrategy()) {
case EMAIL:
return email;
case DOMAIN:
return email.substring(email .indexOf("@") + 1);
case CUSTOM:
StrSubstitutor sub = new StrSubstitutor(attributes, START_PLACEHOLDER_PREFIX, END_PLACEHOLDER_PREFIX);
return sub.replace(config.getBasic().getTenantNamePattern());
default:
throw new RuntimeException("Tenant Name Strategy with type " + config.getBasic().getTenantNameStrategy() + " is not supported!");
}
}
public static String getStringAttributeByKey(Map<String, Object> attributes, String key) {
String result = null;
try {
result = (String) attributes.get(key);
} catch (Exception e) {
log.warn("Can't convert attribute to String by key " + key);
}
return result;
}
}

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

@ -16,11 +16,9 @@
package org.thingsboard.server.service.security.auth.oauth2;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.text.StrSubstitutor;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.server.dao.oauth2.OAuth2ClientMapperConfig;
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
import org.thingsboard.server.dao.oauth2.OAuth2User;
import org.thingsboard.server.service.security.model.SecurityUser;
@ -30,62 +28,12 @@ import java.util.Map;
@Slf4j
public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper {
private static final String START_PLACEHOLDER_PREFIX = "%{";
private static final String END_PLACEHOLDER_PREFIX = "}";
private static final String EMAIL_TENANT_STRATEGY = "email";
private static final String DOMAIN_TENANT_STRATEGY = "domain";
private static final String CUSTOM_TENANT_STRATEGY = "custom";
@Override
public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, OAuth2ClientMapperConfig config) {
OAuth2User oauth2User = new OAuth2User();
public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config) {
Map<String, Object> attributes = token.getPrincipal().getAttributes();
String email = getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey());
oauth2User.setEmail(email);
oauth2User.setTenantName(getTenantName(attributes, config));
if (!StringUtils.isEmpty(config.getBasic().getLastNameAttributeKey())) {
String lastName = getStringAttributeByKey(attributes, config.getBasic().getLastNameAttributeKey());
oauth2User.setLastName(lastName);
}
if (!StringUtils.isEmpty(config.getBasic().getFirstNameAttributeKey())) {
String firstName = getStringAttributeByKey(attributes, config.getBasic().getFirstNameAttributeKey());
oauth2User.setFirstName(firstName);
}
if (!StringUtils.isEmpty(config.getBasic().getCustomerNamePattern())) {
StrSubstitutor sub = new StrSubstitutor(attributes, START_PLACEHOLDER_PREFIX, END_PLACEHOLDER_PREFIX);
String customerName = sub.replace(config.getBasic().getCustomerNamePattern());
oauth2User.setCustomerName(customerName);
}
oauth2User.setAlwaysFullScreen(config.getBasic().isAlwaysFullScreen());
if (!StringUtils.isEmpty(config.getBasic().getDefaultDashboardName())) {
oauth2User.setDefaultDashboardName(config.getBasic().getDefaultDashboardName());
}
String email = BasicMapperUtils.getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey());
OAuth2User oauth2User = BasicMapperUtils.getOAuth2User(email, attributes, config);
return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser());
}
private String getTenantName(Map<String, Object> attributes, OAuth2ClientMapperConfig config) {
switch (config.getBasic().getTenantNameStrategy()) {
case EMAIL_TENANT_STRATEGY:
return getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey());
case DOMAIN_TENANT_STRATEGY:
String email = getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey());
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().getTenantNamePattern());
default:
throw new RuntimeException("Tenant Name Strategy with type " + config.getBasic().getTenantNameStrategy() + " is not supported!");
}
}
private String getStringAttributeByKey(Map<String, Object> attributes, String key) {
String result = null;
try {
result = (String) attributes.get(key);
} catch (Exception e) {
log.warn("Can't convert attribute to String by key " + key);
}
return result;
}
}

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

@ -23,28 +23,34 @@ import org.springframework.security.oauth2.client.authentication.OAuth2Authentic
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.thingsboard.server.dao.oauth2.OAuth2ClientMapperConfig;
import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig;
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
import org.thingsboard.server.dao.oauth2.OAuth2User;
import org.thingsboard.server.service.security.model.SecurityUser;
@Service(value = "customOAuth2ClientMapper")
@Slf4j
public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper {
private static final String PROVIDER_ACCESS_TOKEN = "provider-access-token";
private static final ObjectMapper json = new ObjectMapper();
private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
@Override
public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, OAuth2ClientMapperConfig config) {
OAuth2User oauth2User = getOAuth2User(token, config.getCustom());
public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config) {
OAuth2User oauth2User = getOAuth2User(token, providerAccessToken, config.getCustom());
return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser());
}
private synchronized OAuth2User getOAuth2User(OAuth2AuthenticationToken token, OAuth2ClientMapperConfig.CustomOAuth2ClientMapperConfig custom) {
private synchronized OAuth2User getOAuth2User(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2CustomMapperConfig custom) {
if (!StringUtils.isEmpty(custom.getUsername()) && !StringUtils.isEmpty(custom.getPassword())) {
restTemplateBuilder = restTemplateBuilder.basicAuthentication(custom.getUsername(), custom.getPassword());
}
if (custom.isSendToken() && !StringUtils.isEmpty(providerAccessToken)) {
restTemplateBuilder = restTemplateBuilder.defaultHeader(PROVIDER_ACCESS_TOKEN, providerAccessToken);
}
RestTemplate restTemplate = restTemplateBuilder.build();
String request;
try {

91
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java

@ -0,0 +1,91 @@
/**
* 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 lombok.Data;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
import org.thingsboard.server.dao.oauth2.OAuth2Configuration;
import org.thingsboard.server.dao.oauth2.OAuth2User;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.ArrayList;
import java.util.Map;
import java.util.Optional;
@Service(value = "githubOAuth2ClientMapper")
@Slf4j
public class GithubOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper {
private static final String EMAIL_URL_KEY = "emailUrl";
private static final String AUTHORIZATION = "Authorization";
private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
@Autowired
private OAuth2Configuration oAuth2Configuration;
@Override
public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config) {
Map<String, String> githubMapperConfig = oAuth2Configuration.getGithubMapper();
String email = getEmail(githubMapperConfig.get(EMAIL_URL_KEY), providerAccessToken);
Map<String, Object> attributes = token.getPrincipal().getAttributes();
OAuth2User oAuth2User = BasicMapperUtils.getOAuth2User(email, attributes, config);
return getOrCreateSecurityUserFromOAuth2User(oAuth2User, config.isAllowUserCreation(), config.isActivateUser());
}
private synchronized String getEmail(String emailUrl, String oauth2Token) {
restTemplateBuilder = restTemplateBuilder.defaultHeader(AUTHORIZATION, "token " + oauth2Token);
RestTemplate restTemplate = restTemplateBuilder.build();
GithubEmailsResponse githubEmailsResponse;
try {
githubEmailsResponse = restTemplate.getForEntity(emailUrl, GithubEmailsResponse.class).getBody();
if (githubEmailsResponse == null){
throw new RuntimeException("Empty Github response!");
}
} catch (Exception e) {
log.error("There was an error during connection to Github API", e);
throw new RuntimeException("Unable to login. Please contact your Administrator!");
}
Optional<String> emailOpt = githubEmailsResponse.stream()
.filter(GithubEmailResponse::isPrimary)
.map(GithubEmailResponse::getEmail)
.findAny();
if (emailOpt.isPresent()){
return emailOpt.get();
} else {
log.error("Could not find primary email from {}.", githubEmailsResponse);
throw new RuntimeException("Unable to login. Please contact your Administrator!");
}
}
private static class GithubEmailsResponse extends ArrayList<GithubEmailResponse> {}
@Data
@ToString
private static class GithubEmailResponse {
private String email;
private boolean verified;
private boolean primary;
private String visibility;
}
}

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

@ -16,9 +16,9 @@
package org.thingsboard.server.service.security.auth.oauth2;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.thingsboard.server.dao.oauth2.OAuth2ClientMapperConfig;
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
import org.thingsboard.server.service.security.model.SecurityUser;
public interface OAuth2ClientMapper {
SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, OAuth2ClientMapperConfig config);
SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config);
}

17
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperProvider.java

@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.oauth2.MapperType;
@Component
@Slf4j
@ -32,14 +33,20 @@ public class OAuth2ClientMapperProvider {
@Qualifier("customOAuth2ClientMapper")
private OAuth2ClientMapper customOAuth2ClientMapper;
public OAuth2ClientMapper getOAuth2ClientMapperByType(String oauth2ClientType) {
switch (oauth2ClientType) {
case "custom":
@Autowired
@Qualifier("githubOAuth2ClientMapper")
private OAuth2ClientMapper githubOAuth2ClientMapper;
public OAuth2ClientMapper getOAuth2ClientMapperByType(MapperType oauth2MapperType) {
switch (oauth2MapperType) {
case CUSTOM:
return customOAuth2ClientMapper;
case "basic":
case BASIC:
return basicOAuth2ClientMapper;
case GITHUB:
return githubOAuth2ClientMapper;
default:
throw new RuntimeException("OAuth2ClientMapper with type " + oauth2ClientType + " is not supported!");
throw new RuntimeException("OAuth2ClientRegistrationMapper with type " + oauth2MapperType + " is not supported!");
}
}
}

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

@ -16,13 +16,14 @@
package org.thingsboard.server.service.security.auth.oauth2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import org.thingsboard.server.dao.oauth2.OAuth2Client;
import org.thingsboard.server.dao.oauth2.OAuth2Configuration;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
import org.thingsboard.server.dao.oauth2.OAuth2Service;
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;
@ -34,25 +35,28 @@ import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
@Component(value = "oauth2AuthenticationSuccessHandler")
@ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true")
public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
private final JwtTokenFactory tokenFactory;
private final RefreshTokenRepository refreshTokenRepository;
private final OAuth2ClientMapperProvider oauth2ClientMapperProvider;
private final OAuth2Configuration oauth2Configuration;
private final OAuth2Service oAuth2Service;
private final OAuth2AuthorizedClientService oAuth2AuthorizedClientService;
@Autowired
public Oauth2AuthenticationSuccessHandler(final JwtTokenFactory tokenFactory,
final RefreshTokenRepository refreshTokenRepository,
final OAuth2ClientMapperProvider oauth2ClientMapperProvider,
final OAuth2Configuration oauth2Configuration) {
final OAuth2Service oAuth2Service,
final OAuth2AuthorizedClientService oAuth2AuthorizedClientService) {
this.tokenFactory = tokenFactory;
this.refreshTokenRepository = refreshTokenRepository;
this.oauth2ClientMapperProvider = oauth2ClientMapperProvider;
this.oauth2Configuration = oauth2Configuration;
this.oAuth2Service = oAuth2Service;
this.oAuth2AuthorizedClientService = oAuth2AuthorizedClientService;
}
@Override
@ -64,9 +68,13 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
try {
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
OAuth2Client oauth2Client = oauth2Configuration.getClientByRegistrationId(token.getAuthorizedClientRegistrationId());
OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(oauth2Client.getMapperConfig().getType());
SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, oauth2Client.getMapperConfig());
OAuth2ClientRegistrationInfo clientRegistration = oAuth2Service.findClientRegistrationInfo(UUID.fromString(token.getAuthorizedClientRegistrationId()));
OAuth2AuthorizedClient oAuth2AuthorizedClient = oAuth2AuthorizedClientService.loadAuthorizedClient(
token.getAuthorizedClientRegistrationId(),
token.getPrincipal().getName());
OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(clientRegistration.getMapperConfig().getType());
SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, oAuth2AuthorizedClient.getAccessToken().getTokenValue(),
clientRegistration.getMapperConfig());
JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);

2
application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java

@ -32,6 +32,8 @@ public enum Resource {
USER(EntityType.USER),
WIDGETS_BUNDLE(EntityType.WIDGETS_BUNDLE),
WIDGET_TYPE(EntityType.WIDGET_TYPE),
OAUTH2_CONFIGURATION_INFO(),
OAUTH2_CONFIGURATION_TEMPLATE(),
TENANT_PROFILE(EntityType.TENANT_PROFILE),
DEVICE_PROFILE(EntityType.DEVICE_PROFILE);

6
application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java

@ -19,14 +19,10 @@ import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.HashMap;
import java.util.Optional;
@Component(value="sysAdminPermissions")
public class SysAdminPermissions extends AbstractPermissions {
@ -39,6 +35,8 @@ public class SysAdminPermissions extends AbstractPermissions {
put(Resource.USER, userPermissionChecker);
put(Resource.WIDGETS_BUNDLE, systemEntityPermissionChecker);
put(Resource.WIDGET_TYPE, systemEntityPermissionChecker);
put(Resource.OAUTH2_CONFIGURATION_INFO, PermissionChecker.allowAllPermissionChecker);
put(Resource.OAUTH2_CONFIGURATION_TEMPLATE, PermissionChecker.allowAllPermissionChecker);
put(Resource.TENANT_PROFILE, PermissionChecker.allowAllPermissionChecker);
}

3
application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java

@ -19,13 +19,10 @@ import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.HashMap;
@Component(value="tenantAdminPermissions")
public class TenantAdminPermissions extends AbstractPermissions {

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

@ -49,12 +49,27 @@ public class MiscUtils {
}
public static String constructBaseUrl(HttpServletRequest request) {
String scheme = request.getScheme();
return String.format("%s://%s:%d",
getScheme(request),
getDomainName(request),
getPort(request));
}
public static String getScheme(HttpServletRequest request){
String scheme = request.getScheme();
String forwardedProto = request.getHeader("x-forwarded-proto");
if (forwardedProto != null) {
scheme = forwardedProto;
}
return scheme;
}
public static String getDomainName(HttpServletRequest request){
return request.getServerName();
}
public static int getPort(HttpServletRequest request){
String forwardedProto = request.getHeader("x-forwarded-proto");
int serverPort = request.getServerPort();
if (request.getHeader("x-forwarded-port") != null) {
@ -72,11 +87,6 @@ public class MiscUtils {
break;
}
}
String baseUrl = String.format("%s://%s:%d",
scheme,
request.getServerName(),
serverPort);
return baseUrl;
return serverPort;
}
}

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

@ -113,62 +113,10 @@ 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:
# 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:}"
# 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:}"
# '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:
# 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:
# 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:}"
# 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:}"
# If this field is not empty, user will be created with default defined Dashboard
defaultDashboardName: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_DEFAULT_DASHBOARD_NAME:}"
# If this field is set 'true' along with non-empty 'defaultDashboardName', user will start from the defined Dashboard in fullscreen mode
alwaysFullScreen: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_ALWAYS_FULL_SCREEN:false}"
custom:
url: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_URL:}"
username: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_USERNAME:}"
password: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_PASSWORD:}"
githubMapper:
emailUrl: "${SECURITY_OAUTH2_GITHUB_MAPPER_EMAIL_URL_KEY:https://api.github.com/user/emails}"
# Dashboard parameters
dashboard:

31
common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateService.java

@ -0,0 +1,31 @@
/**
* 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.dao.oauth2;
import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate;
import java.util.List;
public interface OAuth2ConfigTemplateService {
OAuth2ClientRegistrationTemplate saveClientRegistrationTemplate(OAuth2ClientRegistrationTemplate clientRegistrationTemplate);
OAuth2ClientRegistrationTemplate findClientRegistrationTemplateById(OAuth2ClientRegistrationTemplateId templateId);
List<OAuth2ClientRegistrationTemplate> findAllClientRegistrationTemplates();
void deleteClientRegistrationTemplateById(OAuth2ClientRegistrationTemplateId templateId);
}

12
common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java

@ -16,10 +16,20 @@
package org.thingsboard.server.dao.oauth2;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams;
import java.util.List;
import java.util.UUID;
public interface OAuth2Service {
List<OAuth2ClientInfo> getOAuth2Clients(String domainScheme, String domainName);
List<OAuth2ClientInfo> getOAuth2Clients();
void saveOAuth2Params(OAuth2ClientsParams oauth2Params);
OAuth2ClientsParams findOAuth2Params();
OAuth2ClientRegistrationInfo findClientRegistrationInfo(UUID id);
List<OAuth2ClientRegistrationInfo> findAllClientRegistrationInfos();
}

4
common/dao-api/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsService.java

@ -24,7 +24,7 @@ public interface AdminSettingsService {
AdminSettings findAdminSettingsById(TenantId tenantId, AdminSettingsId adminSettingsId);
AdminSettings findAdminSettingsByKey(TenantId tenantId, String key);
AdminSettings saveAdminSettings(TenantId tenantId, AdminSettings adminSettings);
}

10
common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2IntegrationId.java → common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationId.java

@ -20,16 +20,14 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.UUID;
public class OAuth2IntegrationId extends UUIDBased {
private static final long serialVersionUID = 1L;
public class OAuth2ClientRegistrationId extends UUIDBased {
@JsonCreator
public OAuth2IntegrationId(@JsonProperty("id") UUID id) {
public OAuth2ClientRegistrationId(@JsonProperty("id") UUID id) {
super(id);
}
public static OAuth2IntegrationId fromString(String oauth2IntegrationId) {
return new OAuth2IntegrationId(UUID.fromString(oauth2IntegrationId));
public static OAuth2ClientRegistrationId fromString(String clientRegistrationId) {
return new OAuth2ClientRegistrationId(UUID.fromString(clientRegistrationId));
}
}

33
common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationInfoId.java

@ -0,0 +1,33 @@
/**
* 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.common.data.id;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.UUID;
public class OAuth2ClientRegistrationInfoId extends UUIDBased {
@JsonCreator
public OAuth2ClientRegistrationInfoId(@JsonProperty("id") UUID id) {
super(id);
}
public static OAuth2ClientRegistrationInfoId fromString(String clientRegistrationInfoId) {
return new OAuth2ClientRegistrationInfoId(UUID.fromString(clientRegistrationInfoId));
}
}

33
common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationTemplateId.java

@ -0,0 +1,33 @@
/**
* 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.common.data.id;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.UUID;
public class OAuth2ClientRegistrationTemplateId extends UUIDBased {
@JsonCreator
public OAuth2ClientRegistrationTemplateId(@JsonProperty("id") UUID id) {
super(id);
}
public static OAuth2ClientRegistrationTemplateId fromString(String clientRegistrationTemplateId) {
return new OAuth2ClientRegistrationTemplateId(UUID.fromString(clientRegistrationTemplateId));
}
}

35
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Client.java → common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java

@ -13,27 +13,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.oauth2;
package org.thingsboard.server.common.data.oauth2;
import lombok.Data;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.*;
import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId;
@Data
public class OAuth2Client {
import java.util.List;
private String loginButtonLabel;
private String loginButtonIcon;
private String clientName;
@EqualsAndHashCode
@Data
@ToString(exclude = {"clientSecret"})
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ClientRegistrationDto {
private OAuth2MapperConfig mapperConfig;
private String clientId;
private String clientSecret;
private String accessTokenUri;
private String authorizationUri;
private String scope;
private String redirectUriTemplate;
private String jwkSetUri;
private String authorizationGrantType;
private String clientAuthenticationMethod;
private String accessTokenUri;
private List<String> scope;
private String userInfoUri;
private String userNameAttributeName;
private OAuth2ClientMapperConfig mapperConfig;
private String jwkSetUri;
private String clientAuthenticationMethod;
private String loginButtonLabel;
private String loginButtonIcon;
private JsonNode additionalInfo;
}

29
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/DomainInfo.java

@ -0,0 +1,29 @@
/**
* 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.common.data.oauth2;
import lombok.*;
@EqualsAndHashCode
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class DomainInfo {
private SchemeType scheme;
private String name;
}

39
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ExtendedOAuth2ClientRegistrationInfo.java

@ -0,0 +1,39 @@
/**
* 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.common.data.oauth2;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class ExtendedOAuth2ClientRegistrationInfo extends OAuth2ClientRegistrationInfo {
private String domainName;
private SchemeType domainScheme;
public ExtendedOAuth2ClientRegistrationInfo() {
super();
}
public ExtendedOAuth2ClientRegistrationInfo(OAuth2ClientRegistrationInfo oAuth2ClientRegistrationInfo,
SchemeType domainScheme,
String domainName) {
super(oAuth2ClientRegistrationInfo);
this.domainScheme = domainScheme;
this.domainName = domainName;
}
}

20
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/MapperType.java

@ -0,0 +1,20 @@
/**
* 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.common.data.oauth2;
public enum MapperType {
BASIC, CUSTOM, GITHUB;
}

33
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2BasicMapperConfig.java

@ -0,0 +1,33 @@
/**
* 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.common.data.oauth2;
import lombok.*;
@Builder(toBuilder = true)
@EqualsAndHashCode
@Data
@ToString
public class OAuth2BasicMapperConfig {
private final String emailAttributeKey;
private final String firstNameAttributeKey;
private final String lastNameAttributeKey;
private final TenantNameStrategyType tenantNameStrategy;
private final String tenantNamePattern;
private final String customerNamePattern;
private final String defaultDashboardName;
private final boolean alwaysFullScreen;
}

19
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientInfo.java

@ -17,27 +17,20 @@ package org.thingsboard.server.common.data.oauth2;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.id.OAuth2IntegrationId;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
@EqualsAndHashCode(callSuper = true)
@EqualsAndHashCode
@Data
public class OAuth2ClientInfo extends BaseData<OAuth2IntegrationId> {
@NoArgsConstructor
@AllArgsConstructor
public class OAuth2ClientInfo {
private String name;
private String icon;
private String url;
public OAuth2ClientInfo() {
super();
}
public OAuth2ClientInfo(OAuth2IntegrationId id) {
super(id);
}
public OAuth2ClientInfo(OAuth2ClientInfo oauth2ClientInfo) {
super(oauth2ClientInfo);
this.name = oauth2ClientInfo.getName();
this.icon = oauth2ClientInfo.getIcon();
this.url = oauth2ClientInfo.getUrl();

42
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java

@ -0,0 +1,42 @@
/**
* 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.common.data.oauth2;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId;
import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId;
@EqualsAndHashCode(callSuper = true)
@Data
@ToString
@NoArgsConstructor
public class OAuth2ClientRegistration extends BaseData<OAuth2ClientRegistrationId> {
private OAuth2ClientRegistrationInfoId clientRegistrationId;
private String domainName;
private SchemeType domainScheme;
public OAuth2ClientRegistration(OAuth2ClientRegistration clientRegistration) {
super(clientRegistration);
this.clientRegistrationId = clientRegistration.clientRegistrationId;
this.domainName = clientRegistration.domainName;
this.domainScheme = clientRegistration.domainScheme;
}
}

76
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationInfo.java

@ -0,0 +1,76 @@
/**
* 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.common.data.oauth2;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId;
import java.util.List;
@EqualsAndHashCode(callSuper = true)
@Data
@ToString(exclude = {"clientSecret"})
@NoArgsConstructor
public class OAuth2ClientRegistrationInfo extends SearchTextBasedWithAdditionalInfo<OAuth2ClientRegistrationInfoId> implements HasName {
private boolean enabled;
private OAuth2MapperConfig mapperConfig;
private String clientId;
private String clientSecret;
private String authorizationUri;
private String accessTokenUri;
private List<String> scope;
private String userInfoUri;
private String userNameAttributeName;
private String jwkSetUri;
private String clientAuthenticationMethod;
private String loginButtonLabel;
private String loginButtonIcon;
public OAuth2ClientRegistrationInfo(OAuth2ClientRegistrationInfo clientRegistration) {
super(clientRegistration);
this.enabled = clientRegistration.enabled;
this.mapperConfig = clientRegistration.mapperConfig;
this.clientId = clientRegistration.clientId;
this.clientSecret = clientRegistration.clientSecret;
this.authorizationUri = clientRegistration.authorizationUri;
this.accessTokenUri = clientRegistration.accessTokenUri;
this.scope = clientRegistration.scope;
this.userInfoUri = clientRegistration.userInfoUri;
this.userNameAttributeName = clientRegistration.userNameAttributeName;
this.jwkSetUri = clientRegistration.jwkSetUri;
this.clientAuthenticationMethod = clientRegistration.clientAuthenticationMethod;
this.loginButtonLabel = clientRegistration.loginButtonLabel;
this.loginButtonIcon = clientRegistration.loginButtonIcon;
}
@Override
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getName() {
return loginButtonLabel;
}
@Override
public String getSearchText() {
return getName();
}
}

78
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java

@ -0,0 +1,78 @@
/**
* 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.common.data.oauth2;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.List;
@EqualsAndHashCode(callSuper = true)
@Data
@ToString
@NoArgsConstructor
public class OAuth2ClientRegistrationTemplate extends SearchTextBasedWithAdditionalInfo<OAuth2ClientRegistrationTemplateId> implements HasName {
private String providerId;
private MapperType mapperType;
private OAuth2BasicMapperConfig basic;
private String authorizationUri;
private String accessTokenUri;
private List<String> scope;
private String userInfoUri;
private String userNameAttributeName;
private String jwkSetUri;
private String clientAuthenticationMethod;
private String comment;
private String loginButtonIcon;
private String loginButtonLabel;
private String helpLink;
public OAuth2ClientRegistrationTemplate(OAuth2ClientRegistrationTemplate clientRegistrationTemplate) {
super(clientRegistrationTemplate);
this.providerId = clientRegistrationTemplate.providerId;
this.mapperType = clientRegistrationTemplate.mapperType;
this.basic = clientRegistrationTemplate.basic;
this.authorizationUri = clientRegistrationTemplate.authorizationUri;
this.accessTokenUri = clientRegistrationTemplate.accessTokenUri;
this.scope = clientRegistrationTemplate.scope;
this.userInfoUri = clientRegistrationTemplate.userInfoUri;
this.userNameAttributeName = clientRegistrationTemplate.userNameAttributeName;
this.jwkSetUri = clientRegistrationTemplate.jwkSetUri;
this.clientAuthenticationMethod = clientRegistrationTemplate.clientAuthenticationMethod;
this.comment = clientRegistrationTemplate.comment;
this.loginButtonIcon = clientRegistrationTemplate.loginButtonIcon;
this.loginButtonLabel = clientRegistrationTemplate.loginButtonLabel;
this.helpLink = clientRegistrationTemplate.helpLink;
}
@Override
public String getName() {
return providerId;
}
@Override
public String getSearchText() {
return getName();
}
}

32
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java

@ -0,0 +1,32 @@
/**
* 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.common.data.oauth2;
import lombok.*;
import java.util.List;
import java.util.Set;
@EqualsAndHashCode
@Data
@ToString
@Builder(toBuilder = true)
@NoArgsConstructor
@AllArgsConstructor
public class OAuth2ClientsDomainParams {
private Set<DomainInfo> domainInfos;
private Set<ClientRegistrationDto> clientRegistrations;
}

30
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java

@ -0,0 +1,30 @@
/**
* 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.common.data.oauth2;
import lombok.*;
import java.util.Set;
@EqualsAndHashCode
@Data
@ToString
@Builder(toBuilder = true)
@NoArgsConstructor
@AllArgsConstructor
public class OAuth2ClientsParams {
private boolean enabled;
private Set<OAuth2ClientsDomainParams> domainsParams;
}

29
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2CustomMapperConfig.java

@ -0,0 +1,29 @@
/**
* 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.common.data.oauth2;
import lombok.*;
@Builder(toBuilder = true)
@EqualsAndHashCode
@Data
@ToString(exclude = {"password"})
public class OAuth2CustomMapperConfig {
private final String url;
private final String username;
private final String password;
private final boolean sendToken;
}

33
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java

@ -0,0 +1,33 @@
/**
* 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.common.data.oauth2;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Builder(toBuilder = true)
@EqualsAndHashCode
@Data
@ToString
public class OAuth2MapperConfig {
private boolean allowUserCreation;
private boolean activateUser;
private MapperType type;
private OAuth2BasicMapperConfig basic;
private OAuth2CustomMapperConfig custom;
}

20
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/SchemeType.java

@ -0,0 +1,20 @@
/**
* 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.common.data.oauth2;
public enum SchemeType {
HTTP, HTTPS, MIXED;
}

20
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/TenantNameStrategyType.java

@ -0,0 +1,20 @@
/**
* 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.common.data.oauth2;
public enum TenantNameStrategyType {
DOMAIN, EMAIL, CUSTOM;
}

47
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -389,6 +389,53 @@ public class ModelConstants {
public static final String RULE_NODE_STATE_ENTITY_ID_PROPERTY = "entity_id";
public static final String RULE_NODE_STATE_DATA_PROPERTY = "state_data";
/**
* OAuth2 client registration constants.
*/
public static final String OAUTH2_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String OAUTH2_CLIENT_REGISTRATION_INFO_COLUMN_FAMILY_NAME = "oauth2_client_registration_info";
public static final String OAUTH2_CLIENT_REGISTRATION_COLUMN_FAMILY_NAME = "oauth2_client_registration";
public static final String OAUTH2_CLIENT_REGISTRATION_TO_DOMAIN_COLUMN_FAMILY_NAME = "oauth2_client_registration_to_domain";
public static final String OAUTH2_CLIENT_REGISTRATION_TEMPLATE_COLUMN_FAMILY_NAME = "oauth2_client_registration_template";
public static final String OAUTH2_ENABLED_PROPERTY = "enabled";
public static final String OAUTH2_TEMPLATE_PROVIDER_ID_PROPERTY = "provider_id";
public static final String OAUTH2_CLIENT_REGISTRATION_INFO_ID_PROPERTY = "client_registration_info_id";
public static final String OAUTH2_DOMAIN_NAME_PROPERTY = "domain_name";
public static final String OAUTH2_DOMAIN_SCHEME_PROPERTY = "domain_scheme";
public static final String OAUTH2_CLIENT_ID_PROPERTY = "client_id";
public static final String OAUTH2_CLIENT_SECRET_PROPERTY = "client_secret";
public static final String OAUTH2_AUTHORIZATION_URI_PROPERTY = "authorization_uri";
public static final String OAUTH2_TOKEN_URI_PROPERTY = "token_uri";
public static final String OAUTH2_REDIRECT_URI_TEMPLATE_PROPERTY = "redirect_uri_template";
public static final String OAUTH2_SCOPE_PROPERTY = "scope";
public static final String OAUTH2_USER_INFO_URI_PROPERTY = "user_info_uri";
public static final String OAUTH2_USER_NAME_ATTRIBUTE_NAME_PROPERTY = "user_name_attribute_name";
public static final String OAUTH2_JWK_SET_URI_PROPERTY = "jwk_set_uri";
public static final String OAUTH2_CLIENT_AUTHENTICATION_METHOD_PROPERTY = "client_authentication_method";
public static final String OAUTH2_LOGIN_BUTTON_LABEL_PROPERTY = "login_button_label";
public static final String OAUTH2_LOGIN_BUTTON_ICON_PROPERTY = "login_button_icon";
public static final String OAUTH2_ALLOW_USER_CREATION_PROPERTY = "allow_user_creation";
public static final String OAUTH2_ACTIVATE_USER_PROPERTY = "activate_user";
public static final String OAUTH2_MAPPER_TYPE_PROPERTY = "type";
public static final String OAUTH2_EMAIL_ATTRIBUTE_KEY_PROPERTY = "basic_email_attribute_key";
public static final String OAUTH2_FIRST_NAME_ATTRIBUTE_KEY_PROPERTY = "basic_first_name_attribute_key";
public static final String OAUTH2_LAST_NAME_ATTRIBUTE_KEY_PROPERTY = "basic_last_name_attribute_key";
public static final String OAUTH2_TENANT_NAME_STRATEGY_PROPERTY = "basic_tenant_name_strategy";
public static final String OAUTH2_TENANT_NAME_PATTERN_PROPERTY = "basic_tenant_name_pattern";
public static final String OAUTH2_CUSTOMER_NAME_PATTERN_PROPERTY = "basic_customer_name_pattern";
public static final String OAUTH2_DEFAULT_DASHBOARD_NAME_PROPERTY = "basic_default_dashboard_name";
public static final String OAUTH2_ALWAYS_FULL_SCREEN_PROPERTY = "basic_always_full_screen";
public static final String OAUTH2_MAPPER_URL_PROPERTY = "custom_url";
public static final String OAUTH2_MAPPER_USERNAME_PROPERTY = "custom_username";
public static final String OAUTH2_MAPPER_PASSWORD_PROPERTY = "custom_password";
public static final String OAUTH2_MAPPER_SEND_TOKEN_PROPERTY = "custom_send_token";
public static final String OAUTH2_TEMPLATE_COMMENT_PROPERTY = "comment";
public static final String OAUTH2_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
public static final String OAUTH2_TEMPLATE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
public static final String OAUTH2_TEMPLATE_LOGIN_BUTTON_ICON_PROPERTY = OAUTH2_LOGIN_BUTTON_ICON_PROPERTY;
public static final String OAUTH2_TEMPLATE_LOGIN_BUTTON_LABEL_PROPERTY = OAUTH2_LOGIN_BUTTON_LABEL_PROPERTY;
public static final String OAUTH2_TEMPLATE_HELP_LINK_PROPERTY = "help_link";
/**
* Cassandra attributes and timeseries constants.
*/

231
dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractOAuth2ClientRegistrationInfoEntity.java

@ -0,0 +1,231 @@
/**
* 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.dao.model.sql;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId;
import org.thingsboard.server.common.data.oauth2.*;
import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.util.mapping.JsonStringType;
import javax.persistence.*;
import java.util.Arrays;
@Data
@EqualsAndHashCode(callSuper = true)
@TypeDef(name = "json", typeClass = JsonStringType.class)
@MappedSuperclass
public abstract class AbstractOAuth2ClientRegistrationInfoEntity<T extends OAuth2ClientRegistrationInfo> extends BaseSqlEntity<T> {
@Column(name = ModelConstants.OAUTH2_ENABLED_PROPERTY)
private Boolean enabled;
@Column(name = ModelConstants.OAUTH2_CLIENT_ID_PROPERTY)
private String clientId;
@Column(name = ModelConstants.OAUTH2_CLIENT_SECRET_PROPERTY)
private String clientSecret;
@Column(name = ModelConstants.OAUTH2_AUTHORIZATION_URI_PROPERTY)
private String authorizationUri;
@Column(name = ModelConstants.OAUTH2_TOKEN_URI_PROPERTY)
private String tokenUri;
@Column(name = ModelConstants.OAUTH2_SCOPE_PROPERTY)
private String scope;
@Column(name = ModelConstants.OAUTH2_USER_INFO_URI_PROPERTY)
private String userInfoUri;
@Column(name = ModelConstants.OAUTH2_USER_NAME_ATTRIBUTE_NAME_PROPERTY)
private String userNameAttributeName;
@Column(name = ModelConstants.OAUTH2_JWK_SET_URI_PROPERTY)
private String jwkSetUri;
@Column(name = ModelConstants.OAUTH2_CLIENT_AUTHENTICATION_METHOD_PROPERTY)
private String clientAuthenticationMethod;
@Column(name = ModelConstants.OAUTH2_LOGIN_BUTTON_LABEL_PROPERTY)
private String loginButtonLabel;
@Column(name = ModelConstants.OAUTH2_LOGIN_BUTTON_ICON_PROPERTY)
private String loginButtonIcon;
@Column(name = ModelConstants.OAUTH2_ALLOW_USER_CREATION_PROPERTY)
private Boolean allowUserCreation;
@Column(name = ModelConstants.OAUTH2_ACTIVATE_USER_PROPERTY)
private Boolean activateUser;
@Enumerated(EnumType.STRING)
@Column(name = ModelConstants.OAUTH2_MAPPER_TYPE_PROPERTY)
private MapperType type;
@Column(name = ModelConstants.OAUTH2_EMAIL_ATTRIBUTE_KEY_PROPERTY)
private String emailAttributeKey;
@Column(name = ModelConstants.OAUTH2_FIRST_NAME_ATTRIBUTE_KEY_PROPERTY)
private String firstNameAttributeKey;
@Column(name = ModelConstants.OAUTH2_LAST_NAME_ATTRIBUTE_KEY_PROPERTY)
private String lastNameAttributeKey;
@Enumerated(EnumType.STRING)
@Column(name = ModelConstants.OAUTH2_TENANT_NAME_STRATEGY_PROPERTY)
private TenantNameStrategyType tenantNameStrategy;
@Column(name = ModelConstants.OAUTH2_TENANT_NAME_PATTERN_PROPERTY)
private String tenantNamePattern;
@Column(name = ModelConstants.OAUTH2_CUSTOMER_NAME_PATTERN_PROPERTY)
private String customerNamePattern;
@Column(name = ModelConstants.OAUTH2_DEFAULT_DASHBOARD_NAME_PROPERTY)
private String defaultDashboardName;
@Column(name = ModelConstants.OAUTH2_ALWAYS_FULL_SCREEN_PROPERTY)
private Boolean alwaysFullScreen;
@Column(name = ModelConstants.OAUTH2_MAPPER_URL_PROPERTY)
private String url;
@Column(name = ModelConstants.OAUTH2_MAPPER_USERNAME_PROPERTY)
private String username;
@Column(name = ModelConstants.OAUTH2_MAPPER_PASSWORD_PROPERTY)
private String password;
@Column(name = ModelConstants.OAUTH2_MAPPER_SEND_TOKEN_PROPERTY)
private Boolean sendToken;
@Type(type = "json")
@Column(name = ModelConstants.OAUTH2_ADDITIONAL_INFO_PROPERTY)
private JsonNode additionalInfo;
public AbstractOAuth2ClientRegistrationInfoEntity() {
super();
}
public AbstractOAuth2ClientRegistrationInfoEntity(OAuth2ClientRegistrationInfo clientRegistrationInfo) {
if (clientRegistrationInfo.getId() != null) {
this.setUuid(clientRegistrationInfo.getId().getId());
}
this.createdTime = clientRegistrationInfo.getCreatedTime();
this.enabled = clientRegistrationInfo.isEnabled();
this.clientId = clientRegistrationInfo.getClientId();
this.clientSecret = clientRegistrationInfo.getClientSecret();
this.authorizationUri = clientRegistrationInfo.getAuthorizationUri();
this.tokenUri = clientRegistrationInfo.getAccessTokenUri();
this.scope = clientRegistrationInfo.getScope().stream().reduce((result, element) -> result + "," + element).orElse("");
this.userInfoUri = clientRegistrationInfo.getUserInfoUri();
this.userNameAttributeName = clientRegistrationInfo.getUserNameAttributeName();
this.jwkSetUri = clientRegistrationInfo.getJwkSetUri();
this.clientAuthenticationMethod = clientRegistrationInfo.getClientAuthenticationMethod();
this.loginButtonLabel = clientRegistrationInfo.getLoginButtonLabel();
this.loginButtonIcon = clientRegistrationInfo.getLoginButtonIcon();
this.additionalInfo = clientRegistrationInfo.getAdditionalInfo();
OAuth2MapperConfig mapperConfig = clientRegistrationInfo.getMapperConfig();
if (mapperConfig != null) {
this.allowUserCreation = mapperConfig.isAllowUserCreation();
this.activateUser = mapperConfig.isActivateUser();
this.type = mapperConfig.getType();
OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic();
if (basicConfig != null) {
this.emailAttributeKey = basicConfig.getEmailAttributeKey();
this.firstNameAttributeKey = basicConfig.getFirstNameAttributeKey();
this.lastNameAttributeKey = basicConfig.getLastNameAttributeKey();
this.tenantNameStrategy = basicConfig.getTenantNameStrategy();
this.tenantNamePattern = basicConfig.getTenantNamePattern();
this.customerNamePattern = basicConfig.getCustomerNamePattern();
this.defaultDashboardName = basicConfig.getDefaultDashboardName();
this.alwaysFullScreen = basicConfig.isAlwaysFullScreen();
}
OAuth2CustomMapperConfig customConfig = mapperConfig.getCustom();
if (customConfig != null) {
this.url = customConfig.getUrl();
this.username = customConfig.getUsername();
this.password = customConfig.getPassword();
this.sendToken = customConfig.isSendToken();
}
}
}
public AbstractOAuth2ClientRegistrationInfoEntity(OAuth2ClientRegistrationInfoEntity oAuth2ClientRegistrationInfoEntity) {
this.setId(oAuth2ClientRegistrationInfoEntity.getId());
this.setCreatedTime(oAuth2ClientRegistrationInfoEntity.getCreatedTime());
this.enabled = oAuth2ClientRegistrationInfoEntity.getEnabled();
this.clientId = oAuth2ClientRegistrationInfoEntity.getClientId();
this.clientSecret = oAuth2ClientRegistrationInfoEntity.getClientSecret();
this.authorizationUri = oAuth2ClientRegistrationInfoEntity.getAuthorizationUri();
this.tokenUri = oAuth2ClientRegistrationInfoEntity.getTokenUri();
this.scope = oAuth2ClientRegistrationInfoEntity.getScope();
this.userInfoUri = oAuth2ClientRegistrationInfoEntity.getUserInfoUri();
this.userNameAttributeName = oAuth2ClientRegistrationInfoEntity.getUserNameAttributeName();
this.jwkSetUri = oAuth2ClientRegistrationInfoEntity.getJwkSetUri();
this.clientAuthenticationMethod = oAuth2ClientRegistrationInfoEntity.getClientAuthenticationMethod();
this.loginButtonLabel = oAuth2ClientRegistrationInfoEntity.getLoginButtonLabel();
this.loginButtonIcon = oAuth2ClientRegistrationInfoEntity.getLoginButtonIcon();
this.additionalInfo = oAuth2ClientRegistrationInfoEntity.getAdditionalInfo();
this.allowUserCreation = oAuth2ClientRegistrationInfoEntity.getAllowUserCreation();
this.activateUser = oAuth2ClientRegistrationInfoEntity.getActivateUser();
this.type = oAuth2ClientRegistrationInfoEntity.getType();
this.emailAttributeKey = oAuth2ClientRegistrationInfoEntity.getEmailAttributeKey();
this.firstNameAttributeKey = oAuth2ClientRegistrationInfoEntity.getFirstNameAttributeKey();
this.lastNameAttributeKey = oAuth2ClientRegistrationInfoEntity.getLastNameAttributeKey();
this.tenantNameStrategy = oAuth2ClientRegistrationInfoEntity.getTenantNameStrategy();
this.tenantNamePattern = oAuth2ClientRegistrationInfoEntity.getTenantNamePattern();
this.customerNamePattern = oAuth2ClientRegistrationInfoEntity.getCustomerNamePattern();
this.defaultDashboardName = oAuth2ClientRegistrationInfoEntity.getDefaultDashboardName();
this.alwaysFullScreen = oAuth2ClientRegistrationInfoEntity.getAlwaysFullScreen();
this.url = oAuth2ClientRegistrationInfoEntity.getUrl();
this.username = oAuth2ClientRegistrationInfoEntity.getUsername();
this.password = oAuth2ClientRegistrationInfoEntity.getPassword();
this.sendToken = oAuth2ClientRegistrationInfoEntity.getSendToken();
}
protected OAuth2ClientRegistrationInfo toOAuth2ClientRegistrationInfo() {
OAuth2ClientRegistrationInfo clientRegistrationInfo = new OAuth2ClientRegistrationInfo();
clientRegistrationInfo.setId(new OAuth2ClientRegistrationInfoId(id));
clientRegistrationInfo.setEnabled(enabled);
clientRegistrationInfo.setCreatedTime(createdTime);
clientRegistrationInfo.setAdditionalInfo(additionalInfo);
clientRegistrationInfo.setMapperConfig(
OAuth2MapperConfig.builder()
.allowUserCreation(allowUserCreation)
.activateUser(activateUser)
.type(type)
.basic(
(type == MapperType.BASIC || type == MapperType.GITHUB) ?
OAuth2BasicMapperConfig.builder()
.emailAttributeKey(emailAttributeKey)
.firstNameAttributeKey(firstNameAttributeKey)
.lastNameAttributeKey(lastNameAttributeKey)
.tenantNameStrategy(tenantNameStrategy)
.tenantNamePattern(tenantNamePattern)
.customerNamePattern(customerNamePattern)
.defaultDashboardName(defaultDashboardName)
.alwaysFullScreen(alwaysFullScreen)
.build()
: null
)
.custom(
type == MapperType.CUSTOM ?
OAuth2CustomMapperConfig.builder()
.url(url)
.username(username)
.password(password)
.sendToken(sendToken)
.build()
: null
)
.build()
);
clientRegistrationInfo.setClientId(clientId);
clientRegistrationInfo.setClientSecret(clientSecret);
clientRegistrationInfo.setAuthorizationUri(authorizationUri);
clientRegistrationInfo.setAccessTokenUri(tokenUri);
clientRegistrationInfo.setScope(Arrays.asList(scope.split(",")));
clientRegistrationInfo.setUserInfoUri(userInfoUri);
clientRegistrationInfo.setUserNameAttributeName(userNameAttributeName);
clientRegistrationInfo.setJwkSetUri(jwkSetUri);
clientRegistrationInfo.setClientAuthenticationMethod(clientAuthenticationMethod);
clientRegistrationInfo.setLoginButtonLabel(loginButtonLabel);
clientRegistrationInfo.setLoginButtonIcon(loginButtonIcon);
return clientRegistrationInfo;
}
}

48
dao/src/main/java/org/thingsboard/server/dao/model/sql/ExtendedOAuth2ClientRegistrationInfoEntity.java

@ -0,0 +1,48 @@
/**
* 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.dao.model.sql;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.oauth2.ExtendedOAuth2ClientRegistrationInfo;
import org.thingsboard.server.common.data.oauth2.SchemeType;
@Data
@EqualsAndHashCode(callSuper = true)
public class ExtendedOAuth2ClientRegistrationInfoEntity extends AbstractOAuth2ClientRegistrationInfoEntity<ExtendedOAuth2ClientRegistrationInfo> {
private String domainName;
private SchemeType domainScheme;
public ExtendedOAuth2ClientRegistrationInfoEntity() {
super();
}
public ExtendedOAuth2ClientRegistrationInfoEntity(OAuth2ClientRegistrationInfoEntity oAuth2ClientRegistrationInfoEntity,
String domainName,
SchemeType domainScheme) {
super(oAuth2ClientRegistrationInfoEntity);
this.domainName = domainName;
this.domainScheme = domainScheme;
}
@Override
public ExtendedOAuth2ClientRegistrationInfo toData() {
return new ExtendedOAuth2ClientRegistrationInfo(super.toOAuth2ClientRegistrationInfo(),
domainScheme,
domainName);
}
}

75
dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java

@ -0,0 +1,75 @@
/**
* 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.dao.model.sql;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId;
import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId;
import org.thingsboard.server.common.data.oauth2.*;
import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.util.mapping.JsonStringType;
import javax.persistence.*;
import java.util.Arrays;
import java.util.UUID;
@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@TypeDef(name = "json", typeClass = JsonStringType.class)
@Table(name = ModelConstants.OAUTH2_CLIENT_REGISTRATION_COLUMN_FAMILY_NAME)
public class OAuth2ClientRegistrationEntity extends BaseSqlEntity<OAuth2ClientRegistration> {
@Column(name = ModelConstants.OAUTH2_CLIENT_REGISTRATION_INFO_ID_PROPERTY, columnDefinition = "uuid")
private UUID clientRegistrationInfoId;
@Column(name = ModelConstants.OAUTH2_DOMAIN_NAME_PROPERTY)
private String domainName;
@Enumerated(EnumType.STRING)
@Column(name = ModelConstants.OAUTH2_DOMAIN_SCHEME_PROPERTY)
private SchemeType domainScheme;
public OAuth2ClientRegistrationEntity() {
super();
}
public OAuth2ClientRegistrationEntity(OAuth2ClientRegistration clientRegistration) {
if (clientRegistration.getId() != null) {
this.setUuid(clientRegistration.getId().getId());
}
if (clientRegistration.getClientRegistrationId() != null){
this.clientRegistrationInfoId = clientRegistration.getClientRegistrationId().getId();
}
this.createdTime = clientRegistration.getCreatedTime();
this.domainName = clientRegistration.getDomainName();
this.domainScheme = clientRegistration.getDomainScheme();
}
@Override
public OAuth2ClientRegistration toData() {
OAuth2ClientRegistration clientRegistration = new OAuth2ClientRegistration();
clientRegistration.setId(new OAuth2ClientRegistrationId(id));
clientRegistration.setClientRegistrationId(new OAuth2ClientRegistrationInfoId(clientRegistrationInfoId));
clientRegistration.setCreatedTime(createdTime);
clientRegistration.setDomainName(domainName);
clientRegistration.setDomainScheme(domainScheme);
return clientRegistration;
}
}

51
dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationInfoEntity.java

@ -0,0 +1,51 @@
/**
* 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.dao.model.sql;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.util.mapping.JsonStringType;
import javax.persistence.Entity;
import javax.persistence.Table;
@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@TypeDef(name = "json", typeClass = JsonStringType.class)
@Table(name = ModelConstants.OAUTH2_CLIENT_REGISTRATION_INFO_COLUMN_FAMILY_NAME)
public class OAuth2ClientRegistrationInfoEntity extends AbstractOAuth2ClientRegistrationInfoEntity<OAuth2ClientRegistrationInfo> {
public OAuth2ClientRegistrationInfoEntity() {
super();
}
public OAuth2ClientRegistrationInfoEntity(OAuth2ClientRegistrationInfo clientRegistration) {
super(clientRegistration);
}
public OAuth2ClientRegistrationInfoEntity(OAuth2ClientRegistrationInfoEntity oAuth2ClientRegistrationInfoEntity) {
super(oAuth2ClientRegistrationInfoEntity);
}
@Override
public OAuth2ClientRegistrationInfo toData() {
return super.toOAuth2ClientRegistrationInfo();
}
}

160
dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationTemplateEntity.java

@ -0,0 +1,160 @@
/**
* 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.dao.model.sql;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.*;
import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.util.mapping.JsonStringType;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate;
import javax.persistence.*;
import java.util.Arrays;
import java.util.UUID;
@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@TypeDef(name = "json", typeClass = JsonStringType.class)
@Table(name = ModelConstants.OAUTH2_CLIENT_REGISTRATION_TEMPLATE_COLUMN_FAMILY_NAME)
public class OAuth2ClientRegistrationTemplateEntity extends BaseSqlEntity<OAuth2ClientRegistrationTemplate> {
@Column(name = ModelConstants.OAUTH2_TEMPLATE_PROVIDER_ID_PROPERTY)
private String providerId;
@Column(name = ModelConstants.OAUTH2_AUTHORIZATION_URI_PROPERTY)
private String authorizationUri;
@Column(name = ModelConstants.OAUTH2_TOKEN_URI_PROPERTY)
private String tokenUri;
@Column(name = ModelConstants.OAUTH2_SCOPE_PROPERTY)
private String scope;
@Column(name = ModelConstants.OAUTH2_USER_INFO_URI_PROPERTY)
private String userInfoUri;
@Column(name = ModelConstants.OAUTH2_USER_NAME_ATTRIBUTE_NAME_PROPERTY)
private String userNameAttributeName;
@Column(name = ModelConstants.OAUTH2_JWK_SET_URI_PROPERTY)
private String jwkSetUri;
@Column(name = ModelConstants.OAUTH2_CLIENT_AUTHENTICATION_METHOD_PROPERTY)
private String clientAuthenticationMethod;
@Enumerated(EnumType.STRING)
@Column(name = ModelConstants.OAUTH2_MAPPER_TYPE_PROPERTY)
private MapperType type;
@Column(name = ModelConstants.OAUTH2_EMAIL_ATTRIBUTE_KEY_PROPERTY)
private String emailAttributeKey;
@Column(name = ModelConstants.OAUTH2_FIRST_NAME_ATTRIBUTE_KEY_PROPERTY)
private String firstNameAttributeKey;
@Column(name = ModelConstants.OAUTH2_LAST_NAME_ATTRIBUTE_KEY_PROPERTY)
private String lastNameAttributeKey;
@Enumerated(EnumType.STRING)
@Column(name = ModelConstants.OAUTH2_TENANT_NAME_STRATEGY_PROPERTY)
private TenantNameStrategyType tenantNameStrategy;
@Column(name = ModelConstants.OAUTH2_TENANT_NAME_PATTERN_PROPERTY)
private String tenantNamePattern;
@Column(name = ModelConstants.OAUTH2_CUSTOMER_NAME_PATTERN_PROPERTY)
private String customerNamePattern;
@Column(name = ModelConstants.OAUTH2_DEFAULT_DASHBOARD_NAME_PROPERTY)
private String defaultDashboardName;
@Column(name = ModelConstants.OAUTH2_ALWAYS_FULL_SCREEN_PROPERTY)
private Boolean alwaysFullScreen;
@Column(name = ModelConstants.OAUTH2_TEMPLATE_COMMENT_PROPERTY)
private String comment;
@Column(name = ModelConstants.OAUTH2_TEMPLATE_LOGIN_BUTTON_ICON_PROPERTY)
private String loginButtonIcon;
@Column(name = ModelConstants.OAUTH2_TEMPLATE_LOGIN_BUTTON_LABEL_PROPERTY)
private String loginButtonLabel;
@Column(name = ModelConstants.OAUTH2_TEMPLATE_HELP_LINK_PROPERTY)
private String helpLink;
@Type(type = "json")
@Column(name = ModelConstants.OAUTH2_TEMPLATE_ADDITIONAL_INFO_PROPERTY)
private JsonNode additionalInfo;
public OAuth2ClientRegistrationTemplateEntity() {
}
public OAuth2ClientRegistrationTemplateEntity(OAuth2ClientRegistrationTemplate clientRegistrationTemplate) {
if (clientRegistrationTemplate.getId() != null) {
this.setUuid(clientRegistrationTemplate.getId().getId());
}
this.createdTime = clientRegistrationTemplate.getCreatedTime();
this.providerId = clientRegistrationTemplate.getProviderId();
this.authorizationUri = clientRegistrationTemplate.getAuthorizationUri();
this.tokenUri = clientRegistrationTemplate.getAccessTokenUri();
this.scope = clientRegistrationTemplate.getScope().stream().reduce((result, element) -> result + "," + element).orElse("");
this.userInfoUri = clientRegistrationTemplate.getUserInfoUri();
this.userNameAttributeName = clientRegistrationTemplate.getUserNameAttributeName();
this.jwkSetUri = clientRegistrationTemplate.getJwkSetUri();
this.clientAuthenticationMethod = clientRegistrationTemplate.getClientAuthenticationMethod();
this.comment = clientRegistrationTemplate.getComment();
this.loginButtonIcon = clientRegistrationTemplate.getLoginButtonIcon();
this.loginButtonLabel = clientRegistrationTemplate.getLoginButtonLabel();
this.helpLink = clientRegistrationTemplate.getHelpLink();
this.additionalInfo = clientRegistrationTemplate.getAdditionalInfo();
this.type = clientRegistrationTemplate.getMapperType();
OAuth2BasicMapperConfig basicConfig = clientRegistrationTemplate.getBasic();
if (basicConfig != null) {
this.emailAttributeKey = basicConfig.getEmailAttributeKey();
this.firstNameAttributeKey = basicConfig.getFirstNameAttributeKey();
this.lastNameAttributeKey = basicConfig.getLastNameAttributeKey();
this.tenantNameStrategy = basicConfig.getTenantNameStrategy();
this.tenantNamePattern = basicConfig.getTenantNamePattern();
this.customerNamePattern = basicConfig.getCustomerNamePattern();
this.defaultDashboardName = basicConfig.getDefaultDashboardName();
this.alwaysFullScreen = basicConfig.isAlwaysFullScreen();
}
}
@Override
public OAuth2ClientRegistrationTemplate toData() {
OAuth2ClientRegistrationTemplate clientRegistrationTemplate = new OAuth2ClientRegistrationTemplate();
clientRegistrationTemplate.setId(new OAuth2ClientRegistrationTemplateId(id));
clientRegistrationTemplate.setCreatedTime(createdTime);
clientRegistrationTemplate.setAdditionalInfo(additionalInfo);
clientRegistrationTemplate.setMapperType(type);
clientRegistrationTemplate.setProviderId(providerId);
clientRegistrationTemplate.setBasic(
OAuth2BasicMapperConfig.builder()
.emailAttributeKey(emailAttributeKey)
.firstNameAttributeKey(firstNameAttributeKey)
.lastNameAttributeKey(lastNameAttributeKey)
.tenantNameStrategy(tenantNameStrategy)
.tenantNamePattern(tenantNamePattern)
.customerNamePattern(customerNamePattern)
.defaultDashboardName(defaultDashboardName)
.alwaysFullScreen(alwaysFullScreen)
.build()
);
clientRegistrationTemplate.setAuthorizationUri(authorizationUri);
clientRegistrationTemplate.setAccessTokenUri(tokenUri);
clientRegistrationTemplate.setScope(Arrays.asList(scope.split(",")));
clientRegistrationTemplate.setUserInfoUri(userInfoUri);
clientRegistrationTemplate.setUserNameAttributeName(userNameAttributeName);
clientRegistrationTemplate.setJwkSetUri(jwkSetUri);
clientRegistrationTemplate.setClientAuthenticationMethod(clientAuthenticationMethod);
clientRegistrationTemplate.setComment(comment);
clientRegistrationTemplate.setLoginButtonIcon(loginButtonIcon);
clientRegistrationTemplate.setLoginButtonLabel(loginButtonLabel);
clientRegistrationTemplate.setHelpLink(helpLink);
return clientRegistrationTemplate;
}
}

59
dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java

@ -0,0 +1,59 @@
/**
* 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.dao.oauth2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
import java.util.UUID;
@Component
public class HybridClientRegistrationRepository implements ClientRegistrationRepository {
private static final String defaultRedirectUriTemplate = "{baseUrl}/login/oauth2/code/{registrationId}";
@Autowired
private OAuth2Service oAuth2Service;
@Override
public ClientRegistration findByRegistrationId(String registrationId) {
OAuth2ClientRegistrationInfo oAuth2ClientRegistrationInfo = oAuth2Service.findClientRegistrationInfo(UUID.fromString(registrationId));
return oAuth2ClientRegistrationInfo == null ?
null : toSpringClientRegistration(oAuth2ClientRegistrationInfo);
}
private ClientRegistration toSpringClientRegistration(OAuth2ClientRegistrationInfo localClientRegistration){
String registrationId = localClientRegistration.getUuidId().toString();
return ClientRegistration.withRegistrationId(registrationId)
.clientName(localClientRegistration.getName())
.clientId(localClientRegistration.getClientId())
.authorizationUri(localClientRegistration.getAuthorizationUri())
.clientSecret(localClientRegistration.getClientSecret())
.tokenUri(localClientRegistration.getAccessTokenUri())
.scope(localClientRegistration.getScope())
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.userInfoUri(localClientRegistration.getUserInfoUri())
.userNameAttributeName(localClientRegistration.getUserNameAttributeName())
.jwkSetUri(localClientRegistration.getJwkSetUri())
.clientAuthenticationMethod(new ClientAuthenticationMethod(localClientRegistration.getClientAuthenticationMethod()))
.redirectUriTemplate(defaultRedirectUriTemplate)
.build();
}
}

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

@ -1,47 +0,0 @@
/**
* 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.dao.oauth2;
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 String emailAttributeKey;
private String firstNameAttributeKey;
private String lastNameAttributeKey;
private String tenantNameStrategy;
private String tenantNamePattern;
private String customerNamePattern;
private boolean alwaysFullScreen;
private String defaultDashboardName;
}
@Data
public static class CustomOAuth2ClientMapperConfig {
private String url;
private String username;
private String password;
}
}

23
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java

@ -0,0 +1,23 @@
/**
* 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.dao.oauth2;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration;
import org.thingsboard.server.dao.Dao;
public interface OAuth2ClientRegistrationDao extends Dao<OAuth2ClientRegistration> {
void deleteAll();
}

34
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationInfoDao.java

@ -0,0 +1,34 @@
/**
* 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.dao.oauth2;
import org.thingsboard.server.common.data.oauth2.ExtendedOAuth2ClientRegistrationInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
import org.thingsboard.server.common.data.oauth2.SchemeType;
import org.thingsboard.server.dao.Dao;
import java.util.List;
import java.util.Set;
public interface OAuth2ClientRegistrationInfoDao extends Dao<OAuth2ClientRegistrationInfo> {
List<OAuth2ClientRegistrationInfo> findAll();
List<ExtendedOAuth2ClientRegistrationInfo> findAllExtended();
List<OAuth2ClientRegistrationInfo> findByDomainSchemesAndDomainName(List<SchemeType> domainSchemes, String domainName);
void deleteAll();
}

25
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationTemplateDao.java

@ -0,0 +1,25 @@
/**
* 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.dao.oauth2;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate;
import org.thingsboard.server.dao.Dao;
import java.util.List;
public interface OAuth2ClientRegistrationTemplateDao extends Dao<OAuth2ClientRegistrationTemplate> {
List<OAuth2ClientRegistrationTemplate> findAll();
}

103
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java

@ -0,0 +1,103 @@
/**
* 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.dao.oauth2;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.*;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import java.util.List;
import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.dao.service.Validator.validateString;
@Slf4j
@Service
public class OAuth2ConfigTemplateServiceImpl extends AbstractEntityService implements OAuth2ConfigTemplateService {
public static final String INCORRECT_CLIENT_REGISTRATION_TEMPLATE_ID = "Incorrect clientRegistrationTemplateId ";
@Autowired
private OAuth2ClientRegistrationTemplateDao clientRegistrationTemplateDao;
@Override
public OAuth2ClientRegistrationTemplate saveClientRegistrationTemplate(OAuth2ClientRegistrationTemplate clientRegistrationTemplate) {
log.trace("Executing saveClientRegistrationTemplate [{}]", clientRegistrationTemplate);
clientRegistrationTemplateValidator.validate(clientRegistrationTemplate, o -> TenantId.SYS_TENANT_ID);
OAuth2ClientRegistrationTemplate savedClientRegistrationTemplate;
try {
savedClientRegistrationTemplate = clientRegistrationTemplateDao.save(TenantId.SYS_TENANT_ID, clientRegistrationTemplate);
} catch (Exception t) {
ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("oauth2_template_provider_id_unq_key")) {
throw new DataValidationException("Client registration template with such providerId already exists!");
} else {
throw t;
}
}
return savedClientRegistrationTemplate;
}
@Override
public OAuth2ClientRegistrationTemplate findClientRegistrationTemplateById(OAuth2ClientRegistrationTemplateId templateId) {
log.trace("Executing findClientRegistrationTemplateById [{}]", templateId);
validateId(templateId, INCORRECT_CLIENT_REGISTRATION_TEMPLATE_ID + templateId);
return clientRegistrationTemplateDao.findById(TenantId.SYS_TENANT_ID, templateId.getId());
}
@Override
public List<OAuth2ClientRegistrationTemplate> findAllClientRegistrationTemplates() {
log.trace("Executing findAllClientRegistrationTemplates");
return clientRegistrationTemplateDao.findAll();
}
@Override
public void deleteClientRegistrationTemplateById(OAuth2ClientRegistrationTemplateId templateId) {
log.trace("Executing deleteClientRegistrationTemplateById [{}]", templateId);
validateId(templateId, INCORRECT_CLIENT_REGISTRATION_TEMPLATE_ID + templateId);
clientRegistrationTemplateDao.removeById(TenantId.SYS_TENANT_ID, templateId.getId());
}
private final DataValidator<OAuth2ClientRegistrationTemplate> clientRegistrationTemplateValidator =
new DataValidator<OAuth2ClientRegistrationTemplate>() {
@Override
protected void validateCreate(TenantId tenantId, OAuth2ClientRegistrationTemplate clientRegistrationTemplate) {
}
@Override
protected void validateUpdate(TenantId tenantId, OAuth2ClientRegistrationTemplate clientRegistrationTemplate) {
}
@Override
protected void validateDataImpl(TenantId tenantId, OAuth2ClientRegistrationTemplate clientRegistrationTemplate) {
if (StringUtils.isEmpty(clientRegistrationTemplate.getProviderId())) {
throw new DataValidationException("Provider ID should be specified!");
}
if (clientRegistrationTemplate.getBasic() == null) {
throw new DataValidationException("Basic mapper config should be specified!");
}
}
};
}

53
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Configuration.java

@ -17,66 +17,15 @@ package org.thingsboard.server.dao.oauth2;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Configuration
@ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true")
@ConfigurationProperties(prefix = "security.oauth2")
@Data
@Slf4j
public class OAuth2Configuration {
private boolean enabled;
private String loginProcessingUrl;
private Map<String, OAuth2Client> clients = new HashMap<>();
@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
List<ClientRegistration> result = new ArrayList<>();
for (Map.Entry<String, OAuth2Client> entry : clients.entrySet()) {
OAuth2Client client = entry.getValue();
ClientRegistration registration = ClientRegistration.withRegistrationId(entry.getKey())
.clientId(client.getClientId())
.authorizationUri(client.getAuthorizationUri())
.clientSecret(client.getClientSecret())
.tokenUri(client.getAccessTokenUri())
.redirectUriTemplate(client.getRedirectUriTemplate())
.scope(client.getScope().split(","))
.clientName(client.getClientName())
.authorizationGrantType(new AuthorizationGrantType(client.getAuthorizationGrantType()))
.userInfoUri(client.getUserInfoUri())
.userNameAttributeName(client.getUserNameAttributeName())
.jwkSetUri(client.getJwkSetUri())
.clientAuthenticationMethod(new ClientAuthenticationMethod(client.getClientAuthenticationMethod()))
.build();
result.add(registration);
}
return new InMemoryClientRegistrationRepository(result);
}
public OAuth2Client getClientByRegistrationId(String registrationId) {
OAuth2Client result = null;
if (clients != null && !clients.isEmpty()) {
for (String key : clients.keySet()) {
if (key.equals(registrationId)) {
result = clients.get(key);
break;
}
}
}
return result;
}
private Map<String, String> githubMapper;
}

203
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java

@ -18,33 +18,198 @@ package org.thingsboard.server.dao.oauth2;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.*;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.transaction.Transactional;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.dao.service.Validator.validateString;
@Slf4j
@Service
public class OAuth2ServiceImpl implements OAuth2Service {
public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Service {
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
public static final String INCORRECT_CLIENT_REGISTRATION_ID = "Incorrect clientRegistrationId ";
public static final String INCORRECT_DOMAIN_NAME = "Incorrect domainName ";
public static final String INCORRECT_DOMAIN_SCHEME = "Incorrect domainScheme ";
@Autowired(required = false)
OAuth2Configuration oauth2Configuration;
@Autowired
private OAuth2ClientRegistrationInfoDao clientRegistrationInfoDao;
@Autowired
private OAuth2ClientRegistrationDao clientRegistrationDao;
@Override
public List<OAuth2ClientInfo> getOAuth2Clients() {
if (oauth2Configuration == null || !oauth2Configuration.isEnabled()) {
return Collections.emptyList();
public List<OAuth2ClientInfo> getOAuth2Clients(String domainSchemeStr, String domainName) {
log.trace("Executing getOAuth2Clients [{}://{}]", domainSchemeStr, domainName);
if (domainSchemeStr == null) {
throw new IncorrectParameterException(INCORRECT_DOMAIN_SCHEME);
}
List<OAuth2ClientInfo> result = new ArrayList<>();
for (Map.Entry<String, OAuth2Client> entry : oauth2Configuration.getClients().entrySet()) {
OAuth2ClientInfo client = new OAuth2ClientInfo();
client.setName(entry.getValue().getLoginButtonLabel());
client.setUrl(String.format("/oauth2/authorization/%s", entry.getKey()));
client.setIcon(entry.getValue().getLoginButtonIcon());
result.add(client);
SchemeType domainScheme;
try {
domainScheme = SchemeType.valueOf(domainSchemeStr.toUpperCase());
} catch (IllegalArgumentException e){
throw new IncorrectParameterException(INCORRECT_DOMAIN_SCHEME);
}
return result;
validateString(domainName, INCORRECT_DOMAIN_NAME + domainName);
return clientRegistrationInfoDao.findByDomainSchemesAndDomainName(Arrays.asList(domainScheme, SchemeType.MIXED), domainName).stream()
.filter(OAuth2ClientRegistrationInfo::isEnabled)
.map(OAuth2Utils::toClientInfo)
.collect(Collectors.toList());
}
@Override
@Transactional
public void saveOAuth2Params(OAuth2ClientsParams oauth2Params) {
log.trace("Executing saveOAuth2Params [{}]", oauth2Params);
clientParamsValidator.accept(oauth2Params);
clientRegistrationDao.deleteAll();
clientRegistrationInfoDao.deleteAll();
oauth2Params.getDomainsParams().forEach(domainParams -> {
domainParams.getClientRegistrations().forEach(clientRegistrationDto -> {
OAuth2ClientRegistrationInfo oAuth2ClientRegistrationInfo = OAuth2Utils.toClientRegistrationInfo(oauth2Params.isEnabled(), clientRegistrationDto);
OAuth2ClientRegistrationInfo savedClientRegistrationInfo = clientRegistrationInfoDao.save(TenantId.SYS_TENANT_ID, oAuth2ClientRegistrationInfo);
domainParams.getDomainInfos().forEach(domainInfo -> {
OAuth2ClientRegistration oAuth2ClientRegistration = OAuth2Utils.toClientRegistration(savedClientRegistrationInfo.getId(),
domainInfo.getScheme(), domainInfo.getName());
clientRegistrationDao.save(TenantId.SYS_TENANT_ID, oAuth2ClientRegistration);
});
});
});
}
@Override
public OAuth2ClientsParams findOAuth2Params() {
log.trace("Executing findOAuth2Params");
List<ExtendedOAuth2ClientRegistrationInfo> extendedInfos = clientRegistrationInfoDao.findAllExtended();
return OAuth2Utils.toOAuth2Params(extendedInfos);
}
@Override
public OAuth2ClientRegistrationInfo findClientRegistrationInfo(UUID id) {
log.trace("Executing findClientRegistrationInfo [{}]", id);
validateId(id, INCORRECT_CLIENT_REGISTRATION_ID + id);
return clientRegistrationInfoDao.findById(null, id);
}
@Override
public List<OAuth2ClientRegistrationInfo> findAllClientRegistrationInfos() {
log.trace("Executing findAllClientRegistrationInfos");
return clientRegistrationInfoDao.findAll();
}
private final Consumer<OAuth2ClientsParams> clientParamsValidator = oauth2Params -> {
if (oauth2Params == null
|| oauth2Params.getDomainsParams() == null) {
throw new DataValidationException("Domain params should be specified!");
}
for (OAuth2ClientsDomainParams domainParams : oauth2Params.getDomainsParams()) {
if (domainParams.getDomainInfos() == null
|| domainParams.getDomainInfos().isEmpty()) {
throw new DataValidationException("List of domain configuration should be specified!");
}
for (DomainInfo domainInfo : domainParams.getDomainInfos()) {
if (StringUtils.isEmpty(domainInfo.getName())) {
throw new DataValidationException("Domain name should be specified!");
}
if (domainInfo.getScheme() == null) {
throw new DataValidationException("Domain scheme should be specified!");
}
}
domainParams.getDomainInfos().stream()
.collect(Collectors.groupingBy(DomainInfo::getName))
.forEach((domainName, domainInfos) -> {
if (domainInfos.size() > 1 && domainInfos.stream().anyMatch(domainInfo -> domainInfo.getScheme() == SchemeType.MIXED)) {
throw new DataValidationException("MIXED scheme type shouldn't be combined with another scheme type!");
}
});
if (domainParams.getClientRegistrations() == null || domainParams.getClientRegistrations().isEmpty()) {
throw new DataValidationException("Client registrations should be specified!");
}
for (ClientRegistrationDto clientRegistration : domainParams.getClientRegistrations()) {
if (StringUtils.isEmpty(clientRegistration.getClientId())) {
throw new DataValidationException("Client ID should be specified!");
}
if (StringUtils.isEmpty(clientRegistration.getClientSecret())) {
throw new DataValidationException("Client secret should be specified!");
}
if (StringUtils.isEmpty(clientRegistration.getAuthorizationUri())) {
throw new DataValidationException("Authorization uri should be specified!");
}
if (StringUtils.isEmpty(clientRegistration.getAccessTokenUri())) {
throw new DataValidationException("Token uri should be specified!");
}
if (StringUtils.isEmpty(clientRegistration.getScope())) {
throw new DataValidationException("Scope should be specified!");
}
if (StringUtils.isEmpty(clientRegistration.getUserInfoUri())) {
throw new DataValidationException("User info uri should be specified!");
}
if (StringUtils.isEmpty(clientRegistration.getUserNameAttributeName())) {
throw new DataValidationException("User name attribute name should be specified!");
}
if (StringUtils.isEmpty(clientRegistration.getClientAuthenticationMethod())) {
throw new DataValidationException("Client authentication method should be specified!");
}
if (StringUtils.isEmpty(clientRegistration.getLoginButtonLabel())) {
throw new DataValidationException("Login button label should be specified!");
}
OAuth2MapperConfig mapperConfig = clientRegistration.getMapperConfig();
if (mapperConfig == null) {
throw new DataValidationException("Mapper config should be specified!");
}
if (mapperConfig.getType() == null) {
throw new DataValidationException("Mapper config type should be specified!");
}
if (mapperConfig.getType() == MapperType.BASIC) {
OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic();
if (basicConfig == null) {
throw new DataValidationException("Basic config should be specified!");
}
if (StringUtils.isEmpty(basicConfig.getEmailAttributeKey())) {
throw new DataValidationException("Email attribute key should be specified!");
}
if (basicConfig.getTenantNameStrategy() == null) {
throw new DataValidationException("Tenant name strategy should be specified!");
}
if (basicConfig.getTenantNameStrategy() == TenantNameStrategyType.CUSTOM
&& StringUtils.isEmpty(basicConfig.getTenantNamePattern())) {
throw new DataValidationException("Tenant name pattern should be specified!");
}
}
if (mapperConfig.getType() == MapperType.GITHUB) {
OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic();
if (basicConfig == null) {
throw new DataValidationException("Basic config should be specified!");
}
if (!StringUtils.isEmpty(basicConfig.getEmailAttributeKey())) {
throw new DataValidationException("Email attribute key cannot be configured for GITHUB mapper type!");
}
if (basicConfig.getTenantNameStrategy() == null) {
throw new DataValidationException("Tenant name strategy should be specified!");
}
if (basicConfig.getTenantNameStrategy() == TenantNameStrategyType.CUSTOM
&& StringUtils.isEmpty(basicConfig.getTenantNamePattern())) {
throw new DataValidationException("Tenant name pattern should be specified!");
}
}
if (mapperConfig.getType() == MapperType.CUSTOM) {
OAuth2CustomMapperConfig customConfig = mapperConfig.getCustom();
if (customConfig == null) {
throw new DataValidationException("Custom config should be specified!");
}
if (StringUtils.isEmpty(customConfig.getUrl())) {
throw new DataValidationException("Custom mapper URL should be specified!");
}
}
}
}
};
}

102
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java

@ -0,0 +1,102 @@
/**
* 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.dao.oauth2;
import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId;
import org.thingsboard.server.common.data.oauth2.*;
import java.util.*;
public class OAuth2Utils {
public static final String OAUTH2_AUTHORIZATION_PATH_TEMPLATE = "/oauth2/authorization/%s";
public static OAuth2ClientInfo toClientInfo(OAuth2ClientRegistrationInfo clientRegistrationInfo) {
OAuth2ClientInfo client = new OAuth2ClientInfo();
client.setName(clientRegistrationInfo.getLoginButtonLabel());
client.setUrl(String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, clientRegistrationInfo.getUuidId().toString()));
client.setIcon(clientRegistrationInfo.getLoginButtonIcon());
return client;
}
public static OAuth2ClientsParams toOAuth2Params(List<ExtendedOAuth2ClientRegistrationInfo> extendedOAuth2ClientRegistrationInfos) {
Map<OAuth2ClientRegistrationInfoId, Set<DomainInfo>> domainsByInfoId = new HashMap<>();
Map<OAuth2ClientRegistrationInfoId, OAuth2ClientRegistrationInfo> infoById = new HashMap<>();
for (ExtendedOAuth2ClientRegistrationInfo extendedClientRegistrationInfo : extendedOAuth2ClientRegistrationInfos) {
String domainName = extendedClientRegistrationInfo.getDomainName();
SchemeType domainScheme = extendedClientRegistrationInfo.getDomainScheme();
domainsByInfoId.computeIfAbsent(extendedClientRegistrationInfo.getId(), key -> new HashSet<>())
.add(new DomainInfo(domainScheme, domainName));
infoById.put(extendedClientRegistrationInfo.getId(), extendedClientRegistrationInfo);
}
Map<Set<DomainInfo>, OAuth2ClientsDomainParams> domainParamsMap = new HashMap<>();
domainsByInfoId.forEach((clientRegistrationInfoId, domainInfos) -> {
domainParamsMap.computeIfAbsent(domainInfos,
key -> new OAuth2ClientsDomainParams(key, new HashSet<>())
)
.getClientRegistrations()
.add(toClientRegistrationDto(infoById.get(clientRegistrationInfoId)));
});
boolean enabled = extendedOAuth2ClientRegistrationInfos.stream()
.map(OAuth2ClientRegistrationInfo::isEnabled)
.findFirst().orElse(false);
return new OAuth2ClientsParams(enabled, new HashSet<>(domainParamsMap.values()));
}
public static ClientRegistrationDto toClientRegistrationDto(OAuth2ClientRegistrationInfo oAuth2ClientRegistrationInfo) {
return ClientRegistrationDto.builder()
.mapperConfig(oAuth2ClientRegistrationInfo.getMapperConfig())
.clientId(oAuth2ClientRegistrationInfo.getClientId())
.clientSecret(oAuth2ClientRegistrationInfo.getClientSecret())
.authorizationUri(oAuth2ClientRegistrationInfo.getAuthorizationUri())
.accessTokenUri(oAuth2ClientRegistrationInfo.getAccessTokenUri())
.scope(oAuth2ClientRegistrationInfo.getScope())
.userInfoUri(oAuth2ClientRegistrationInfo.getUserInfoUri())
.userNameAttributeName(oAuth2ClientRegistrationInfo.getUserNameAttributeName())
.jwkSetUri(oAuth2ClientRegistrationInfo.getJwkSetUri())
.clientAuthenticationMethod(oAuth2ClientRegistrationInfo.getClientAuthenticationMethod())
.loginButtonLabel(oAuth2ClientRegistrationInfo.getLoginButtonLabel())
.loginButtonIcon(oAuth2ClientRegistrationInfo.getLoginButtonIcon())
.additionalInfo(oAuth2ClientRegistrationInfo.getAdditionalInfo())
.build();
}
public static OAuth2ClientRegistrationInfo toClientRegistrationInfo(boolean enabled, ClientRegistrationDto clientRegistrationDto) {
OAuth2ClientRegistrationInfo clientRegistrationInfo = new OAuth2ClientRegistrationInfo();
clientRegistrationInfo.setEnabled(enabled);
clientRegistrationInfo.setMapperConfig(clientRegistrationDto.getMapperConfig());
clientRegistrationInfo.setClientId(clientRegistrationDto.getClientId());
clientRegistrationInfo.setClientSecret(clientRegistrationDto.getClientSecret());
clientRegistrationInfo.setAuthorizationUri(clientRegistrationDto.getAuthorizationUri());
clientRegistrationInfo.setAccessTokenUri(clientRegistrationDto.getAccessTokenUri());
clientRegistrationInfo.setScope(clientRegistrationDto.getScope());
clientRegistrationInfo.setUserInfoUri(clientRegistrationDto.getUserInfoUri());
clientRegistrationInfo.setUserNameAttributeName(clientRegistrationDto.getUserNameAttributeName());
clientRegistrationInfo.setJwkSetUri(clientRegistrationDto.getJwkSetUri());
clientRegistrationInfo.setClientAuthenticationMethod(clientRegistrationDto.getClientAuthenticationMethod());
clientRegistrationInfo.setLoginButtonLabel(clientRegistrationDto.getLoginButtonLabel());
clientRegistrationInfo.setLoginButtonIcon(clientRegistrationDto.getLoginButtonIcon());
clientRegistrationInfo.setAdditionalInfo(clientRegistrationDto.getAdditionalInfo());
return clientRegistrationInfo;
}
public static OAuth2ClientRegistration toClientRegistration(OAuth2ClientRegistrationInfoId clientRegistrationInfoId, SchemeType domainScheme, String domainName) {
OAuth2ClientRegistration clientRegistration = new OAuth2ClientRegistration();
clientRegistration.setClientRegistrationId(clientRegistrationInfoId);
clientRegistration.setDomainName(domainName);
clientRegistration.setDomainScheme(domainScheme);
return clientRegistration;
}
}

48
dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java

@ -0,0 +1,48 @@
/**
* 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.dao.sql.oauth2;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration;
import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationEntity;
import org.thingsboard.server.dao.oauth2.OAuth2ClientRegistrationDao;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
import java.util.UUID;
@Component
@RequiredArgsConstructor
public class JpaOAuth2ClientRegistrationDao extends JpaAbstractDao<OAuth2ClientRegistrationEntity, OAuth2ClientRegistration> implements OAuth2ClientRegistrationDao {
private final OAuth2ClientRegistrationRepository repository;
@Override
protected Class<OAuth2ClientRegistrationEntity> getEntityClass() {
return OAuth2ClientRegistrationEntity.class;
}
@Override
protected CrudRepository<OAuth2ClientRegistrationEntity, UUID> getCrudRepository() {
return repository;
}
@Override
public void deleteAll() {
repository.deleteAll();
}
}

76
dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationInfoDao.java

@ -0,0 +1,76 @@
/**
* 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.dao.sql.oauth2;
import lombok.RequiredArgsConstructor;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.oauth2.ExtendedOAuth2ClientRegistrationInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
import org.thingsboard.server.common.data.oauth2.SchemeType;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationInfoEntity;
import org.thingsboard.server.dao.oauth2.OAuth2ClientRegistrationInfoDao;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@Component
@RequiredArgsConstructor
public class JpaOAuth2ClientRegistrationInfoDao extends JpaAbstractDao<OAuth2ClientRegistrationInfoEntity, OAuth2ClientRegistrationInfo> implements OAuth2ClientRegistrationInfoDao {
private final OAuth2ClientRegistrationInfoRepository repository;
@Override
protected Class<OAuth2ClientRegistrationInfoEntity> getEntityClass() {
return OAuth2ClientRegistrationInfoEntity.class;
}
@Override
protected CrudRepository<OAuth2ClientRegistrationInfoEntity, UUID> getCrudRepository() {
return repository;
}
@Override
public List<OAuth2ClientRegistrationInfo> findAll() {
Iterable<OAuth2ClientRegistrationInfoEntity> entities = repository.findAll();
List<OAuth2ClientRegistrationInfo> result = new ArrayList<>();
entities.forEach(entity -> {
result.add(DaoUtil.getData(entity));
});
return result;
}
@Override
public List<ExtendedOAuth2ClientRegistrationInfo> findAllExtended() {
return repository.findAllExtended().stream()
.map(DaoUtil::getData)
.collect(Collectors.toList());
}
@Override
public List<OAuth2ClientRegistrationInfo> findByDomainSchemesAndDomainName(List<SchemeType> domainSchemes, String domainName) {
List<OAuth2ClientRegistrationInfoEntity> entities = repository.findAllByDomainSchemesAndName(domainSchemes, domainName);
return entities.stream().map(DaoUtil::getData).collect(Collectors.toList());
}
@Override
public void deleteAll() {
repository.deleteAll();
}
}

53
dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationTemplateDao.java

@ -0,0 +1,53 @@
/**
* 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.dao.sql.oauth2;
import lombok.RequiredArgsConstructor;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationTemplateEntity;
import org.thingsboard.server.dao.oauth2.OAuth2ClientRegistrationTemplateDao;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Component
@RequiredArgsConstructor
public class JpaOAuth2ClientRegistrationTemplateDao extends JpaAbstractDao<OAuth2ClientRegistrationTemplateEntity, OAuth2ClientRegistrationTemplate> implements OAuth2ClientRegistrationTemplateDao {
private final OAuth2ClientRegistrationTemplateRepository repository;
@Override
protected Class<OAuth2ClientRegistrationTemplateEntity> getEntityClass() {
return OAuth2ClientRegistrationTemplateEntity.class;
}
@Override
protected CrudRepository<OAuth2ClientRegistrationTemplateEntity, UUID> getCrudRepository() {
return repository;
}
@Override
public List<OAuth2ClientRegistrationTemplate> findAll() {
Iterable<OAuth2ClientRegistrationTemplateEntity> entities = repository.findAll();
List<OAuth2ClientRegistrationTemplate> result = new ArrayList<>();
entities.forEach(entity -> result.add(DaoUtil.getData(entity)));
return result;
}
}

41
dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationInfoRepository.java

@ -0,0 +1,41 @@
/**
* 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.dao.sql.oauth2;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.common.data.oauth2.SchemeType;
import org.thingsboard.server.dao.model.sql.ExtendedOAuth2ClientRegistrationInfoEntity;
import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationInfoEntity;
import java.util.List;
import java.util.UUID;
public interface OAuth2ClientRegistrationInfoRepository extends CrudRepository<OAuth2ClientRegistrationInfoEntity, UUID> {
@Query("SELECT new OAuth2ClientRegistrationInfoEntity(cr_info) " +
"FROM OAuth2ClientRegistrationInfoEntity cr_info " +
"LEFT JOIN OAuth2ClientRegistrationEntity cr on cr_info.id = cr.clientRegistrationInfoId " +
"WHERE cr.domainName = :domainName " +
"AND cr.domainScheme IN (:domainSchemes)")
List<OAuth2ClientRegistrationInfoEntity> findAllByDomainSchemesAndName(@Param("domainSchemes") List<SchemeType> domainSchemes,
@Param("domainName") String domainName);
@Query("SELECT new org.thingsboard.server.dao.model.sql.ExtendedOAuth2ClientRegistrationInfoEntity(cr_info, cr.domainName, cr.domainScheme) " +
"FROM OAuth2ClientRegistrationInfoEntity cr_info " +
"LEFT JOIN OAuth2ClientRegistrationEntity cr on cr_info.id = cr.clientRegistrationInfoId ")
List<ExtendedOAuth2ClientRegistrationInfoEntity> findAllExtended();
}

24
dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationRepository.java

@ -0,0 +1,24 @@
/**
* 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.dao.sql.oauth2;
import org.springframework.data.repository.CrudRepository;
import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationEntity;
import java.util.UUID;
public interface OAuth2ClientRegistrationRepository extends CrudRepository<OAuth2ClientRegistrationEntity, UUID> {
}

24
dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationTemplateRepository.java

@ -0,0 +1,24 @@
/**
* 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.dao.sql.oauth2;
import org.springframework.data.repository.CrudRepository;
import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationTemplateEntity;
import java.util.UUID;
public interface OAuth2ClientRegistrationTemplateRepository extends CrudRepository<OAuth2ClientRegistrationTemplateEntity, UUID> {
}

70
dao/src/main/resources/sql/schema-entities-hsql.sql

@ -331,3 +331,73 @@ CREATE TABLE IF NOT EXISTS ts_kv_dictionary (
key_id int GENERATED BY DEFAULT AS IDENTITY(start with 0 increment by 1) UNIQUE,
CONSTRAINT ts_key_id_pkey PRIMARY KEY (key)
);
CREATE TABLE IF NOT EXISTS oauth2_client_registration_info (
id uuid NOT NULL CONSTRAINT oauth2_client_registration_info_pkey PRIMARY KEY,
enabled boolean,
created_time bigint NOT NULL,
additional_info varchar,
client_id varchar(255),
client_secret varchar(255),
authorization_uri varchar(255),
token_uri varchar(255),
scope varchar(255),
user_info_uri varchar(255),
user_name_attribute_name varchar(255),
jwk_set_uri varchar(255),
client_authentication_method varchar(255),
login_button_label varchar(255),
login_button_icon varchar(255),
allow_user_creation boolean,
activate_user boolean,
type varchar(31),
basic_email_attribute_key varchar(31),
basic_first_name_attribute_key varchar(31),
basic_last_name_attribute_key varchar(31),
basic_tenant_name_strategy varchar(31),
basic_tenant_name_pattern varchar(255),
basic_customer_name_pattern varchar(255),
basic_default_dashboard_name varchar(255),
basic_always_full_screen boolean,
custom_url varchar(255),
custom_username varchar(255),
custom_password varchar(255),
custom_send_token boolean
);
CREATE TABLE IF NOT EXISTS oauth2_client_registration (
id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY,
created_time bigint NOT NULL,
domain_name varchar(255),
domain_scheme varchar(31),
client_registration_info_id uuid
);
CREATE TABLE IF NOT EXISTS oauth2_client_registration_template (
id uuid NOT NULL CONSTRAINT oauth2_client_registration_template_pkey PRIMARY KEY,
created_time bigint NOT NULL,
additional_info varchar,
provider_id varchar(255),
authorization_uri varchar(255),
token_uri varchar(255),
scope varchar(255),
user_info_uri varchar(255),
user_name_attribute_name varchar(255),
jwk_set_uri varchar(255),
client_authentication_method varchar(255),
type varchar(31),
basic_email_attribute_key varchar(31),
basic_first_name_attribute_key varchar(31),
basic_last_name_attribute_key varchar(31),
basic_tenant_name_strategy varchar(31),
basic_tenant_name_pattern varchar(255),
basic_customer_name_pattern varchar(255),
basic_default_dashboard_name varchar(255),
basic_always_full_screen boolean,
comment varchar,
login_button_icon varchar(255),
login_button_label varchar(255),
help_link varchar(255),
CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id)
);

69
dao/src/main/resources/sql/schema-entities.sql

@ -359,6 +359,75 @@ CREATE TABLE IF NOT EXISTS ts_kv_dictionary
CONSTRAINT ts_key_id_pkey PRIMARY KEY (key)
);
CREATE TABLE IF NOT EXISTS oauth2_client_registration_info (
id uuid NOT NULL CONSTRAINT oauth2_client_registration_info_pkey PRIMARY KEY,
enabled boolean,
created_time bigint NOT NULL,
additional_info varchar,
client_id varchar(255),
client_secret varchar(255),
authorization_uri varchar(255),
token_uri varchar(255),
scope varchar(255),
user_info_uri varchar(255),
user_name_attribute_name varchar(255),
jwk_set_uri varchar(255),
client_authentication_method varchar(255),
login_button_label varchar(255),
login_button_icon varchar(255),
allow_user_creation boolean,
activate_user boolean,
type varchar(31),
basic_email_attribute_key varchar(31),
basic_first_name_attribute_key varchar(31),
basic_last_name_attribute_key varchar(31),
basic_tenant_name_strategy varchar(31),
basic_tenant_name_pattern varchar(255),
basic_customer_name_pattern varchar(255),
basic_default_dashboard_name varchar(255),
basic_always_full_screen boolean,
custom_url varchar(255),
custom_username varchar(255),
custom_password varchar(255),
custom_send_token boolean
);
CREATE TABLE IF NOT EXISTS oauth2_client_registration (
id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY,
created_time bigint NOT NULL,
domain_name varchar(255),
domain_scheme varchar(31),
client_registration_info_id uuid
);
CREATE TABLE IF NOT EXISTS oauth2_client_registration_template (
id uuid NOT NULL CONSTRAINT oauth2_client_registration_template_pkey PRIMARY KEY,
created_time bigint NOT NULL,
additional_info varchar,
provider_id varchar(255),
authorization_uri varchar(255),
token_uri varchar(255),
scope varchar(255),
user_info_uri varchar(255),
user_name_attribute_name varchar(255),
jwk_set_uri varchar(255),
client_authentication_method varchar(255),
type varchar(31),
basic_email_attribute_key varchar(31),
basic_first_name_attribute_key varchar(31),
basic_last_name_attribute_key varchar(31),
basic_tenant_name_strategy varchar(31),
basic_tenant_name_pattern varchar(255),
basic_customer_name_pattern varchar(255),
basic_default_dashboard_name varchar(255),
basic_always_full_screen boolean,
comment varchar,
login_button_icon varchar(255),
login_button_label varchar(255),
help_link varchar(255),
CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id)
);
CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl(IN ttl bigint, IN debug_ttl bigint, INOUT deleted bigint)
LANGUAGE plpgsql AS
$$

133
dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java

@ -0,0 +1,133 @@
/**
* 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.dao.service;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.MapperType;
import org.thingsboard.server.common.data.oauth2.OAuth2BasicMapperConfig;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
import java.util.Arrays;
import java.util.UUID;
public class BaseOAuth2ConfigTemplateServiceTest extends AbstractServiceTest {
@Autowired
protected OAuth2ConfigTemplateService oAuth2ConfigTemplateService;
@Before
public void beforeRun() throws Exception {
Assert.assertTrue(oAuth2ConfigTemplateService.findAllClientRegistrationTemplates().isEmpty());
}
@After
public void after() throws Exception {
oAuth2ConfigTemplateService.findAllClientRegistrationTemplates().forEach(clientRegistrationTemplate -> {
oAuth2ConfigTemplateService.deleteClientRegistrationTemplateById(clientRegistrationTemplate.getId());
});
Assert.assertTrue(oAuth2ConfigTemplateService.findAllClientRegistrationTemplates().isEmpty());
}
@Test(expected = DataValidationException.class)
public void testSaveDuplicateProviderId() {
OAuth2ClientRegistrationTemplate first = validClientRegistrationTemplate("providerId");
OAuth2ClientRegistrationTemplate second = validClientRegistrationTemplate("providerId");
oAuth2ConfigTemplateService.saveClientRegistrationTemplate(first);
oAuth2ConfigTemplateService.saveClientRegistrationTemplate(second);
}
@Test
public void testCreateNewTemplate() {
OAuth2ClientRegistrationTemplate clientRegistrationTemplate = validClientRegistrationTemplate(UUID.randomUUID().toString());
OAuth2ClientRegistrationTemplate savedClientRegistrationTemplate = oAuth2ConfigTemplateService.saveClientRegistrationTemplate(clientRegistrationTemplate);
Assert.assertNotNull(savedClientRegistrationTemplate);
Assert.assertNotNull(savedClientRegistrationTemplate.getId());
clientRegistrationTemplate.setId(savedClientRegistrationTemplate.getId());
clientRegistrationTemplate.setCreatedTime(savedClientRegistrationTemplate.getCreatedTime());
Assert.assertEquals(clientRegistrationTemplate, savedClientRegistrationTemplate);
}
@Test
public void testFindTemplate() {
OAuth2ClientRegistrationTemplate clientRegistrationTemplate = validClientRegistrationTemplate(UUID.randomUUID().toString());
OAuth2ClientRegistrationTemplate savedClientRegistrationTemplate = oAuth2ConfigTemplateService.saveClientRegistrationTemplate(clientRegistrationTemplate);
OAuth2ClientRegistrationTemplate foundClientRegistrationTemplate = oAuth2ConfigTemplateService.findClientRegistrationTemplateById(savedClientRegistrationTemplate.getId());
Assert.assertEquals(savedClientRegistrationTemplate, foundClientRegistrationTemplate);
}
@Test
public void testFindAll() {
oAuth2ConfigTemplateService.saveClientRegistrationTemplate(validClientRegistrationTemplate(UUID.randomUUID().toString()));
oAuth2ConfigTemplateService.saveClientRegistrationTemplate(validClientRegistrationTemplate(UUID.randomUUID().toString()));
Assert.assertEquals(2, oAuth2ConfigTemplateService.findAllClientRegistrationTemplates().size());
}
@Test
public void testDeleteTemplate() {
oAuth2ConfigTemplateService.saveClientRegistrationTemplate(validClientRegistrationTemplate(UUID.randomUUID().toString()));
oAuth2ConfigTemplateService.saveClientRegistrationTemplate(validClientRegistrationTemplate(UUID.randomUUID().toString()));
OAuth2ClientRegistrationTemplate saved = oAuth2ConfigTemplateService.saveClientRegistrationTemplate(validClientRegistrationTemplate(UUID.randomUUID().toString()));
Assert.assertEquals(3, oAuth2ConfigTemplateService.findAllClientRegistrationTemplates().size());
Assert.assertNotNull(oAuth2ConfigTemplateService.findClientRegistrationTemplateById(saved.getId()));
oAuth2ConfigTemplateService.deleteClientRegistrationTemplateById(saved.getId());
Assert.assertEquals(2, oAuth2ConfigTemplateService.findAllClientRegistrationTemplates().size());
Assert.assertNull(oAuth2ConfigTemplateService.findClientRegistrationTemplateById(saved.getId()));
}
private OAuth2ClientRegistrationTemplate validClientRegistrationTemplate(String providerId) {
OAuth2ClientRegistrationTemplate clientRegistrationTemplate = new OAuth2ClientRegistrationTemplate();
clientRegistrationTemplate.setProviderId(providerId);
clientRegistrationTemplate.setAdditionalInfo(mapper.createObjectNode().put(UUID.randomUUID().toString(), UUID.randomUUID().toString()));
clientRegistrationTemplate.setMapperType(MapperType.BASIC);
clientRegistrationTemplate.setBasic(
OAuth2BasicMapperConfig.builder()
.firstNameAttributeKey("firstName")
.lastNameAttributeKey("lastName")
.emailAttributeKey("email")
.tenantNamePattern("tenant")
.defaultDashboardName("Test")
.alwaysFullScreen(true)
.build()
);
clientRegistrationTemplate.setAuthorizationUri("authorizationUri");
clientRegistrationTemplate.setAccessTokenUri("tokenUri");
clientRegistrationTemplate.setScope(Arrays.asList("scope1", "scope2"));
clientRegistrationTemplate.setUserInfoUri("userInfoUri");
clientRegistrationTemplate.setUserNameAttributeName("userNameAttributeName");
clientRegistrationTemplate.setJwkSetUri("jwkSetUri");
clientRegistrationTemplate.setClientAuthenticationMethod("clientAuthenticationMethod");
clientRegistrationTemplate.setComment("comment");
clientRegistrationTemplate.setLoginButtonIcon("icon");
clientRegistrationTemplate.setLoginButtonLabel("label");
clientRegistrationTemplate.setHelpLink("helpLink");
return clientRegistrationTemplate;
}
}

524
dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java

@ -0,0 +1,524 @@
/**
* 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.dao.service;
import com.google.common.collect.Sets;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.oauth2.*;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.oauth2.OAuth2Service;
import java.util.*;
import java.util.stream.Collectors;
public class BaseOAuth2ServiceTest extends AbstractServiceTest {
private static final OAuth2ClientsParams EMPTY_PARAMS = new OAuth2ClientsParams(false, new HashSet<>());
@Autowired
protected OAuth2Service oAuth2Service;
@Before
public void beforeRun() {
Assert.assertTrue(oAuth2Service.findAllClientRegistrationInfos().isEmpty());
}
@After
public void after() {
oAuth2Service.saveOAuth2Params(EMPTY_PARAMS);
Assert.assertTrue(oAuth2Service.findAllClientRegistrationInfos().isEmpty());
Assert.assertTrue(oAuth2Service.findOAuth2Params().getDomainsParams().isEmpty());
}
@Test(expected = DataValidationException.class)
public void testSaveHttpAndMixedDomainsTogether() {
OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet(
OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet(
DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(),
DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
))
.clientRegistrations(Sets.newHashSet(
validClientRegistrationDto(),
validClientRegistrationDto(),
validClientRegistrationDto()
))
.build()
));
oAuth2Service.saveOAuth2Params(clientsParams);
}
@Test(expected = DataValidationException.class)
public void testSaveHttpsAndMixedDomainsTogether() {
OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet(
OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet(
DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build(),
DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(),
DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
))
.clientRegistrations(Sets.newHashSet(
validClientRegistrationDto(),
validClientRegistrationDto(),
validClientRegistrationDto()
))
.build()
));
oAuth2Service.saveOAuth2Params(clientsParams);
}
@Test
public void testCreateAndFindParams() {
OAuth2ClientsParams clientsParams = createDefaultClientsParams();
oAuth2Service.saveOAuth2Params(clientsParams);
OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params();
Assert.assertNotNull(foundClientsParams);
// TODO ask if it's safe to check equality on AdditionalProperties
Assert.assertEquals(clientsParams, foundClientsParams);
}
@Test
public void testDisableParams() {
OAuth2ClientsParams clientsParams = createDefaultClientsParams();
clientsParams.setEnabled(true);
oAuth2Service.saveOAuth2Params(clientsParams);
OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params();
Assert.assertNotNull(foundClientsParams);
Assert.assertEquals(clientsParams, foundClientsParams);
clientsParams.setEnabled(false);
oAuth2Service.saveOAuth2Params(clientsParams);
OAuth2ClientsParams foundDisabledClientsParams = oAuth2Service.findOAuth2Params();
Assert.assertEquals(clientsParams, foundDisabledClientsParams);
}
@Test
public void testClearDomainParams() {
OAuth2ClientsParams clientsParams = createDefaultClientsParams();
oAuth2Service.saveOAuth2Params(clientsParams);
OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params();
Assert.assertNotNull(foundClientsParams);
Assert.assertEquals(clientsParams, foundClientsParams);
oAuth2Service.saveOAuth2Params(EMPTY_PARAMS);
OAuth2ClientsParams foundAfterClearClientsParams = oAuth2Service.findOAuth2Params();
Assert.assertNotNull(foundAfterClearClientsParams);
Assert.assertEquals(EMPTY_PARAMS, foundAfterClearClientsParams);
}
@Test
public void testUpdateClientsParams() {
OAuth2ClientsParams clientsParams = createDefaultClientsParams();
oAuth2Service.saveOAuth2Params(clientsParams);
OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params();
Assert.assertNotNull(foundClientsParams);
Assert.assertEquals(clientsParams, foundClientsParams);
OAuth2ClientsParams newClientsParams = new OAuth2ClientsParams(true, Sets.newHashSet(
OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet(
DomainInfo.builder().name("another-domain").scheme(SchemeType.HTTPS).build()
))
.clientRegistrations(Sets.newHashSet(
validClientRegistrationDto()
))
.build(),
OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet(
DomainInfo.builder().name("test-domain").scheme(SchemeType.MIXED).build()
))
.clientRegistrations(Sets.newHashSet(
validClientRegistrationDto()
))
.build()
));
oAuth2Service.saveOAuth2Params(newClientsParams);
OAuth2ClientsParams foundAfterUpdateClientsParams = oAuth2Service.findOAuth2Params();
Assert.assertNotNull(foundAfterUpdateClientsParams);
Assert.assertEquals(newClientsParams, foundAfterUpdateClientsParams);
}
@Test
public void testGetOAuth2Clients() {
Set<ClientRegistrationDto> firstGroup = Sets.newHashSet(
validClientRegistrationDto(),
validClientRegistrationDto(),
validClientRegistrationDto(),
validClientRegistrationDto()
);
Set<ClientRegistrationDto> secondGroup = Sets.newHashSet(
validClientRegistrationDto(),
validClientRegistrationDto()
);
Set<ClientRegistrationDto> thirdGroup = Sets.newHashSet(
validClientRegistrationDto()
);
OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet(
OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet(
DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
))
.clientRegistrations(firstGroup)
.build(),
OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet(
DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(),
DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build()
))
.clientRegistrations(secondGroup)
.build(),
OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet(
DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(),
DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build()
))
.clientRegistrations(thirdGroup)
.build()
));
oAuth2Service.saveOAuth2Params(clientsParams);
OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params();
Assert.assertNotNull(foundClientsParams);
Assert.assertEquals(clientsParams, foundClientsParams);
List<OAuth2ClientInfo> firstGroupClientInfos = firstGroup.stream()
.map(clientRegistrationDto -> new OAuth2ClientInfo(
clientRegistrationDto.getLoginButtonLabel(), clientRegistrationDto.getLoginButtonIcon(), null))
.collect(Collectors.toList());
List<OAuth2ClientInfo> secondGroupClientInfos = secondGroup.stream()
.map(clientRegistrationDto -> new OAuth2ClientInfo(
clientRegistrationDto.getLoginButtonLabel(), clientRegistrationDto.getLoginButtonIcon(), null))
.collect(Collectors.toList());
List<OAuth2ClientInfo> thirdGroupClientInfos = thirdGroup.stream()
.map(clientRegistrationDto -> new OAuth2ClientInfo(
clientRegistrationDto.getLoginButtonLabel(), clientRegistrationDto.getLoginButtonIcon(), null))
.collect(Collectors.toList());
List<OAuth2ClientInfo> nonExistentDomainClients = oAuth2Service.getOAuth2Clients("http", "non-existent-domain");
Assert.assertTrue(nonExistentDomainClients.isEmpty());
List<OAuth2ClientInfo> firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain");
Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpClients.size());
firstGroupClientInfos.forEach(firstGroupClientInfo -> {
Assert.assertTrue(
firstDomainHttpClients.stream().anyMatch(clientInfo ->
clientInfo.getIcon().equals(firstGroupClientInfo.getIcon())
&& clientInfo.getName().equals(firstGroupClientInfo.getName()))
);
});
List<OAuth2ClientInfo> firstDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "first-domain");
Assert.assertTrue(firstDomainHttpsClients.isEmpty());
List<OAuth2ClientInfo> fourthDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "fourth-domain");
Assert.assertEquals(secondGroupClientInfos.size(), fourthDomainHttpClients.size());
secondGroupClientInfos.forEach(secondGroupClientInfo -> {
Assert.assertTrue(
fourthDomainHttpClients.stream().anyMatch(clientInfo ->
clientInfo.getIcon().equals(secondGroupClientInfo.getIcon())
&& clientInfo.getName().equals(secondGroupClientInfo.getName()))
);
});
List<OAuth2ClientInfo> fourthDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "fourth-domain");
Assert.assertEquals(secondGroupClientInfos.size(), fourthDomainHttpsClients.size());
secondGroupClientInfos.forEach(secondGroupClientInfo -> {
Assert.assertTrue(
fourthDomainHttpsClients.stream().anyMatch(clientInfo ->
clientInfo.getIcon().equals(secondGroupClientInfo.getIcon())
&& clientInfo.getName().equals(secondGroupClientInfo.getName()))
);
});
List<OAuth2ClientInfo> secondDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "second-domain");
Assert.assertEquals(firstGroupClientInfos.size() + secondGroupClientInfos.size(), secondDomainHttpClients.size());
firstGroupClientInfos.forEach(firstGroupClientInfo -> {
Assert.assertTrue(
secondDomainHttpClients.stream().anyMatch(clientInfo ->
clientInfo.getIcon().equals(firstGroupClientInfo.getIcon())
&& clientInfo.getName().equals(firstGroupClientInfo.getName()))
);
});
secondGroupClientInfos.forEach(secondGroupClientInfo -> {
Assert.assertTrue(
secondDomainHttpClients.stream().anyMatch(clientInfo ->
clientInfo.getIcon().equals(secondGroupClientInfo.getIcon())
&& clientInfo.getName().equals(secondGroupClientInfo.getName()))
);
});
List<OAuth2ClientInfo> secondDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "second-domain");
Assert.assertEquals(firstGroupClientInfos.size() + thirdGroupClientInfos.size(), secondDomainHttpsClients.size());
firstGroupClientInfos.forEach(firstGroupClientInfo -> {
Assert.assertTrue(
secondDomainHttpsClients.stream().anyMatch(clientInfo ->
clientInfo.getIcon().equals(firstGroupClientInfo.getIcon())
&& clientInfo.getName().equals(firstGroupClientInfo.getName()))
);
});
thirdGroupClientInfos.forEach(thirdGroupClientInfo -> {
Assert.assertTrue(
secondDomainHttpsClients.stream().anyMatch(clientInfo ->
clientInfo.getIcon().equals(thirdGroupClientInfo.getIcon())
&& clientInfo.getName().equals(thirdGroupClientInfo.getName()))
);
});
}
@Test
public void testGetOAuth2ClientsForHttpAndHttps() {
Set<ClientRegistrationDto> firstGroup = Sets.newHashSet(
validClientRegistrationDto(),
validClientRegistrationDto(),
validClientRegistrationDto(),
validClientRegistrationDto()
);
OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet(
OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet(
DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build()
))
.clientRegistrations(firstGroup)
.build()
));
oAuth2Service.saveOAuth2Params(clientsParams);
OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params();
Assert.assertNotNull(foundClientsParams);
Assert.assertEquals(clientsParams, foundClientsParams);
List<OAuth2ClientInfo> firstGroupClientInfos = firstGroup.stream()
.map(clientRegistrationDto -> new OAuth2ClientInfo(
clientRegistrationDto.getLoginButtonLabel(), clientRegistrationDto.getLoginButtonIcon(), null))
.collect(Collectors.toList());
List<OAuth2ClientInfo> firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain");
Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpClients.size());
firstGroupClientInfos.forEach(firstGroupClientInfo -> {
Assert.assertTrue(
firstDomainHttpClients.stream().anyMatch(clientInfo ->
clientInfo.getIcon().equals(firstGroupClientInfo.getIcon())
&& clientInfo.getName().equals(firstGroupClientInfo.getName()))
);
});
List<OAuth2ClientInfo> firstDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "first-domain");
Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpsClients.size());
firstGroupClientInfos.forEach(firstGroupClientInfo -> {
Assert.assertTrue(
firstDomainHttpsClients.stream().anyMatch(clientInfo ->
clientInfo.getIcon().equals(firstGroupClientInfo.getIcon())
&& clientInfo.getName().equals(firstGroupClientInfo.getName()))
);
});
}
@Test
public void testGetDisabledOAuth2Clients() {
OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet(
OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet(
DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
))
.clientRegistrations(Sets.newHashSet(
validClientRegistrationDto(),
validClientRegistrationDto(),
validClientRegistrationDto()
))
.build(),
OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet(
DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(),
DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build()
))
.clientRegistrations(Sets.newHashSet(
validClientRegistrationDto(),
validClientRegistrationDto()
))
.build()
));
oAuth2Service.saveOAuth2Params(clientsParams);
List<OAuth2ClientInfo> secondDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "second-domain");
Assert.assertEquals(5, secondDomainHttpClients.size());
clientsParams.setEnabled(false);
oAuth2Service.saveOAuth2Params(clientsParams);
List<OAuth2ClientInfo> secondDomainHttpDisabledClients = oAuth2Service.getOAuth2Clients("http", "second-domain");
Assert.assertEquals(0, secondDomainHttpDisabledClients.size());
}
@Test
public void testFindAllClientRegistrationInfos() {
OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet(
OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet(
DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
))
.clientRegistrations(Sets.newHashSet(
validClientRegistrationDto(),
validClientRegistrationDto(),
validClientRegistrationDto()
))
.build(),
OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet(
DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(),
DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build()
))
.clientRegistrations(Sets.newHashSet(
validClientRegistrationDto(),
validClientRegistrationDto()
))
.build(),
OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet(
DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(),
DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build()
))
.clientRegistrations(Sets.newHashSet(
validClientRegistrationDto()
))
.build()
));
oAuth2Service.saveOAuth2Params(clientsParams);
List<OAuth2ClientRegistrationInfo> foundClientRegistrationInfos = oAuth2Service.findAllClientRegistrationInfos();
Assert.assertEquals(6, foundClientRegistrationInfos.size());
clientsParams.getDomainsParams().stream()
.flatMap(domainParams -> domainParams.getClientRegistrations().stream())
.forEach(clientRegistrationDto ->
Assert.assertTrue(
foundClientRegistrationInfos.stream()
.anyMatch(clientRegistrationInfo -> clientRegistrationInfo.getClientId().equals(clientRegistrationDto.getClientId()))
)
);
}
@Test
public void testFindClientRegistrationById() {
OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet(
OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet(
DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
))
.clientRegistrations(Sets.newHashSet(
validClientRegistrationDto(),
validClientRegistrationDto(),
validClientRegistrationDto()
))
.build(),
OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet(
DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(),
DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build()
))
.clientRegistrations(Sets.newHashSet(
validClientRegistrationDto(),
validClientRegistrationDto()
))
.build(),
OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet(
DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(),
DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build()
))
.clientRegistrations(Sets.newHashSet(
validClientRegistrationDto()
))
.build()
));
oAuth2Service.saveOAuth2Params(clientsParams);
List<OAuth2ClientRegistrationInfo> clientRegistrationInfos = oAuth2Service.findAllClientRegistrationInfos();
clientRegistrationInfos.forEach(clientRegistrationInfo -> {
OAuth2ClientRegistrationInfo foundClientRegistrationInfo = oAuth2Service.findClientRegistrationInfo(clientRegistrationInfo.getUuidId());
Assert.assertEquals(clientRegistrationInfo, foundClientRegistrationInfo);
});
}
private OAuth2ClientsParams createDefaultClientsParams() {
return new OAuth2ClientsParams(true, Sets.newHashSet(
OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet(
DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
))
.clientRegistrations(Sets.newHashSet(
validClientRegistrationDto(),
validClientRegistrationDto(),
validClientRegistrationDto(),
validClientRegistrationDto()
))
.build(),
OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet(
DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build()
))
.clientRegistrations(Sets.newHashSet(
validClientRegistrationDto(),
validClientRegistrationDto()
))
.build()
));
}
private ClientRegistrationDto validClientRegistrationDto() {
return ClientRegistrationDto.builder()
.clientId(UUID.randomUUID().toString())
.clientSecret(UUID.randomUUID().toString())
.authorizationUri(UUID.randomUUID().toString())
.accessTokenUri(UUID.randomUUID().toString())
.scope(Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString()))
.userInfoUri(UUID.randomUUID().toString())
.userNameAttributeName(UUID.randomUUID().toString())
.jwkSetUri(UUID.randomUUID().toString())
.clientAuthenticationMethod(UUID.randomUUID().toString())
.loginButtonLabel(UUID.randomUUID().toString())
.loginButtonIcon(UUID.randomUUID().toString())
.additionalInfo(mapper.createObjectNode().put(UUID.randomUUID().toString(), UUID.randomUUID().toString()))
.mapperConfig(
OAuth2MapperConfig.builder()
.allowUserCreation(true)
.activateUser(true)
.type(MapperType.CUSTOM)
.custom(
OAuth2CustomMapperConfig.builder()
.url(UUID.randomUUID().toString())
.build()
)
.build()
)
.build();
}
}

23
dao/src/test/java/org/thingsboard/server/dao/service/sql/OAuth2ConfigTemplateServiceSqlTest.java

@ -0,0 +1,23 @@
/**
* 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.dao.service.sql;
import org.thingsboard.server.dao.service.BaseOAuth2ConfigTemplateServiceTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
@DaoSqlTest
public class OAuth2ConfigTemplateServiceSqlTest extends BaseOAuth2ConfigTemplateServiceTest {
}

23
dao/src/test/java/org/thingsboard/server/dao/service/sql/OAuth2ServiceSqlTest.java

@ -0,0 +1,23 @@
/**
* 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.dao.service.sql;
import org.thingsboard.server.dao.service.BaseOAuth2ServiceTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
@DaoSqlTest
public class OAuth2ServiceSqlTest extends BaseOAuth2ServiceTest {
}

3
dao/src/test/resources/sql/hsql/drop-all-tables.sql

@ -24,4 +24,7 @@ DROP TABLE IF EXISTS tenant_profile;
DROP TABLE IF EXISTS rule_node_state;
DROP TABLE IF EXISTS rule_node;
DROP TABLE IF EXISTS rule_chain;
DROP TABLE IF EXISTS oauth2_client_registration;
DROP TABLE IF EXISTS oauth2_client_registration_info;
DROP TABLE IF EXISTS oauth2_client_registration_template;
DROP FUNCTION IF EXISTS to_uuid;

3
dao/src/test/resources/sql/psql/drop-all-tables.sql

@ -25,3 +25,6 @@ DROP TABLE IF EXISTS rule_node_state;
DROP TABLE IF EXISTS rule_node;
DROP TABLE IF EXISTS rule_chain;
DROP TABLE IF EXISTS tb_schema_settings;
DROP TABLE IF EXISTS oauth2_client_registration;
DROP TABLE IF EXISTS oauth2_client_registration_info;
DROP TABLE IF EXISTS oauth2_client_registration_template;

3
dao/src/test/resources/sql/timescale/drop-all-tables.sql

@ -25,3 +25,6 @@ DROP TABLE IF EXISTS entity_view;
DROP TABLE IF EXISTS device_profile;
DROP TABLE IF EXISTS tenant_profile;
DROP TABLE IF EXISTS tb_schema_settings;
DROP TABLE IF EXISTS oauth2_client_registration;
DROP TABLE IF EXISTS oauth2_client_registration_info;
DROP TABLE IF EXISTS oauth2_client_registration_template;

2
ui-ngx/src/app/core/auth/auth.service.ts

@ -18,7 +18,7 @@ import { Injectable, NgZone } from '@angular/core';
import { JwtHelperService } from '@auth0/angular-jwt';
import { HttpClient } from '@angular/common/http';
import { forkJoin, Observable, of, throwError, ReplaySubject } from 'rxjs';
import { forkJoin, Observable, of, ReplaySubject, throwError } from 'rxjs';
import { catchError, map, mergeMap, tap } from 'rxjs/operators';
import { LoginRequest, LoginResponse, OAuth2Client, PublicLoginRequest } from '@shared/models/login.models';

22
ui-ngx/src/app/core/http/admin.service.ts

@ -18,7 +18,14 @@ import { Injectable } from '@angular/core';
import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { AdminSettings, MailServerSettings, SecuritySettings, UpdateMessage } from '@shared/models/settings.models';
import {
AdminSettings,
ClientProviderTemplated,
MailServerSettings,
OAuth2Settings,
SecuritySettings,
UpdateMessage
} from '@shared/models/settings.models';
@Injectable({
providedIn: 'root'
@ -53,6 +60,19 @@ export class AdminService {
defaultHttpOptionsFromConfig(config));
}
public getOAuth2Settings(config?: RequestConfig): Observable<OAuth2Settings> {
return this.http.get<OAuth2Settings>(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config));
}
public getOAuth2Template(config?: RequestConfig): Observable<Array<ClientProviderTemplated>> {
return this.http.get<Array<ClientProviderTemplated>>(`/api/oauth2/config/template`, defaultHttpOptionsFromConfig(config));
}
public saveOAuth2Settings(OAuth2Setting: OAuth2Settings, config?: RequestConfig): Observable<OAuth2Settings> {
return this.http.post<OAuth2Settings>('/api/oauth2/config', OAuth2Setting,
defaultHttpOptionsFromConfig(config));
}
public checkUpdates(config?: RequestConfig): Observable<UpdateMessage> {
return this.http.get<UpdateMessage>(`/api/admin/updates`, defaultHttpOptionsFromConfig(config));
}

9
ui-ngx/src/app/core/services/menu.service.ts

@ -108,7 +108,7 @@ export class MenuService {
name: 'admin.system-settings',
type: 'toggle',
path: '/settings',
height: '120px',
height: '160px',
icon: 'settings',
pages: [
{
@ -131,6 +131,13 @@ export class MenuService {
type: 'link',
path: '/settings/security-settings',
icon: 'security'
},
{
id: guid(),
name: 'admin.oauth2.oauth2',
type: 'link',
path: '/settings/oauth2',
icon: 'security'
}
]
}

14
ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts

@ -22,6 +22,7 @@ import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { Authority } from '@shared/models/authority.enum';
import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-settings.component';
import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component';
import { OAuth2SettingsComponent } from '@home/pages/admin/oauth2-settings.component';
const routes: Routes = [
{
@ -77,6 +78,19 @@ const routes: Routes = [
icon: 'security'
}
}
},
{
path: 'oauth2',
component: OAuth2SettingsComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
auth: [Authority.SYS_ADMIN],
title: 'admin.oauth2.oauth2',
breadcrumb: {
label: 'admin.oauth2.oauth2',
icon: 'security'
}
}
}
]
}

4
ui-ngx/src/app/modules/home/pages/admin/admin.module.ts

@ -23,13 +23,15 @@ import { MailServerComponent } from '@modules/home/pages/admin/mail-server.compo
import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-settings.component';
import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component';
import { HomeComponentsModule } from '@modules/home/components/home-components.module';
import { OAuth2SettingsComponent } from '@modules/home/pages/admin/oauth2-settings.component';
@NgModule({
declarations:
[
GeneralSettingsComponent,
MailServerComponent,
SecuritySettingsComponent
SecuritySettingsComponent,
OAuth2SettingsComponent
],
imports: [
CommonModule,

478
ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html

@ -0,0 +1,478 @@
<!--
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.
-->
<div>
<mat-card class="settings-card">
<mat-card-title>
<div fxLayout="row">
<span class="mat-headline" translate>admin.oauth2.oauth2</span>
<span fxFlex></span>
<div tb-help="oauth2Settings"></div>
</div>
</mat-card-title>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<mat-card-content style="padding-top: 16px;">
<form [formGroup]="oauth2SettingsForm" (ngSubmit)="save()">
<fieldset [disabled]="isLoading$ | async">
<mat-checkbox formControlName="enabled">
{{ 'admin.oauth2.enable' | translate }}
</mat-checkbox>
<section *ngIf="oauth2SettingsForm.get('enabled').value && !(isLoading$ | async)" style="margin-top: 1em;">
<ng-container formArrayName="domainsParams">
<div class="container">
<mat-accordion multi>
<ng-container *ngFor="let domain of domainsParams.controls; let i = index; trackBy: trackByParams">
<mat-expansion-panel [formGroupName]="i">
<mat-expansion-panel-header>
<mat-panel-title fxLayoutAlign="start center">
{{ domainListTittle(domain) }}
</mat-panel-title>
<mat-panel-description fxLayoutAlign="end center">
<button mat-icon-button
type="button"
(click)="deleteDomain($event, i)"
matTooltip="{{ 'action.delete' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<ng-container formArrayName="domainInfos">
<section *ngFor="let domainInfo of clientDomainInfos(domain).controls; let n = index; trackBy: trackByParams"
class="domains-list">
<div [formGroupName]="n" fxLayout="row" fxLayoutGap="8px">
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap="8px">
<div fxLayout="column" fxFlex.sm="60" fxFlex.gt-sm="50">
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap="8px">
<mat-form-field fxFlex="30" fxFlex.xs class="mat-block">
<mat-label translate>admin.oauth2.protocol</mat-label>
<mat-select formControlName="scheme">
<mat-option *ngFor="let protocol of protocols" [value]="protocol">
{{ domainSchemaTranslations.get(protocol) | translate | uppercase }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.domain-name</mat-label>
<input matInput formControlName="name" required>
<mat-error *ngIf="domainInfo.get('name').hasError('pattern')">
{{ 'admin.error-verification-url' | translate }}
</mat-error>
</mat-form-field>
</div>
<mat-error *ngIf="domainInfo.hasError('unique')">
{{ 'admin.domain-name-unique' | translate }}
</mat-error>
</div>
<div fxFlex fxLayout="column">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.redirect-uri-template</mat-label>
<input matInput [value]="redirectURI(domainInfo)" readonly>
<button mat-icon-button color="primary" matSuffix type="button"
ngxClipboard cbContent="{{ redirectURI(domainInfo) }}"
matTooltip="{{ 'admin.oauth2.copy-redirect-uri' | translate }}"
matTooltipPosition="above">
<mat-icon class="material-icons" svgIcon="mdi:clipboard-arrow-left"></mat-icon>
</button>
</mat-form-field>
<mat-form-field fxFlex *ngIf="domainInfo.get('scheme').value === 'MIXED'" class="mat-block">
<mat-label></mat-label>
<input matInput [value]="redirectURIMixed(domainInfo)" readonly>
<button mat-icon-button color="primary" matSuffix type="button"
ngxClipboard cbContent="{{ redirectURIMixed(domainInfo) }}"
matTooltip="{{ 'admin.oauth2.copy-redirect-uri' | translate }}"
matTooltipPosition="above">
<mat-icon class="material-icons" svgIcon="mdi:clipboard-arrow-left"></mat-icon>
</button>
</mat-form-field>
</div>
</div>
<div fxLayout="column" fxLayoutAlign="center start">
<button type="button" mat-icon-button color="primary"
(click)="removeDomain($event, domain, n)"
[disabled]="clientDomainInfos(domain).controls.length < 2"
matTooltip="{{ 'admin.oauth2.delete-domain' | translate }}"
matTooltipPosition="above">
<mat-icon>close</mat-icon>
</button>
</div>
</div>
</section>
<div fxLayout="row" fxLayoutAlign="end center" style="margin-bottom: 1.25em">
<button mat-button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="addDomainInfo(domain)"
type="button">
{{'admin.oauth2.add-domain' | translate}}
</button>
</div>
</ng-container>
<ng-container formArrayName="clientRegistrations">
<div class="container">
<mat-expansion-panel *ngFor="let registration of clientDomainProviders(domain).controls; let j = index; trackBy: trackByParams"
class="registration-card mat-elevation-z0">
<mat-expansion-panel-header>
<mat-panel-title fxLayoutAlign="start center">
{{ getProviderName(registration) }}
</mat-panel-title>
<mat-panel-description fxLayoutAlign="end center">
<button mat-icon-button
type="button"
[disabled]="clientDomainProviders(domain).controls.length < 2"
(click)="deleteProvider($event, domain, j)"
matTooltip="{{ 'admin.oauth2.delete-provider' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<section [formGroupName]="j">
<section formGroupName="additionalInfo" fxLayout="row">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.login-provider</mat-label>
<mat-select formControlName="providerName">
<mat-option *ngFor="let provider of templateProvider" [value]="provider">
{{ provider }}
</mat-option>
</mat-select>
</mat-form-field>
<div [tb-help]="getHelpLink(registration)" *ngIf="getProviderName(registration) !== 'Custom'"></div>
</section>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.client-id</mat-label>
<input matInput formControlName="clientId" required>
<mat-error *ngIf="registration.get('clientId').hasError('required')">
{{ 'admin.oauth2.client-id-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.client-secret</mat-label>
<input matInput formControlName="clientSecret" required>
<mat-error *ngIf="registration.get('clientSecret').hasError('required')">
{{ 'admin.oauth2.client-secret-required' | translate }}
</mat-error>
</mat-form-field>
</div>
<mat-expansion-panel class="mat-elevation-z0 custom-settings"
[disabled]="getProviderName(registration) === 'Custom'"
[expanded]="getProviderName(registration) === 'Custom'">
<mat-expansion-panel-header [fxHide]="getProviderName(registration) === 'Custom'">
<mat-panel-description fxLayoutAlign="end center">
{{ 'admin.oauth2.custom-setting' | translate }}
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<mat-tab-group dynamicHeight>
<mat-tab label="{{ 'admin.oauth2.general' | translate }}">
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" style="margin-top: 16px;">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.access-token-uri</mat-label>
<input matInput formControlName="accessTokenUri" required>
<button mat-icon-button matSuffix
type="button"
(click)="toggleEditMode(registration, 'accessTokenUri')"
*ngIf="getProviderName(registration) !== 'Custom'">
<mat-icon class="material-icons">create</mat-icon>
</button>
<mat-error *ngIf="registration.get('accessTokenUri').hasError('required')">
{{ 'admin.oauth2.access-token-uri-required' | translate }}
</mat-error>
<mat-error *ngIf="registration.get('accessTokenUri').hasError('pattern')">
{{ 'admin.oauth2.uri-pattern-error' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.authorization-uri</mat-label>
<input matInput formControlName="authorizationUri" required>
<button mat-icon-button matSuffix
type="button"
(click)="toggleEditMode(registration, 'authorizationUri')"
*ngIf="getProviderName(registration) !== 'Custom'">
<mat-icon class="material-icons">create</mat-icon>
</button>
<mat-error *ngIf="registration.get('authorizationUri').hasError('required')">
{{ 'admin.oauth2.authorization-uri-required' | translate }}
</mat-error>
<mat-error *ngIf="registration.get('authorizationUri').hasError('pattern')">
{{ 'admin.oauth2.uri-pattern-error' | translate }}
</mat-error>
</mat-form-field>
</div>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block" appearance="legacy">
<mat-label translate>admin.oauth2.jwk-set-uri</mat-label>
<input matInput formControlName="jwkSetUri">
<button mat-icon-button matSuffix
type="button" aria-label="Clear"
(click)="toggleEditMode(registration, 'jwkSetUri')"
*ngIf="getProviderName(registration) !== 'Custom'">
<mat-icon class="material-icons">create</mat-icon>
</button>
<mat-error *ngIf="registration.get('jwkSetUri').hasError('pattern')">
{{ 'admin.oauth2.uri-pattern-error' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.user-info-uri</mat-label>
<input matInput formControlName="userInfoUri" required>
<button mat-icon-button matSuffix
type="button"
(click)="toggleEditMode(registration, 'userInfoUri')"
*ngIf="getProviderName(registration) !== 'Custom'">
<mat-icon class="material-icons">create</mat-icon>
</button>
<mat-error *ngIf="registration.get('userInfoUri').hasError('required')">
{{ 'admin.oauth2.user-info-uri-required' | translate }}
</mat-error>
<mat-error *ngIf="registration.get('userInfoUri').hasError('pattern')">
{{ 'admin.oauth2.uri-pattern-error' | translate }}
</mat-error>
</mat-form-field>
</div>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.client-authentication-method</mat-label>
<mat-select formControlName="clientAuthenticationMethod">
<mat-option *ngFor="let clientAuthenticationMethod of clientAuthenticationMethods"
[value]="clientAuthenticationMethod">
{{ clientAuthenticationMethod | uppercase }}
</mat-option>
</mat-select>
</mat-form-field>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" *ngIf="getProviderName(registration) === 'Custom'">
<mat-form-field fxFlex class="mat-block" floatLabel="always">
<mat-label translate>admin.oauth2.login-button-label</mat-label>
<input matInput formControlName="loginButtonLabel"
placeholder="{{ 'admin.oauth2.login-button-label-1' | translate }}"
required>
<mat-error *ngIf="registration.get('loginButtonLabel').hasError('required')">
{{ 'admin.oauth2.login-button-label-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.login-button-icon</mat-label>
<input matInput formControlName="loginButtonIcon">
</mat-form-field>
</div>
<section formGroupName="mapperConfig">
<div fxLayout="column" fxLayoutGap="8px" style="margin-bottom: 8px;">
<mat-checkbox formControlName="allowUserCreation">
{{ 'admin.oauth2.allow-user-creation' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="activateUser">
{{ 'admin.oauth2.activate-user' | translate }}
</mat-checkbox>
</div>
</section>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.scope</mat-label>
<mat-chip-list #scopeList>
<mat-chip *ngFor="let scope of registration.get('scope').value; let k = index; trackBy: trackByParams"
removable (removed)="removeScope(k, registration)">
{{scope}}
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
<input [matChipInputFor]="scopeList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
matChipInputAddOnBlur
(matChipInputTokenEnd)="addScope($event, registration)">
</mat-chip-list>
<mat-error *ngIf="registration.get('scope').hasError('required')">
{{ 'admin.oauth2.scope-required' | translate }}
</mat-error>
</mat-form-field>
</mat-tab>
<mat-tab label="{{ 'admin.oauth2.mapper' | translate }}">
<mat-form-field class="mat-block" style="margin-top: 16px;">
<mat-label translate>admin.oauth2.user-name-attribute-name</mat-label>
<input matInput formControlName="userNameAttributeName" required>
<mat-error
*ngIf="registration.get('userNameAttributeName').hasError('required')">
{{ 'admin.oauth2.user-name-attribute-name-required' | translate }}
</mat-error>
</mat-form-field>
<section formGroupName="mapperConfig">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.type</mat-label>
<mat-select formControlName="type">
<mat-option *ngFor="let converterTypeExternalUser of converterTypesExternalUser"
[value]="converterTypeExternalUser">
{{ converterTypeExternalUser }}
</mat-option>
</mat-select>
</mat-form-field>
<section formGroupName="basic"
*ngIf="registration.get('mapperConfig.type').value === 'BASIC'">
<mat-form-field class="mat-block">
<mat-label translate>admin.oauth2.email-attribute-key</mat-label>
<input matInput formControlName="emailAttributeKey" required>
<mat-error
*ngIf="registration.get('mapperConfig.basic.emailAttributeKey').hasError('required')">
{{ 'admin.oauth2.email-attribute-key-required' | translate }}
</mat-error>
</mat-form-field>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.first-name-attribute-key</mat-label>
<input matInput formControlName="firstNameAttributeKey">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.last-name-attribute-key</mat-label>
<input matInput formControlName="lastNameAttributeKey">
</mat-form-field>
</div>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.tenant-name-strategy</mat-label>
<mat-select formControlName="tenantNameStrategy">
<mat-option *ngFor="let tenantNameStrategy of tenantNameStrategies"
[value]="tenantNameStrategy">
{{ tenantNameStrategy }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex class="mat-block" [fxShow]="registration.get('mapperConfig.basic.tenantNameStrategy').value === 'CUSTOM'">
<mat-label translate>admin.oauth2.tenant-name-pattern</mat-label>
<input matInput
formControlName="tenantNamePattern"
[required]="registration.get('mapperConfig.basic.tenantNameStrategy').value === 'CUSTOM'">
<mat-error
*ngIf="registration.get('mapperConfig.basic.tenantNamePattern').hasError('required')">
{{ 'admin.oauth2.tenant-name-pattern-required' | translate }}
</mat-error>
</mat-form-field>
</div>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.customer-name-pattern</mat-label>
<input matInput formControlName="customerNamePattern">
</mat-form-field>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.default-dashboard-name</mat-label>
<input matInput formControlName="defaultDashboardName">
</mat-form-field>
<mat-checkbox fxFlex formControlName="alwaysFullScreen" class="checkbox-row">
{{ 'admin.oauth2.always-fullscreen' | translate}}
</mat-checkbox>
</div>
</section>
<section formGroupName="custom"
*ngIf="registration.get('mapperConfig.type').value === 'CUSTOM'">
<mat-form-field class="mat-block">
<mat-label translate>admin.oauth2.url</mat-label>
<input matInput formControlName="url" required>
<mat-error
*ngIf="registration.get('mapperConfig.custom.url').hasError('required')">
{{ 'admin.oauth2.url-required' | translate }}
</mat-error>
<mat-error
*ngIf="registration.get('mapperConfig.custom.url').hasError('pattern')">
{{ 'admin.oauth2.url-pattern' | translate }}
</mat-error>
</mat-form-field>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>common.username</mat-label>
<input matInput formControlName="username" autocomplete="new-username">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>common.password</mat-label>
<input matInput type="password" formControlName="password" autocomplete="new-password">
</mat-form-field>
</div>
</section>
</section>
</mat-tab>
</mat-tab-group>
</ng-template>
</mat-expansion-panel>
</section>
</ng-template>
</mat-expansion-panel>
</div>
</ng-container>
<div fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px">
<button mat-button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="addProvider(domain)"
type="button">
{{'admin.oauth2.add-provider' | translate}}
</button>
</div>
</ng-template>
</mat-expansion-panel>
</ng-container>
</mat-accordion>
</div>
</ng-container>
</section>
</fieldset>
<div fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px">
<button type="button" mat-raised-button color="primary"
[disabled]="isLoading$ | async"
*ngIf="oauth2SettingsForm.get('enabled').value"
(click)="addDomain()">
<mat-icon>add</mat-icon>
<span translate>action.add</span>
</button>
<button mat-button mat-raised-button color="primary"
[disabled]="(isLoading$ | async) || oauth2SettingsForm.invalid || !oauth2SettingsForm.dirty"
type="submit">
{{'action.save' | translate}}
</button>
</div>
</form>
</mat-card-content>
</mat-card>
</div>

66
ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss

@ -0,0 +1,66 @@
/**
* 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.
*/
:host{
.checkbox-row {
margin-top: 1em;
}
.registration-card{
margin-bottom: 0.5em;
border: 1px solid rgba(0, 0, 0, 0.2);
.custom-settings{
font-size: 16px;
.mat-expansion-panel-header{
padding: 0 2px;
}
}
}
.container{
margin-bottom: 1em;
.tb-highlight{
margin: 0;
}
.tb-hint{
padding-bottom: 0;
}
}
}
:host ::ng-deep{
.registration-card{
.custom-settings{
.mat-expansion-panel-body{
padding: 0 2px 1em;
}
.mat-tab-label{
text-transform: none;
}
.mat-form-field-suffix .mat-icon-button .mat-icon{
font-size: 18px;
}
}
}
.domains-list{
margin-bottom: 1.5em;
.mat-form-field-suffix .mat-icon-button .mat-icon{
font-size: 24px;
}
}
}

500
ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts

@ -0,0 +1,500 @@
///
/// 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.
///
import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
import {
ClientAuthenticationMethod,
ClientProviderTemplated,
ClientRegistration,
DomainInfo,
DomainSchema,
domainSchemaTranslations,
DomainsParam,
MapperConfig,
MapperConfigBasic,
MapperConfigCustom,
MapperConfigType,
OAuth2Settings,
TenantNameStrategy
} from '@shared/models/settings.models';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { AdminService } from '@core/http/admin.service';
import { PageComponent } from '@shared/components/page.component';
import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard';
import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { MatChipInputEvent } from '@angular/material/chips';
import { WINDOW } from '@core/services/window.service';
import { forkJoin, Subscription } from 'rxjs';
import { DialogService } from '@core/services/dialog.service';
import { TranslateService } from '@ngx-translate/core';
import { isDefined } from '@core/utils';
@Component({
selector: 'tb-oauth2-settings',
templateUrl: './oauth2-settings.component.html',
styleUrls: ['./oauth2-settings.component.scss', './settings-card.scss']
})
export class OAuth2SettingsComponent extends PageComponent implements OnInit, HasConfirmForm, OnDestroy {
private URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.,?+=&%@\-/]*)?$/;
private subscriptions: Subscription[] = [];
private templates = new Map<string, ClientProviderTemplated>();
private defaultProvider = {
additionalInfo: {
providerName: 'Custom'
},
clientAuthenticationMethod: ClientAuthenticationMethod.POST,
userNameAttributeName: 'email',
mapperConfig: {
allowUserCreation: true,
activateUser: false,
type: MapperConfigType.BASIC,
basic: {
emailAttributeKey: 'email',
tenantNameStrategy: TenantNameStrategy.DOMAIN,
alwaysFullScreen: false
}
}
};
readonly separatorKeysCodes: number[] = [ENTER, COMMA];
oauth2SettingsForm: FormGroup;
oauth2Settings: OAuth2Settings;
clientAuthenticationMethods = Object.keys(ClientAuthenticationMethod);
converterTypesExternalUser = Object.keys(MapperConfigType);
tenantNameStrategies = Object.keys(TenantNameStrategy);
protocols = Object.keys(DomainSchema);
domainSchemaTranslations = domainSchemaTranslations;
templateProvider = ['Custom'];
constructor(protected store: Store<AppState>,
private adminService: AdminService,
private fb: FormBuilder,
private dialogService: DialogService,
private translate: TranslateService,
@Inject(WINDOW) private window: Window) {
super(store);
}
ngOnInit(): void {
this.buildOAuth2SettingsForm();
forkJoin([
this.adminService.getOAuth2Template(),
this.adminService.getOAuth2Settings()
]).subscribe(
([templates, oauth2Settings]) => {
this.initTemplates(templates);
this.oauth2Settings = oauth2Settings;
this.initOAuth2Settings(this.oauth2Settings);
}
);
}
ngOnDestroy() {
super.ngOnDestroy();
this.subscriptions.forEach((subscription) => {
subscription.unsubscribe();
});
}
private initTemplates(templates: ClientProviderTemplated[]): void {
templates.map(provider => this.templates.set(provider.name, provider));
this.templateProvider.push(...Array.from(this.templates.keys()));
this.templateProvider.sort();
}
get domainsParams(): FormArray {
return this.oauth2SettingsForm.get('domainsParams') as FormArray;
}
private formBasicGroup(mapperConfigBasic?: MapperConfigBasic): FormGroup {
let tenantNamePattern;
if (mapperConfigBasic?.tenantNamePattern) {
tenantNamePattern = mapperConfigBasic.tenantNamePattern;
} else {
tenantNamePattern = {value: null, disabled: true};
}
const basicGroup = this.fb.group({
emailAttributeKey: [mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email', Validators.required],
firstNameAttributeKey: [mapperConfigBasic?.firstNameAttributeKey ? mapperConfigBasic.firstNameAttributeKey : ''],
lastNameAttributeKey: [mapperConfigBasic?.lastNameAttributeKey ? mapperConfigBasic.lastNameAttributeKey : ''],
tenantNameStrategy: [mapperConfigBasic?.tenantNameStrategy ? mapperConfigBasic.tenantNameStrategy : TenantNameStrategy.DOMAIN],
tenantNamePattern: [tenantNamePattern, Validators.required],
customerNamePattern: [mapperConfigBasic?.customerNamePattern ? mapperConfigBasic.customerNamePattern : null],
defaultDashboardName: [mapperConfigBasic?.defaultDashboardName ? mapperConfigBasic.defaultDashboardName : null],
alwaysFullScreen: [mapperConfigBasic?.alwaysFullScreen ? mapperConfigBasic.alwaysFullScreen : false]
});
this.subscriptions.push(basicGroup.get('tenantNameStrategy').valueChanges.subscribe((domain) => {
if (domain === 'CUSTOM') {
basicGroup.get('tenantNamePattern').enable();
} else {
basicGroup.get('tenantNamePattern').disable();
}
}));
return basicGroup;
}
private formCustomGroup(mapperConfigCustom?: MapperConfigCustom): FormGroup {
return this.fb.group({
url: [mapperConfigCustom?.url ? mapperConfigCustom.url : null, [Validators.required, Validators.pattern(this.URL_REGEXP)]],
username: [mapperConfigCustom?.username ? mapperConfigCustom.username : null],
password: [mapperConfigCustom?.password ? mapperConfigCustom.password : null]
});
}
private buildOAuth2SettingsForm(): void {
this.oauth2SettingsForm = this.fb.group({
domainsParams: this.fb.array([]),
enabled: [false]
});
}
private initOAuth2Settings(oauth2Settings: OAuth2Settings): void {
if (oauth2Settings) {
this.oauth2SettingsForm.patchValue({enabled: oauth2Settings.enabled}, {emitEvent: false});
oauth2Settings.domainsParams.forEach((domain) => {
this.domainsParams.push(this.buildDomainsForm(domain));
});
}
}
private uniqueDomainValidator(control: FormGroup): { [key: string]: boolean } | null {
if (control.parent?.value) {
const domain = control.value.name;
const listProtocols = control.parent.getRawValue()
.filter((domainInfo) => domainInfo.name === domain)
.map((domainInfo) => domainInfo.scheme);
if (listProtocols.length > 1 && listProtocols.indexOf(DomainSchema.MIXED) > -1 ||
new Set(listProtocols).size !== listProtocols.length) {
return {unique: true};
}
}
return null;
}
public domainListTittle(control: AbstractControl): string {
const domainInfos = control.get('domainInfos').value as DomainInfo[];
if (domainInfos.length) {
const domainList = new Set<string>();
domainInfos.forEach((domain) => {
domainList.add(domain.name);
});
return Array.from(domainList).join(', ');
}
return this.translate.instant('admin.oauth2.new-domain');
}
private buildDomainsForm(domainParams?: DomainsParam): FormGroup {
const formDomain = this.fb.group({
domainInfos: this.fb.array([], Validators.required),
clientRegistrations: this.fb.array([], Validators.required)
});
if (domainParams) {
domainParams.domainInfos.forEach((domain) => {
this.clientDomainInfos(formDomain).push(this.buildDomainForm(domain));
});
domainParams.clientRegistrations.forEach((registration) => {
this.clientDomainProviders(formDomain).push(this.buildProviderForm(registration));
});
} else {
this.clientDomainProviders(formDomain).push(this.buildProviderForm());
this.clientDomainInfos(formDomain).push(this.buildDomainForm());
}
return formDomain;
}
private buildDomainForm(domainInfo?: DomainInfo): FormGroup {
const domain = this.fb.group({
name: [domainInfo ? domainInfo.name : this.window.location.hostname, [
Validators.required,
Validators.pattern('((?![:/]).)*$')]],
scheme: [domainInfo?.scheme ? domainInfo.scheme : DomainSchema.HTTPS, Validators.required]
}, {validators: this.uniqueDomainValidator});
return domain;
}
private buildProviderForm(registrationData?: ClientRegistration): FormGroup {
let additionalInfo = null;
if (registrationData?.additionalInfo) {
additionalInfo = JSON.parse(registrationData.additionalInfo);
if (this.templateProvider.indexOf(additionalInfo.providerName) === -1) {
additionalInfo.providerName = 'Custom';
}
}
let defaultProviderName = 'Custom';
if (this.templateProvider.indexOf('Google')) {
defaultProviderName = 'Google';
}
const clientRegistration = this.fb.group({
id: this.fb.group({
id: [registrationData?.id?.id ? registrationData.id.id : null],
entityType: [registrationData?.id?.entityType ? registrationData.id.entityType : null]
}),
additionalInfo: this.fb.group({
providerName: [additionalInfo?.providerName ? additionalInfo?.providerName : defaultProviderName, Validators.required]
}),
loginButtonLabel: [registrationData?.loginButtonLabel ? registrationData.loginButtonLabel : null, Validators.required],
loginButtonIcon: [registrationData?.loginButtonIcon ? registrationData.loginButtonIcon : null],
clientId: [registrationData?.clientId ? registrationData.clientId : '', Validators.required],
clientSecret: [registrationData?.clientSecret ? registrationData.clientSecret : '', Validators.required],
accessTokenUri: [registrationData?.accessTokenUri ? registrationData.accessTokenUri : '',
[Validators.required,
Validators.pattern(this.URL_REGEXP)]],
authorizationUri: [registrationData?.authorizationUri ? registrationData.authorizationUri : '',
[Validators.required,
Validators.pattern(this.URL_REGEXP)]],
scope: this.fb.array(registrationData?.scope ? registrationData.scope : [], Validators.required),
jwkSetUri: [registrationData?.jwkSetUri ? registrationData.jwkSetUri : '', Validators.pattern(this.URL_REGEXP)],
userInfoUri: [registrationData?.userInfoUri ? registrationData.userInfoUri : '',
[Validators.required,
Validators.pattern(this.URL_REGEXP)]],
clientAuthenticationMethod: [
registrationData?.clientAuthenticationMethod ? registrationData.clientAuthenticationMethod : ClientAuthenticationMethod.POST,
Validators.required],
userNameAttributeName: [
registrationData?.userNameAttributeName ? registrationData.userNameAttributeName : 'email', Validators.required],
mapperConfig: this.fb.group({
allowUserCreation: [registrationData?.mapperConfig?.allowUserCreation ? registrationData.mapperConfig.allowUserCreation : true],
activateUser: [registrationData?.mapperConfig?.activateUser ? registrationData.mapperConfig.activateUser : false],
type: [registrationData?.mapperConfig?.type ? registrationData.mapperConfig.type : MapperConfigType.BASIC, Validators.required]
}
)
});
if (registrationData) {
this.changeMapperConfigType(clientRegistration, registrationData.mapperConfig.type, registrationData.mapperConfig);
} else {
this.changeMapperConfigType(clientRegistration, MapperConfigType.BASIC);
this.setProviderDefaultValue(defaultProviderName, clientRegistration);
}
this.subscriptions.push(clientRegistration.get('mapperConfig.type').valueChanges.subscribe((value) => {
this.changeMapperConfigType(clientRegistration, value);
}));
this.subscriptions.push(clientRegistration.get('additionalInfo.providerName').valueChanges.subscribe((provider) => {
(clientRegistration.get('scope') as FormArray).clear();
this.setProviderDefaultValue(provider, clientRegistration);
}));
return clientRegistration;
}
private setProviderDefaultValue(provider: string, clientRegistration: FormGroup) {
if (provider === 'Custom') {
const defaultSettings = {...this.defaultProvider, ...{id: clientRegistration.get('id').value}};
clientRegistration.reset(defaultSettings, {emitEvent: false});
clientRegistration.get('accessTokenUri').enable();
clientRegistration.get('authorizationUri').enable();
clientRegistration.get('jwkSetUri').enable();
clientRegistration.get('userInfoUri').enable();
} else {
const template = this.templates.get(provider);
delete template.id;
delete template.additionalInfo;
template.clientId = '';
template.clientSecret = '';
template.scope.forEach(() => {
(clientRegistration.get('scope') as FormArray).push(this.fb.control(''));
});
clientRegistration.get('accessTokenUri').disable();
clientRegistration.get('authorizationUri').disable();
clientRegistration.get('jwkSetUri').disable();
clientRegistration.get('userInfoUri').disable();
clientRegistration.patchValue(template, {emitEvent: false});
}
}
private changeMapperConfigType(control: AbstractControl, type: MapperConfigType, predefinedValue?: MapperConfig) {
const mapperConfig = control.get('mapperConfig') as FormGroup;
if (type === MapperConfigType.BASIC) {
mapperConfig.removeControl('custom');
mapperConfig.addControl('basic', this.formBasicGroup(predefinedValue?.basic));
} else {
mapperConfig.removeControl('basic');
mapperConfig.addControl('custom', this.formCustomGroup(predefinedValue?.custom));
}
}
save(): void {
const setting = this.prepareFormValue(this.oauth2SettingsForm.getRawValue());
this.adminService.saveOAuth2Settings(setting).subscribe(
(oauth2Settings) => {
this.oauth2Settings = oauth2Settings;
this.oauth2SettingsForm.markAsPristine();
this.oauth2SettingsForm.markAsUntouched();
}
);
}
private prepareFormValue(formValue: OAuth2Settings): OAuth2Settings{
formValue.domainsParams.forEach((setting, index) => {
setting.clientRegistrations.forEach((registration) => {
registration.additionalInfo = JSON.stringify(registration.additionalInfo);
if (registration.id.id === null) {
delete registration.id;
}
});
});
return formValue;
}
confirmForm(): FormGroup {
return this.oauth2SettingsForm;
}
addScope(event: MatChipInputEvent, control: AbstractControl): void {
const input = event.input;
const value = event.value;
const controller = control.get('scope') as FormArray;
if ((value.trim() !== '')) {
controller.push(this.fb.control(value.trim()));
controller.markAsDirty();
}
if (input) {
input.value = '';
}
}
removeScope(i: number, control: AbstractControl): void {
const controller = control.get('scope') as FormArray;
controller.removeAt(i);
controller.markAsTouched();
controller.markAsDirty();
}
addDomain(): void {
this.domainsParams.push(this.buildDomainsForm());
}
deleteDomain($event: Event, index: number): void {
if ($event) {
$event.stopPropagation();
$event.preventDefault();
}
const domainName = this.domainListTittle(this.domainsParams.at(index));
this.dialogService.confirm(
this.translate.instant('admin.oauth2.delete-domain-title', {domainName: domainName || ''}),
this.translate.instant('admin.oauth2.delete-domain-text'), null,
this.translate.instant('action.delete')
).subscribe((data) => {
if (data) {
this.domainsParams.removeAt(index);
this.domainsParams.markAsTouched();
this.domainsParams.markAsDirty();
}
});
}
clientDomainProviders(control: AbstractControl): FormArray {
return control.get('clientRegistrations') as FormArray;
}
clientDomainInfos(control: AbstractControl): FormArray {
return control.get('domainInfos') as FormArray;
}
addProvider(control: AbstractControl): void {
this.clientDomainProviders(control).push(this.buildProviderForm());
}
deleteProvider($event: Event, control: AbstractControl, index: number): void {
if ($event) {
$event.stopPropagation();
$event.preventDefault();
}
const providerName = this.clientDomainProviders(control).at(index).get('additionalInfo.providerName').value;
this.dialogService.confirm(
this.translate.instant('admin.oauth2.delete-registration-title', {name: providerName || ''}),
this.translate.instant('admin.oauth2.delete-registration-text'), null,
this.translate.instant('action.delete')
).subscribe((data) => {
if (data) {
this.clientDomainProviders(control).removeAt(index);
this.clientDomainProviders(control).markAsTouched();
this.clientDomainProviders(control).markAsDirty();
}
});
}
toggleEditMode(control: AbstractControl, path: string) {
control.get(path).disabled ? control.get(path).enable() : control.get(path).disable();
}
getProviderName(controller: AbstractControl): string {
return controller.get('additionalInfo.providerName').value;
}
getHelpLink(controller: AbstractControl): string {
const provider = controller.get('additionalInfo.providerName').value;
if (provider === null || provider === 'Custom') {
return '';
}
return this.templates.get(provider).helpLink;
}
addDomainInfo(control: AbstractControl): void {
this.clientDomainInfos(control).push(this.buildDomainForm({
name: '',
scheme: DomainSchema.HTTPS
}));
}
removeDomain($event: Event, control: AbstractControl, index: number): void {
if ($event) {
$event.stopPropagation();
$event.preventDefault();
}
this.clientDomainInfos(control).removeAt(index);
this.clientDomainInfos(control).markAsTouched();
this.clientDomainInfos(control).markAsDirty();
}
redirectURI(control: AbstractControl, schema?: DomainSchema): string {
const domainInfo = control.value as DomainInfo;
if (domainInfo.name !== '') {
let protocol;
if (isDefined(schema)) {
protocol = schema.toLowerCase();
} else {
protocol = domainInfo.scheme === DomainSchema.MIXED ? DomainSchema.HTTPS.toLowerCase() : domainInfo.scheme.toLowerCase();
}
return `${protocol}://${domainInfo.name}/login/oauth2/code/`;
}
return '';
}
redirectURIMixed(control: AbstractControl): string {
return this.redirectURI(control, DomainSchema.HTTP);
}
trackByParams(index: number): number {
return index;
}
}

3
ui-ngx/src/app/shared/models/constants.ts

@ -60,6 +60,7 @@ export const HelpLinks = {
linksMap: {
outgoingMailSettings: helpBaseUrl + '/docs/user-guide/ui/mail-settings',
securitySettings: helpBaseUrl + '/docs/user-guide/ui/security-settings',
oauth2Settings: helpBaseUrl + '/docs/user-guide/oauth-2-support/',
ruleEngine: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/overview/',
ruleNodeCheckRelation: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#check-relation-filter-node',
ruleNodeCheckExistenceFields: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#check-existence-fields-node',
@ -123,7 +124,7 @@ export const HelpLinks = {
widgetsConfigLatest: helpBaseUrl + '/docs/user-guide/ui/dashboards#latest',
widgetsConfigRpc: helpBaseUrl + '/docs/user-guide/ui/dashboards#rpc',
widgetsConfigAlarm: helpBaseUrl + '/docs/user-guide/ui/dashboards#alarm',
widgetsConfigStatic: helpBaseUrl + '/docs/user-guide/ui/dashboards#static'
widgetsConfigStatic: helpBaseUrl + '/docs/user-guide/ui/dashboards#static',
}
};

99
ui-ngx/src/app/shared/models/settings.models.ts

@ -14,6 +14,9 @@
/// limitations under the License.
///
import { EntityId } from '@shared/models/id/entity-id';
import { TenantId } from '@shared/models/id/tenant-id';
export const smtpPortPattern: RegExp = /^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/;
export interface AdminSettings<T> {
@ -60,3 +63,99 @@ export interface UpdateMessage {
message: string;
updateAvailable: boolean;
}
export interface OAuth2Settings {
enabled: boolean;
domainsParams: DomainsParam[];
}
export interface DomainsParam {
clientRegistrations: ClientRegistration[];
domainInfos: DomainInfo[];
}
export interface DomainInfo {
name: string;
scheme: DomainSchema;
}
export enum DomainSchema{
HTTP = 'HTTP',
HTTPS = 'HTTPS',
MIXED = 'MIXED'
}
export const domainSchemaTranslations = new Map<DomainSchema, string>(
[
[DomainSchema.HTTP, 'admin.oauth2.domain-schema-http'],
[DomainSchema.HTTPS, 'admin.oauth2.domain-schema-https'],
[DomainSchema.MIXED, 'admin.oauth2.domain-schema-mixed']
]
);
export enum MapperConfigType{
BASIC = 'BASIC',
CUSTOM = 'CUSTOM'
}
export enum TenantNameStrategy{
DOMAIN = 'DOMAIN',
EMAIL = 'EMAIL',
CUSTOM = 'CUSTOM'
}
export interface ClientProviderTemplated extends ClientRegistration{
comment: string;
createdTime: number;
helpLink: string;
name: string;
providerId: string;
tenantId: TenantId;
}
export interface ClientRegistration {
loginButtonLabel: string;
loginButtonIcon: string;
clientId: string;
clientSecret: string;
accessTokenUri: string;
authorizationUri: string;
scope: string[];
jwkSetUri?: string;
userInfoUri: string;
clientAuthenticationMethod: ClientAuthenticationMethod;
userNameAttributeName: string;
mapperConfig: MapperConfig;
id?: EntityId;
additionalInfo: string;
}
export enum ClientAuthenticationMethod {
BASIC = 'BASIC',
POST = 'POST'
}
export interface MapperConfig {
allowUserCreation: boolean;
activateUser: boolean;
type: MapperConfigType;
basic?: MapperConfigBasic;
custom?: MapperConfigCustom;
}
export interface MapperConfigBasic {
emailAttributeKey: string;
firstNameAttributeKey?: string;
lastNameAttributeKey?: string;
tenantNameStrategy: TenantNameStrategy;
tenantNamePattern?: string;
customerNamePattern?: string;
defaultDashboardName?: string;
alwaysFullScreen?: boolean;
}
export interface MapperConfigCustom {
url: string;
username?: string;
password?: string;
}

70
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -120,8 +120,74 @@
"general-policy": "General policy",
"max-failed-login-attempts": "Maximum number of failed login attempts, before account is locked",
"minimum-max-failed-login-attempts-range": "Maximum number of failed login attempts can't be negative",
"user-lockout-notification-email": "In case user account lockout, send notification to email"
},
"user-lockout-notification-email": "In case user account lockout, send notification to email",
"domain-name": "Domain name",
"domain-name-unique": "Domain name and protocol need to unique.",
"error-verification-url": "A domain name shouldn't contain symbols '/' and ':'. Example: thingsboard.io",
"oauth2": {
"access-token-uri": "Access token URI",
"access-token-uri-required": "Access token URI is required.",
"activate-user": "Activate user",
"add-domain": "Add domain",
"delete-domain": "Delete domain",
"add-provider": "Add provider",
"delete-provider": "Delete provider",
"allow-user-creation": "Allow user creation",
"always-fullscreen": "Always fullscreen",
"authorization-uri": "Authorization URI",
"authorization-uri-required": "Authorization URI is required.",
"client-authentication-method": "Client authentication method",
"client-id": "Client ID",
"client-id-required": "Client ID is required.",
"client-secret": "Client secret",
"client-secret-required": "Client secret is required.",
"custom-setting": "Custom settings",
"customer-name-pattern": "Customer name pattern",
"default-dashboard-name": "Default dashboard name",
"delete-domain-text": "Be careful, after the confirmation a domain and all provider data will be unavailable.",
"delete-domain-title": "Are you sure you want to delete settings the domain '{{domainName}}'?",
"delete-registration-text": "Be careful, after the confirmation a provider data will be unavailable.",
"delete-registration-title": "Are you sure you want to delete the provider '{{name}}'?",
"email-attribute-key": "Email attribute key",
"email-attribute-key-required": "Email attribute key is required.",
"first-name-attribute-key": "First name attribute key",
"general": "General",
"jwk-set-uri": "JSON Web Key URI",
"last-name-attribute-key": "Last name attribute key",
"login-button-icon": "Login button icon",
"login-button-label": "Provider label",
"login-button-label-1": "Login with $(Provider label)",
"login-button-label-required": "Label is required.",
"login-provider": "Login provider",
"mapper": "Mapper",
"new-domain": "New domain",
"oauth2": "OAuth2",
"redirect-uri-template": "Redirect URI template",
"copy-redirect-uri": "Copy redirect URI",
"registration-id": "Registration ID",
"registration-id-required": "Registration ID is required.",
"registration-id-unique": "Registration ID need to unique for the system.",
"scope": "Scope",
"scope-required": "Scope is required.",
"tenant-name-pattern": "Tenant name pattern",
"tenant-name-pattern-required": "Tenant name pattern is required.",
"tenant-name-strategy": "Tenant name strategy",
"type": "Mapper type",
"uri-pattern-error": "Invalid URI format.",
"url": "URL",
"url-pattern": "Invalid URL format.",
"url-required": "URL is required.",
"user-info-uri": "User info URI",
"user-info-uri-required": "User info URI is required.",
"user-name-attribute-name": "User name attribute key",
"user-name-attribute-name-required": "User name attribute key is required",
"protocol": "Protocol",
"domain-schema-http": "HTTP",
"domain-schema-https": "HTTPS",
"domain-schema-mixed": "HTTP+HTTPS",
"enable": "Enable OAuth2 settings"
}
},
"alarm": {
"alarm": "Alarm",
"alarms": "Alarms",

Loading…
Cancel
Save