Commit 628ce7991da43b50aeeed7d349ea184cdfd96adc

Authored by Igor Kulikov
2 parents 93fc099e 8cfbcee0

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

Showing 88 changed files with 4951 additions and 320 deletions
  1 +{
  2 + "providerId": "Facebook",
  3 + "accessTokenUri": "https://graph.facebook.com/v2.8/oauth/access_token",
  4 + "authorizationUri": "https://www.facebook.com/v2.8/dialog/oauth",
  5 + "scope": ["email","public_profile"],
  6 + "jwkSetUri": null,
  7 + "userInfoUri": "https://graph.facebook.com/me?fields=id,name,first_name,last_name,email",
  8 + "clientAuthenticationMethod": "BASIC",
  9 + "userNameAttributeName": "email",
  10 + "basic": {
  11 + "emailAttributeKey": "email",
  12 + "firstNameAttributeKey": "first_name",
  13 + "lastNameAttributeKey": "last_name",
  14 + "tenantNameStrategy": "DOMAIN"
  15 + },
  16 + "comment": null,
  17 + "loginButtonIcon": "mdi:facebook",
  18 + "loginButtonLabel": "Facebook",
  19 + "helpLink": "https://developers.facebook.com/docs/facebook-login/web#logindialog"
  20 +}
... ...
  1 +{
  2 + "providerId": "Github",
  3 + "accessTokenUri": "https://github.com/login/oauth/access_token",
  4 + "authorizationUri": "https://github.com/login/oauth/authorize",
  5 + "scope": ["read:user","user:email"],
  6 + "jwkSetUri": null,
  7 + "userInfoUri": "https://api.github.com/user",
  8 + "clientAuthenticationMethod": "BASIC",
  9 + "userNameAttributeName": "login",
  10 + "basic": {
  11 + "tenantNameStrategy": "DOMAIN"
  12 + },
  13 + "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>",
  14 + "loginButtonIcon": "mdi:github",
  15 + "loginButtonLabel": "Github",
  16 + "helpLink": "https://docs.github.com/en/developers/apps/creating-an-oauth-app"
  17 +}
... ...
  1 +{
  2 + "providerId": "Google",
  3 + "additionalInfo": null,
  4 + "accessTokenUri": "https://oauth2.googleapis.com/token",
  5 + "authorizationUri": "https://accounts.google.com/o/oauth2/v2/auth",
  6 + "scope": ["email","openid","profile"],
  7 + "jwkSetUri": "https://www.googleapis.com/oauth2/v3/certs",
  8 + "userInfoUri": "https://openidconnect.googleapis.com/v1/userinfo",
  9 + "clientAuthenticationMethod": "BASIC",
  10 + "userNameAttributeName": "email",
  11 + "basic": {
  12 + "emailAttributeKey": "email",
  13 + "firstNameAttributeKey": "given_name",
  14 + "lastNameAttributeKey": "family_name",
  15 + "tenantNameStrategy": "DOMAIN"
  16 + },
  17 + "comment": null,
  18 + "loginButtonIcon": "mdi:google",
  19 + "loginButtonLabel": "Google",
  20 + "helpLink": "https://developers.google.com/adwords/api/docs/guides/authentication"
  21 +}
... ...
... ... @@ -14,6 +14,75 @@
14 14 -- limitations under the License.
15 15 --
16 16
  17 +CREATE TABLE IF NOT EXISTS oauth2_client_registration_info (
  18 + id uuid NOT NULL CONSTRAINT oauth2_client_registration_info_pkey PRIMARY KEY,
  19 + enabled boolean,
  20 + created_time bigint NOT NULL,
  21 + additional_info varchar,
  22 + client_id varchar(255),
  23 + client_secret varchar(255),
  24 + authorization_uri varchar(255),
  25 + token_uri varchar(255),
  26 + scope varchar(255),
  27 + user_info_uri varchar(255),
  28 + user_name_attribute_name varchar(255),
  29 + jwk_set_uri varchar(255),
  30 + client_authentication_method varchar(255),
  31 + login_button_label varchar(255),
  32 + login_button_icon varchar(255),
  33 + allow_user_creation boolean,
  34 + activate_user boolean,
  35 + type varchar(31),
  36 + basic_email_attribute_key varchar(31),
  37 + basic_first_name_attribute_key varchar(31),
  38 + basic_last_name_attribute_key varchar(31),
  39 + basic_tenant_name_strategy varchar(31),
  40 + basic_tenant_name_pattern varchar(255),
  41 + basic_customer_name_pattern varchar(255),
  42 + basic_default_dashboard_name varchar(255),
  43 + basic_always_full_screen boolean,
  44 + custom_url varchar(255),
  45 + custom_username varchar(255),
  46 + custom_password varchar(255),
  47 + custom_send_token boolean
  48 +);
  49 +
  50 +CREATE TABLE IF NOT EXISTS oauth2_client_registration (
  51 + id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY,
  52 + created_time bigint NOT NULL,
  53 + domain_name varchar(255),
  54 + domain_scheme varchar(31),
  55 + client_registration_info_id uuid
  56 +);
  57 +
  58 +CREATE TABLE IF NOT EXISTS oauth2_client_registration_template (
  59 + id uuid NOT NULL CONSTRAINT oauth2_client_registration_template_pkey PRIMARY KEY,
  60 + created_time bigint NOT NULL,
  61 + additional_info varchar,
  62 + provider_id varchar(255),
  63 + authorization_uri varchar(255),
  64 + token_uri varchar(255),
  65 + scope varchar(255),
  66 + user_info_uri varchar(255),
  67 + user_name_attribute_name varchar(255),
  68 + jwk_set_uri varchar(255),
  69 + client_authentication_method varchar(255),
  70 + type varchar(31),
  71 + basic_email_attribute_key varchar(31),
  72 + basic_first_name_attribute_key varchar(31),
  73 + basic_last_name_attribute_key varchar(31),
  74 + basic_tenant_name_strategy varchar(31),
  75 + basic_tenant_name_pattern varchar(255),
  76 + basic_customer_name_pattern varchar(255),
  77 + basic_default_dashboard_name varchar(255),
  78 + basic_always_full_screen boolean,
  79 + comment varchar,
  80 + login_button_icon varchar(255),
  81 + login_button_label varchar(255),
  82 + help_link varchar(255),
  83 + CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id)
  84 +);
  85 +
17 86 CREATE TABLE IF NOT EXISTS device_profile (
18 87 id uuid NOT NULL CONSTRAINT device_profile_pkey PRIMARY KEY,
19 88 created_time bigint NOT NULL,
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.config;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.beans.factory.annotation.Autowired;
  20 +import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
  21 +import org.springframework.security.crypto.keygen.StringKeyGenerator;
  22 +import org.springframework.security.oauth2.client.registration.ClientRegistration;
  23 +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
  24 +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
  25 +import org.springframework.security.oauth2.core.AuthorizationGrantType;
  26 +import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
  27 +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
  28 +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
  29 +import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
  30 +import org.springframework.security.oauth2.core.oidc.OidcScopes;
  31 +import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
  32 +import org.springframework.security.web.util.UrlUtils;
  33 +import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
  34 +import org.springframework.stereotype.Service;
  35 +import org.springframework.util.CollectionUtils;
  36 +import org.springframework.util.StringUtils;
  37 +import org.springframework.web.util.UriComponents;
  38 +import org.springframework.web.util.UriComponentsBuilder;
  39 +import org.thingsboard.server.dao.oauth2.OAuth2Configuration;
  40 +import org.thingsboard.server.utils.MiscUtils;
  41 +
  42 +import javax.servlet.http.HttpServletRequest;
  43 +import java.nio.charset.StandardCharsets;
  44 +import java.security.MessageDigest;
  45 +import java.security.NoSuchAlgorithmException;
  46 +import java.util.Base64;
  47 +import java.util.HashMap;
  48 +import java.util.Map;
  49 +
  50 +@Service
  51 +@Slf4j
  52 +public class CustomOAuth2AuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver {
  53 + public static final String DEFAULT_AUTHORIZATION_REQUEST_BASE_URI = "/oauth2/authorization";
  54 + public static final String DEFAULT_LOGIN_PROCESSING_URI = "/login/oauth2/code/";
  55 + private static final String REGISTRATION_ID_URI_VARIABLE_NAME = "registrationId";
  56 + private static final char PATH_DELIMITER = '/';
  57 +
  58 + private final AntPathRequestMatcher authorizationRequestMatcher = new AntPathRequestMatcher(
  59 + DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/{" + REGISTRATION_ID_URI_VARIABLE_NAME + "}");
  60 + private final StringKeyGenerator stateGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder());
  61 + private final StringKeyGenerator secureKeyGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
  62 +
  63 + @Autowired
  64 + private ClientRegistrationRepository clientRegistrationRepository;
  65 +
  66 + @Autowired(required = false)
  67 + private OAuth2Configuration oauth2Configuration;
  68 +
  69 +
  70 + @Override
  71 + public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
  72 + String registrationId = this.resolveRegistrationId(request);
  73 + String redirectUriAction = getAction(request, "login");
  74 + return resolve(request, registrationId, redirectUriAction);
  75 + }
  76 +
  77 + @Override
  78 + public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId) {
  79 + if (registrationId == null) {
  80 + return null;
  81 + }
  82 + String redirectUriAction = getAction(request, "authorize");
  83 + return resolve(request, registrationId, redirectUriAction);
  84 + }
  85 +
  86 + private String getAction(HttpServletRequest request, String defaultAction) {
  87 + String action = request.getParameter("action");
  88 + if (action == null) {
  89 + return defaultAction;
  90 + }
  91 + return action;
  92 + }
  93 +
  94 + private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId, String redirectUriAction) {
  95 + if (registrationId == null) {
  96 + return null;
  97 + }
  98 +
  99 + ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
  100 + if (clientRegistration == null) {
  101 + throw new IllegalArgumentException("Invalid Client Registration with Id: " + registrationId);
  102 + }
  103 +
  104 + Map<String, Object> attributes = new HashMap<>();
  105 + attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId());
  106 +
  107 + OAuth2AuthorizationRequest.Builder builder;
  108 + if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
  109 + builder = OAuth2AuthorizationRequest.authorizationCode();
  110 + Map<String, Object> additionalParameters = new HashMap<>();
  111 + if (!CollectionUtils.isEmpty(clientRegistration.getScopes()) &&
  112 + clientRegistration.getScopes().contains(OidcScopes.OPENID)) {
  113 + // Section 3.1.2.1 Authentication Request - https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
  114 + // scope
  115 + // REQUIRED. OpenID Connect requests MUST contain the "openid" scope value.
  116 + addNonceParameters(attributes, additionalParameters);
  117 + }
  118 + if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())) {
  119 + addPkceParameters(attributes, additionalParameters);
  120 + }
  121 + builder.additionalParameters(additionalParameters);
  122 + } else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) {
  123 + builder = OAuth2AuthorizationRequest.implicit();
  124 + } else {
  125 + throw new IllegalArgumentException("Invalid Authorization Grant Type (" +
  126 + clientRegistration.getAuthorizationGrantType().getValue() +
  127 + ") for Client Registration with Id: " + clientRegistration.getRegistrationId());
  128 + }
  129 +
  130 + String redirectUriStr = expandRedirectUri(request, clientRegistration, redirectUriAction);
  131 +
  132 + return builder
  133 + .clientId(clientRegistration.getClientId())
  134 + .authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
  135 + .redirectUri(redirectUriStr)
  136 + .scopes(clientRegistration.getScopes())
  137 + .state(this.stateGenerator.generateKey())
  138 + .attributes(attributes)
  139 + .build();
  140 + }
  141 +
  142 + private String resolveRegistrationId(HttpServletRequest request) {
  143 + if (this.authorizationRequestMatcher.matches(request)) {
  144 + return this.authorizationRequestMatcher
  145 + .matcher(request).getVariables().get(REGISTRATION_ID_URI_VARIABLE_NAME);
  146 + }
  147 + return null;
  148 + }
  149 +
  150 + /**
  151 + * Expands the {@link ClientRegistration#getRedirectUriTemplate()} with following provided variables:<br/>
  152 + * - baseUrl (e.g. https://localhost/app) <br/>
  153 + * - baseScheme (e.g. https) <br/>
  154 + * - baseHost (e.g. localhost) <br/>
  155 + * - basePort (e.g. :8080) <br/>
  156 + * - basePath (e.g. /app) <br/>
  157 + * - registrationId (e.g. google) <br/>
  158 + * - action (e.g. login) <br/>
  159 + * <p/>
  160 + * Null variables are provided as empty strings.
  161 + * <p/>
  162 + * Default redirectUriTemplate is: {@link org.springframework.security.config.oauth2.client}.CommonOAuth2Provider#DEFAULT_REDIRECT_URL
  163 + *
  164 + * @return expanded URI
  165 + */
  166 + private String expandRedirectUri(HttpServletRequest request, ClientRegistration clientRegistration, String action) {
  167 + Map<String, String> uriVariables = new HashMap<>();
  168 + uriVariables.put("registrationId", clientRegistration.getRegistrationId());
  169 +
  170 + UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
  171 + .replacePath(request.getContextPath())
  172 + .replaceQuery(null)
  173 + .fragment(null)
  174 + .build();
  175 + String scheme = uriComponents.getScheme();
  176 + uriVariables.put("baseScheme", scheme == null ? "" : scheme);
  177 + String host = uriComponents.getHost();
  178 + uriVariables.put("baseHost", host == null ? "" : host);
  179 + // following logic is based on HierarchicalUriComponents#toUriString()
  180 + int port = uriComponents.getPort();
  181 + uriVariables.put("basePort", port == -1 ? "" : ":" + port);
  182 + String path = uriComponents.getPath();
  183 + if (StringUtils.hasLength(path)) {
  184 + if (path.charAt(0) != PATH_DELIMITER) {
  185 + path = PATH_DELIMITER + path;
  186 + }
  187 + }
  188 + uriVariables.put("basePath", path == null ? "" : path);
  189 + uriVariables.put("baseUrl", uriComponents.toUriString());
  190 +
  191 + uriVariables.put("action", action == null ? "" : action);
  192 +
  193 + String redirectUri = getRedirectUri(request);
  194 + log.trace("Redirect URI - {}.", redirectUri);
  195 +
  196 + return UriComponentsBuilder.fromUriString(redirectUri)
  197 + .buildAndExpand(uriVariables)
  198 + .toUriString();
  199 + }
  200 +
  201 + private String getRedirectUri(HttpServletRequest request) {
  202 + String loginProcessingUri = oauth2Configuration != null ? oauth2Configuration.getLoginProcessingUrl() : DEFAULT_LOGIN_PROCESSING_URI;
  203 +
  204 + String scheme = MiscUtils.getScheme(request);
  205 + String domainName = MiscUtils.getDomainName(request);
  206 + int port = MiscUtils.getPort(request);
  207 + String baseUrl = scheme + "://" + domainName;
  208 + if (needsPort(scheme, port)){
  209 + baseUrl += ":" + port;
  210 + }
  211 + return baseUrl + loginProcessingUri;
  212 + }
  213 +
  214 + private boolean needsPort(String scheme, int port) {
  215 + boolean isHttpDefault = "http".equals(scheme.toLowerCase()) && port == 80;
  216 + boolean isHttpsDefault = "https".equals(scheme.toLowerCase()) && port == 443;
  217 + return !isHttpDefault && !isHttpsDefault;
  218 + }
  219 +
  220 + /**
  221 + * Creates nonce and its hash for use in OpenID Connect 1.0 Authentication Requests.
  222 + *
  223 + * @param attributes where the {@link OidcParameterNames#NONCE} is stored for the authentication request
  224 + * @param additionalParameters where the {@link OidcParameterNames#NONCE} hash is added for the authentication request
  225 + *
  226 + * @since 5.2
  227 + * @see <a target="_blank" href="https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest">3.1.2.1. Authentication Request</a>
  228 + */
  229 + private void addNonceParameters(Map<String, Object> attributes, Map<String, Object> additionalParameters) {
  230 + try {
  231 + String nonce = this.secureKeyGenerator.generateKey();
  232 + String nonceHash = createHash(nonce);
  233 + attributes.put(OidcParameterNames.NONCE, nonce);
  234 + additionalParameters.put(OidcParameterNames.NONCE, nonceHash);
  235 + } catch (NoSuchAlgorithmException e) { }
  236 + }
  237 +
  238 + /**
  239 + * Creates and adds additional PKCE parameters for use in the OAuth 2.0 Authorization and Access Token Requests
  240 + *
  241 + * @param attributes where {@link PkceParameterNames#CODE_VERIFIER} is stored for the token request
  242 + * @param additionalParameters where {@link PkceParameterNames#CODE_CHALLENGE} and, usually,
  243 + * {@link PkceParameterNames#CODE_CHALLENGE_METHOD} are added to be used in the authorization request.
  244 + *
  245 + * @since 5.2
  246 + * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-1.1">1.1. Protocol Flow</a>
  247 + * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-4.1">4.1. Client Creates a Code Verifier</a>
  248 + * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-4.2">4.2. Client Creates the Code Challenge</a>
  249 + */
  250 + private void addPkceParameters(Map<String, Object> attributes, Map<String, Object> additionalParameters) {
  251 + String codeVerifier = this.secureKeyGenerator.generateKey();
  252 + attributes.put(PkceParameterNames.CODE_VERIFIER, codeVerifier);
  253 + try {
  254 + String codeChallenge = createHash(codeVerifier);
  255 + additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, codeChallenge);
  256 + additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256");
  257 + } catch (NoSuchAlgorithmException e) {
  258 + additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, codeVerifier);
  259 + }
  260 + }
  261 +
  262 + private static String createHash(String value) throws NoSuchAlgorithmException {
  263 + MessageDigest md = MessageDigest.getInstance("SHA-256");
  264 + byte[] digest = md.digest(value.getBytes(StandardCharsets.US_ASCII));
  265 + return Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
  266 + }
  267 +}
... ...
... ... @@ -32,6 +32,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
32 32 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
33 33 import org.springframework.security.config.http.SessionCreationPolicy;
34 34 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  35 +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
35 36 import org.springframework.security.web.authentication.AuthenticationFailureHandler;
36 37 import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
37 38 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
... ... @@ -175,6 +176,9 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
175 176 web.ignoring().antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**");
176 177 }
177 178
  179 + @Autowired
  180 + private OAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver;
  181 +
178 182 @Override
179 183 protected void configure(HttpSecurity http) throws Exception {
180 184 http.headers().cacheControl().and().frameOptions().disable()
... ... @@ -207,8 +211,10 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
207 211 .addFilterBefore(buildRefreshTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
208 212 .addFilterBefore(buildWsJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
209 213 .addFilterAfter(rateLimitProcessingFilter, UsernamePasswordAuthenticationFilter.class);
210   - if (oauth2Configuration != null && oauth2Configuration.isEnabled()) {
  214 + if (oauth2Configuration != null) {
211 215 http.oauth2Login()
  216 + .authorizationEndpoint().authorizationRequestResolver(oAuth2AuthorizationRequestResolver)
  217 + .and()
212 218 .loginPage("/oauth2Login")
213 219 .loginProcessingUrl(oauth2Configuration.getLoginProcessingUrl())
214 220 .successHandler(oauth2AuthenticationSuccessHandler)
... ...
... ... @@ -38,10 +38,8 @@ import org.thingsboard.server.common.data.audit.ActionType;
38 38 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
39 39 import org.thingsboard.server.common.data.exception.ThingsboardException;
40 40 import org.thingsboard.server.common.data.id.TenantId;
41   -import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
42 41 import org.thingsboard.server.common.data.security.UserCredentials;
43 42 import org.thingsboard.server.dao.audit.AuditLogService;
44   -import org.thingsboard.server.dao.oauth2.OAuth2Service;
45 43 import org.thingsboard.server.queue.util.TbCoreComponent;
46 44 import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
47 45 import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails;
... ... @@ -84,9 +82,6 @@ public class AuthController extends BaseController {
84 82 @Autowired
85 83 private AuditLogService auditLogService;
86 84
87   - @Autowired
88   - private OAuth2Service oauth2Service;
89   -
90 85 @PreAuthorize("isAuthenticated()")
91 86 @RequestMapping(value = "/auth/user", method = RequestMethod.GET)
92 87 public @ResponseBody User getUser() throws ThingsboardException {
... ... @@ -336,14 +331,4 @@ public class AuthController extends BaseController {
336 331 throw handleException(e);
337 332 }
338 333 }
339   -
340   - @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST)
341   - @ResponseBody
342   - public List<OAuth2ClientInfo> getOAuth2Clients() throws ThingsboardException {
343   - try {
344   - return oauth2Service.getOAuth2Clients();
345   - } catch (Exception e) {
346   - throw handleException(e);
347   - }
348   - }
349 334 }
... ...
... ... @@ -27,22 +27,7 @@ import org.springframework.beans.factory.annotation.Value;
27 27 import org.springframework.security.core.Authentication;
28 28 import org.springframework.security.core.context.SecurityContextHolder;
29 29 import org.springframework.web.bind.annotation.ExceptionHandler;
30   -import org.thingsboard.server.common.data.Customer;
31   -import org.thingsboard.server.common.data.Dashboard;
32   -import org.thingsboard.server.common.data.DashboardInfo;
33   -import org.thingsboard.server.common.data.DataConstants;
34   -import org.thingsboard.server.common.data.Device;
35   -import org.thingsboard.server.common.data.DeviceInfo;
36   -import org.thingsboard.server.common.data.DeviceProfile;
37   -import org.thingsboard.server.common.data.EntityType;
38   -import org.thingsboard.server.common.data.EntityView;
39   -import org.thingsboard.server.common.data.EntityViewInfo;
40   -import org.thingsboard.server.common.data.HasName;
41   -import org.thingsboard.server.common.data.HasTenantId;
42   -import org.thingsboard.server.common.data.Tenant;
43   -import org.thingsboard.server.common.data.TenantInfo;
44   -import org.thingsboard.server.common.data.TenantProfile;
45   -import org.thingsboard.server.common.data.User;
  30 +import org.thingsboard.server.common.data.*;
46 31 import org.thingsboard.server.common.data.alarm.Alarm;
47 32 import org.thingsboard.server.common.data.alarm.AlarmInfo;
48 33 import org.thingsboard.server.common.data.asset.Asset;
... ... @@ -50,6 +35,7 @@ import org.thingsboard.server.common.data.asset.AssetInfo;
50 35 import org.thingsboard.server.common.data.audit.ActionType;
51 36 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
52 37 import org.thingsboard.server.common.data.exception.ThingsboardException;
  38 +import org.thingsboard.server.common.data.id.*;
53 39 import org.thingsboard.server.common.data.id.AlarmId;
54 40 import org.thingsboard.server.common.data.id.AssetId;
55 41 import org.thingsboard.server.common.data.id.CustomerId;
... ... @@ -93,6 +79,8 @@ import org.thingsboard.server.dao.entityview.EntityViewService;
93 79 import org.thingsboard.server.dao.exception.DataValidationException;
94 80 import org.thingsboard.server.dao.exception.IncorrectParameterException;
95 81 import org.thingsboard.server.dao.model.ModelConstants;
  82 +import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
  83 +import org.thingsboard.server.dao.oauth2.OAuth2Service;
96 84 import org.thingsboard.server.dao.relation.RelationService;
97 85 import org.thingsboard.server.dao.rule.RuleChainService;
98 86 import org.thingsboard.server.dao.tenant.TenantProfileService;
... ... @@ -176,6 +164,12 @@ public abstract class BaseController {
176 164 protected DashboardService dashboardService;
177 165
178 166 @Autowired
  167 + protected OAuth2Service oAuth2Service;
  168 +
  169 + @Autowired
  170 + protected OAuth2ConfigTemplateService oAuth2ConfigTemplateService;
  171 +
  172 + @Autowired
179 173 protected ComponentDiscoveryService componentDescriptorService;
180 174
181 175 @Autowired
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.http.HttpStatus;
  20 +import org.springframework.security.access.prepost.PreAuthorize;
  21 +import org.springframework.web.bind.annotation.*;
  22 +import org.thingsboard.server.common.data.EntityType;
  23 +import org.thingsboard.server.common.data.audit.ActionType;
  24 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  25 +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId;
  26 +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate;
  27 +import org.thingsboard.server.queue.util.TbCoreComponent;
  28 +import org.thingsboard.server.service.security.permission.Operation;
  29 +import org.thingsboard.server.service.security.permission.Resource;
  30 +
  31 +import java.util.List;
  32 +
  33 +@RestController
  34 +@TbCoreComponent
  35 +@RequestMapping("/api/oauth2/config/template")
  36 +@Slf4j
  37 +public class OAuth2ConfigTemplateController extends BaseController {
  38 + private static final String CLIENT_REGISTRATION_TEMPLATE_ID = "clientRegistrationTemplateId";
  39 +
  40 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
  41 + @RequestMapping(method = RequestMethod.POST)
  42 + @ResponseStatus(value = HttpStatus.OK)
  43 + public OAuth2ClientRegistrationTemplate saveClientRegistrationTemplate(@RequestBody OAuth2ClientRegistrationTemplate clientRegistrationTemplate) throws ThingsboardException {
  44 + try {
  45 + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_TEMPLATE, Operation.WRITE);
  46 + return oAuth2ConfigTemplateService.saveClientRegistrationTemplate(clientRegistrationTemplate);
  47 + } catch (Exception e) {
  48 + throw handleException(e);
  49 + }
  50 + }
  51 +
  52 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
  53 + @RequestMapping(value = "/{clientRegistrationTemplateId}", method = RequestMethod.DELETE)
  54 + @ResponseStatus(value = HttpStatus.OK)
  55 + public void deleteClientRegistrationTemplate(@PathVariable(CLIENT_REGISTRATION_TEMPLATE_ID) String strClientRegistrationTemplateId) throws ThingsboardException {
  56 + checkParameter(CLIENT_REGISTRATION_TEMPLATE_ID, strClientRegistrationTemplateId);
  57 + try {
  58 + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_TEMPLATE, Operation.DELETE);
  59 + OAuth2ClientRegistrationTemplateId clientRegistrationTemplateId = new OAuth2ClientRegistrationTemplateId(toUUID(strClientRegistrationTemplateId));
  60 + oAuth2ConfigTemplateService.deleteClientRegistrationTemplateById(clientRegistrationTemplateId);
  61 + } catch (Exception e) {
  62 + throw handleException(e);
  63 + }
  64 + }
  65 +
  66 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
  67 + @RequestMapping(method = RequestMethod.GET, produces = "application/json")
  68 + @ResponseBody
  69 + public List<OAuth2ClientRegistrationTemplate> getClientRegistrationTemplates() throws ThingsboardException {
  70 + try {
  71 + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_TEMPLATE, Operation.READ);
  72 + return oAuth2ConfigTemplateService.findAllClientRegistrationTemplates();
  73 + } catch (Exception e) {
  74 + throw handleException(e);
  75 + }
  76 + }
  77 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.http.HttpStatus;
  20 +import org.springframework.security.access.prepost.PreAuthorize;
  21 +import org.springframework.web.bind.annotation.*;
  22 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  23 +import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
  24 +import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams;
  25 +import org.thingsboard.server.common.data.oauth2.SchemeType;
  26 +import org.thingsboard.server.queue.util.TbCoreComponent;
  27 +import org.thingsboard.server.service.security.permission.Operation;
  28 +import org.thingsboard.server.service.security.permission.Resource;
  29 +import org.thingsboard.server.utils.MiscUtils;
  30 +
  31 +import javax.servlet.http.HttpServletRequest;
  32 +import java.util.List;
  33 +
  34 +@RestController
  35 +@TbCoreComponent
  36 +@RequestMapping("/api")
  37 +@Slf4j
  38 +public class OAuth2Controller extends BaseController {
  39 + @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST)
  40 + @ResponseBody
  41 + public List<OAuth2ClientInfo> getOAuth2Clients(HttpServletRequest request) throws ThingsboardException {
  42 + try {
  43 + return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainName(request));
  44 + } catch (Exception e) {
  45 + throw handleException(e);
  46 + }
  47 + }
  48 +
  49 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
  50 + @RequestMapping(value = "/oauth2/config", method = RequestMethod.GET, produces = "application/json")
  51 + @ResponseBody
  52 + public OAuth2ClientsParams getCurrentOAuth2Params() throws ThingsboardException {
  53 + try {
  54 + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.READ);
  55 + return oAuth2Service.findOAuth2Params();
  56 + } catch (Exception e) {
  57 + throw handleException(e);
  58 + }
  59 + }
  60 +
  61 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
  62 + @RequestMapping(value = "/oauth2/config", method = RequestMethod.POST)
  63 + @ResponseStatus(value = HttpStatus.OK)
  64 + public OAuth2ClientsParams saveOAuth2Params(@RequestBody OAuth2ClientsParams oauth2Params) throws ThingsboardException {
  65 + try {
  66 + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.WRITE);
  67 + oAuth2Service.saveOAuth2Params(oauth2Params);
  68 + return oAuth2Service.findOAuth2Params();
  69 + } catch (Exception e) {
  70 + throw handleException(e);
  71 + }
  72 + }
  73 +}
... ...
... ... @@ -184,6 +184,7 @@ public class ThingsboardInstallService {
184 184 dataUpdateService.updateData("3.1.1");
185 185 log.info("Updating system data...");
186 186 systemDataLoaderService.updateSystemWidgets();
  187 + systemDataLoaderService.createOAuth2Templates();
187 188 break;
188 189 default:
189 190 throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion);
... ... @@ -216,6 +217,7 @@ public class ThingsboardInstallService {
216 217 systemDataLoaderService.createDefaultTenantProfiles();
217 218 systemDataLoaderService.createAdminSettings();
218 219 systemDataLoaderService.loadSystemWidgets();
  220 + systemDataLoaderService.createOAuth2Templates();
219 221 // systemDataLoaderService.loadSystemPlugins();
220 222 // systemDataLoaderService.loadSystemRules();
221 223
... ...
... ... @@ -194,6 +194,11 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
194 194 }
195 195
196 196 @Override
  197 + public void createOAuth2Templates() throws Exception {
  198 + installScripts.createOAuth2Templates();
  199 + }
  200 +
  201 + @Override
197 202 public void loadDemoData() throws Exception {
198 203 Tenant demoTenant = new Tenant();
199 204 demoTenant.setRegion("Global");
... ...
... ... @@ -26,11 +26,13 @@ import org.thingsboard.server.common.data.id.CustomerId;
26 26 import org.thingsboard.server.common.data.id.EntityId;
27 27 import org.thingsboard.server.common.data.id.RuleChainId;
28 28 import org.thingsboard.server.common.data.id.TenantId;
  29 +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate;
29 30 import org.thingsboard.server.common.data.rule.RuleChain;
30 31 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
31 32 import org.thingsboard.server.common.data.widget.WidgetType;
32 33 import org.thingsboard.server.common.data.widget.WidgetsBundle;
33 34 import org.thingsboard.server.dao.dashboard.DashboardService;
  35 +import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
34 36 import org.thingsboard.server.dao.rule.RuleChainService;
35 37 import org.thingsboard.server.dao.widget.WidgetTypeService;
36 38 import org.thingsboard.server.dao.widget.WidgetsBundleService;
... ... @@ -61,6 +63,7 @@ public class InstallScripts {
61 63 public static final String DEMO_DIR = "demo";
62 64 public static final String RULE_CHAINS_DIR = "rule_chains";
63 65 public static final String WIDGET_BUNDLES_DIR = "widget_bundles";
  66 + public static final String OAUTH2_CONFIG_TEMPLATES_DIR = "oauth2_config_templates";
64 67 public static final String DASHBOARDS_DIR = "dashboards";
65 68
66 69 public static final String JSON_EXT = ".json";
... ... @@ -80,6 +83,9 @@ public class InstallScripts {
80 83 @Autowired
81 84 private WidgetsBundleService widgetsBundleService;
82 85
  86 + @Autowired
  87 + private OAuth2ConfigTemplateService oAuth2TemplateService;
  88 +
83 89 public Path getTenantRuleChainsDir() {
84 90 return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR);
85 91 }
... ... @@ -228,4 +234,22 @@ public class InstallScripts {
228 234 throw new RuntimeException("Unable to load dashboard from json", e);
229 235 }
230 236 }
  237 +
  238 + public void createOAuth2Templates() throws Exception {
  239 + Path oauth2ConfigTemplatesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, OAUTH2_CONFIG_TEMPLATES_DIR);
  240 + try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(oauth2ConfigTemplatesDir, path -> path.toString().endsWith(JSON_EXT))) {
  241 + dirStream.forEach(
  242 + path -> {
  243 + try {
  244 + JsonNode oauth2ConfigTemplateJson = objectMapper.readTree(path.toFile());
  245 + OAuth2ClientRegistrationTemplate clientRegistrationTemplate = objectMapper.treeToValue(oauth2ConfigTemplateJson, OAuth2ClientRegistrationTemplate.class);
  246 + oAuth2TemplateService.saveClientRegistrationTemplate(clientRegistrationTemplate);
  247 + } catch (Exception e) {
  248 + log.error("Unable to load oauth2 config templates from json: [{}]", path.toString());
  249 + throw new RuntimeException("Unable to load oauth2 config templates from json", e);
  250 + }
  251 + }
  252 + );
  253 + }
  254 + }
231 255 }
... ...
... ... @@ -23,6 +23,8 @@ public interface SystemDataLoaderService {
23 23
24 24 void createAdminSettings() throws Exception;
25 25
  26 + void createOAuth2Templates() throws Exception;
  27 +
26 28 void loadSystemWidgets() throws Exception;
27 29
28 30 void updateSystemWidgets() throws Exception;
... ...
... ... @@ -17,7 +17,6 @@ package org.thingsboard.server.service.security.auth.oauth2;
17 17
18 18 import com.fasterxml.jackson.databind.ObjectMapper;
19 19 import com.fasterxml.jackson.databind.node.ObjectNode;
20   -import com.google.common.base.Strings;
21 20 import lombok.extern.slf4j.Slf4j;
22 21 import org.springframework.beans.factory.annotation.Autowired;
23 22 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
... ... @@ -34,7 +33,6 @@ import org.thingsboard.server.common.data.id.IdBased;
34 33 import org.thingsboard.server.common.data.id.TenantId;
35 34 import org.thingsboard.server.common.data.page.PageData;
36 35 import org.thingsboard.server.common.data.page.PageLink;
37   -import org.thingsboard.server.common.data.page.TimePageLink;
38 36 import org.thingsboard.server.common.data.security.Authority;
39 37 import org.thingsboard.server.common.data.security.UserCredentials;
40 38 import org.thingsboard.server.dao.customer.CustomerService;
... ... @@ -49,7 +47,6 @@ import org.thingsboard.server.service.security.model.UserPrincipal;
49 47 import java.io.IOException;
50 48 import java.util.List;
51 49 import java.util.Optional;
52   -import java.util.concurrent.ExecutionException;
53 50 import java.util.concurrent.locks.Lock;
54 51 import java.util.concurrent.locks.ReentrantLock;
55 52
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.security.auth.oauth2;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.apache.commons.lang3.text.StrSubstitutor;
  20 +import org.springframework.util.StringUtils;
  21 +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
  22 +import org.thingsboard.server.dao.oauth2.OAuth2User;
  23 +
  24 +import java.util.Map;
  25 +
  26 +@Slf4j
  27 +public class BasicMapperUtils {
  28 + private static final String START_PLACEHOLDER_PREFIX = "%{";
  29 + private static final String END_PLACEHOLDER_PREFIX = "}";
  30 +
  31 + public static OAuth2User getOAuth2User(String email, Map<String, Object> attributes, OAuth2MapperConfig config) {
  32 + OAuth2User oauth2User = new OAuth2User();
  33 + oauth2User.setEmail(email);
  34 + oauth2User.setTenantName(getTenantName(email, attributes, config));
  35 + if (!StringUtils.isEmpty(config.getBasic().getLastNameAttributeKey())) {
  36 + String lastName = getStringAttributeByKey(attributes, config.getBasic().getLastNameAttributeKey());
  37 + oauth2User.setLastName(lastName);
  38 + }
  39 + if (!StringUtils.isEmpty(config.getBasic().getFirstNameAttributeKey())) {
  40 + String firstName = getStringAttributeByKey(attributes, config.getBasic().getFirstNameAttributeKey());
  41 + oauth2User.setFirstName(firstName);
  42 + }
  43 + if (!StringUtils.isEmpty(config.getBasic().getCustomerNamePattern())) {
  44 + StrSubstitutor sub = new StrSubstitutor(attributes, START_PLACEHOLDER_PREFIX, END_PLACEHOLDER_PREFIX);
  45 + String customerName = sub.replace(config.getBasic().getCustomerNamePattern());
  46 + oauth2User.setCustomerName(customerName);
  47 + }
  48 + oauth2User.setAlwaysFullScreen(config.getBasic().isAlwaysFullScreen());
  49 + if (!StringUtils.isEmpty(config.getBasic().getDefaultDashboardName())) {
  50 + oauth2User.setDefaultDashboardName(config.getBasic().getDefaultDashboardName());
  51 + }
  52 + return oauth2User;
  53 + }
  54 +
  55 + public static String getTenantName(String email, Map<String, Object> attributes, OAuth2MapperConfig config) {
  56 + switch (config.getBasic().getTenantNameStrategy()) {
  57 + case EMAIL:
  58 + return email;
  59 + case DOMAIN:
  60 + return email.substring(email .indexOf("@") + 1);
  61 + case CUSTOM:
  62 + StrSubstitutor sub = new StrSubstitutor(attributes, START_PLACEHOLDER_PREFIX, END_PLACEHOLDER_PREFIX);
  63 + return sub.replace(config.getBasic().getTenantNamePattern());
  64 + default:
  65 + throw new RuntimeException("Tenant Name Strategy with type " + config.getBasic().getTenantNameStrategy() + " is not supported!");
  66 + }
  67 + }
  68 +
  69 + public static String getStringAttributeByKey(Map<String, Object> attributes, String key) {
  70 + String result = null;
  71 + try {
  72 + result = (String) attributes.get(key);
  73 + } catch (Exception e) {
  74 + log.warn("Can't convert attribute to String by key " + key);
  75 + }
  76 + return result;
  77 + }
  78 +}
... ...
... ... @@ -16,11 +16,9 @@
16 16 package org.thingsboard.server.service.security.auth.oauth2;
17 17
18 18 import lombok.extern.slf4j.Slf4j;
19   -import org.apache.commons.lang3.text.StrSubstitutor;
20 19 import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
21 20 import org.springframework.stereotype.Service;
22   -import org.springframework.util.StringUtils;
23   -import org.thingsboard.server.dao.oauth2.OAuth2ClientMapperConfig;
  21 +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
24 22 import org.thingsboard.server.dao.oauth2.OAuth2User;
25 23 import org.thingsboard.server.service.security.model.SecurityUser;
26 24
... ... @@ -30,62 +28,12 @@ import java.util.Map;
30 28 @Slf4j
31 29 public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper {
32 30
33   - private static final String START_PLACEHOLDER_PREFIX = "%{";
34   - private static final String END_PLACEHOLDER_PREFIX = "}";
35   - private static final String EMAIL_TENANT_STRATEGY = "email";
36   - private static final String DOMAIN_TENANT_STRATEGY = "domain";
37   - private static final String CUSTOM_TENANT_STRATEGY = "custom";
38   -
39 31 @Override
40   - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, OAuth2ClientMapperConfig config) {
41   - OAuth2User oauth2User = new OAuth2User();
  32 + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config) {
42 33 Map<String, Object> attributes = token.getPrincipal().getAttributes();
43   - String email = getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey());
44   - oauth2User.setEmail(email);
45   - oauth2User.setTenantName(getTenantName(attributes, config));
46   - if (!StringUtils.isEmpty(config.getBasic().getLastNameAttributeKey())) {
47   - String lastName = getStringAttributeByKey(attributes, config.getBasic().getLastNameAttributeKey());
48   - oauth2User.setLastName(lastName);
49   - }
50   - if (!StringUtils.isEmpty(config.getBasic().getFirstNameAttributeKey())) {
51   - String firstName = getStringAttributeByKey(attributes, config.getBasic().getFirstNameAttributeKey());
52   - oauth2User.setFirstName(firstName);
53   - }
54   - if (!StringUtils.isEmpty(config.getBasic().getCustomerNamePattern())) {
55   - StrSubstitutor sub = new StrSubstitutor(attributes, START_PLACEHOLDER_PREFIX, END_PLACEHOLDER_PREFIX);
56   - String customerName = sub.replace(config.getBasic().getCustomerNamePattern());
57   - oauth2User.setCustomerName(customerName);
58   - }
59   - oauth2User.setAlwaysFullScreen(config.getBasic().isAlwaysFullScreen());
60   - if (!StringUtils.isEmpty(config.getBasic().getDefaultDashboardName())) {
61   - oauth2User.setDefaultDashboardName(config.getBasic().getDefaultDashboardName());
62   - }
  34 + String email = BasicMapperUtils.getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey());
  35 + OAuth2User oauth2User = BasicMapperUtils.getOAuth2User(email, attributes, config);
63 36
64 37 return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser());
65 38 }
66   -
67   - private String getTenantName(Map<String, Object> attributes, OAuth2ClientMapperConfig config) {
68   - switch (config.getBasic().getTenantNameStrategy()) {
69   - case EMAIL_TENANT_STRATEGY:
70   - return getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey());
71   - case DOMAIN_TENANT_STRATEGY:
72   - String email = getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey());
73   - return email.substring(email .indexOf("@") + 1);
74   - case CUSTOM_TENANT_STRATEGY:
75   - StrSubstitutor sub = new StrSubstitutor(attributes, START_PLACEHOLDER_PREFIX, END_PLACEHOLDER_PREFIX);
76   - return sub.replace(config.getBasic().getTenantNamePattern());
77   - default:
78   - throw new RuntimeException("Tenant Name Strategy with type " + config.getBasic().getTenantNameStrategy() + " is not supported!");
79   - }
80   - }
81   -
82   - private String getStringAttributeByKey(Map<String, Object> attributes, String key) {
83   - String result = null;
84   - try {
85   - result = (String) attributes.get(key);
86   - } catch (Exception e) {
87   - log.warn("Can't convert attribute to String by key " + key);
88   - }
89   - return result;
90   - }
91 39 }
... ...
... ... @@ -23,28 +23,34 @@ import org.springframework.security.oauth2.client.authentication.OAuth2Authentic
23 23 import org.springframework.stereotype.Service;
24 24 import org.springframework.util.StringUtils;
25 25 import org.springframework.web.client.RestTemplate;
26   -import org.thingsboard.server.dao.oauth2.OAuth2ClientMapperConfig;
  26 +import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig;
  27 +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
27 28 import org.thingsboard.server.dao.oauth2.OAuth2User;
28 29 import org.thingsboard.server.service.security.model.SecurityUser;
29 30
30 31 @Service(value = "customOAuth2ClientMapper")
31 32 @Slf4j
32 33 public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper {
  34 + private static final String PROVIDER_ACCESS_TOKEN = "provider-access-token";
33 35
34 36 private static final ObjectMapper json = new ObjectMapper();
35 37
36 38 private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
37 39
38 40 @Override
39   - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, OAuth2ClientMapperConfig config) {
40   - OAuth2User oauth2User = getOAuth2User(token, config.getCustom());
  41 + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config) {
  42 + OAuth2User oauth2User = getOAuth2User(token, providerAccessToken, config.getCustom());
41 43 return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser());
42 44 }
43 45
44   - private synchronized OAuth2User getOAuth2User(OAuth2AuthenticationToken token, OAuth2ClientMapperConfig.CustomOAuth2ClientMapperConfig custom) {
  46 + private synchronized OAuth2User getOAuth2User(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2CustomMapperConfig custom) {
45 47 if (!StringUtils.isEmpty(custom.getUsername()) && !StringUtils.isEmpty(custom.getPassword())) {
46 48 restTemplateBuilder = restTemplateBuilder.basicAuthentication(custom.getUsername(), custom.getPassword());
47 49 }
  50 + if (custom.isSendToken() && !StringUtils.isEmpty(providerAccessToken)) {
  51 + restTemplateBuilder = restTemplateBuilder.defaultHeader(PROVIDER_ACCESS_TOKEN, providerAccessToken);
  52 + }
  53 +
48 54 RestTemplate restTemplate = restTemplateBuilder.build();
49 55 String request;
50 56 try {
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.security.auth.oauth2;
  17 +
  18 +import lombok.Data;
  19 +import lombok.ToString;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.springframework.beans.factory.annotation.Autowired;
  22 +import org.springframework.boot.web.client.RestTemplateBuilder;
  23 +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
  24 +import org.springframework.stereotype.Service;
  25 +import org.springframework.web.client.RestTemplate;
  26 +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
  27 +import org.thingsboard.server.dao.oauth2.OAuth2Configuration;
  28 +import org.thingsboard.server.dao.oauth2.OAuth2User;
  29 +import org.thingsboard.server.service.security.model.SecurityUser;
  30 +
  31 +import java.util.ArrayList;
  32 +import java.util.Map;
  33 +import java.util.Optional;
  34 +
  35 +@Service(value = "githubOAuth2ClientMapper")
  36 +@Slf4j
  37 +public class GithubOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper {
  38 + private static final String EMAIL_URL_KEY = "emailUrl";
  39 +
  40 + private static final String AUTHORIZATION = "Authorization";
  41 +
  42 + private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
  43 +
  44 + @Autowired
  45 + private OAuth2Configuration oAuth2Configuration;
  46 +
  47 + @Override
  48 + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config) {
  49 + Map<String, String> githubMapperConfig = oAuth2Configuration.getGithubMapper();
  50 + String email = getEmail(githubMapperConfig.get(EMAIL_URL_KEY), providerAccessToken);
  51 + Map<String, Object> attributes = token.getPrincipal().getAttributes();
  52 + OAuth2User oAuth2User = BasicMapperUtils.getOAuth2User(email, attributes, config);
  53 + return getOrCreateSecurityUserFromOAuth2User(oAuth2User, config.isAllowUserCreation(), config.isActivateUser());
  54 + }
  55 +
  56 + private synchronized String getEmail(String emailUrl, String oauth2Token) {
  57 + restTemplateBuilder = restTemplateBuilder.defaultHeader(AUTHORIZATION, "token " + oauth2Token);
  58 +
  59 + RestTemplate restTemplate = restTemplateBuilder.build();
  60 + GithubEmailsResponse githubEmailsResponse;
  61 + try {
  62 + githubEmailsResponse = restTemplate.getForEntity(emailUrl, GithubEmailsResponse.class).getBody();
  63 + if (githubEmailsResponse == null){
  64 + throw new RuntimeException("Empty Github response!");
  65 + }
  66 + } catch (Exception e) {
  67 + log.error("There was an error during connection to Github API", e);
  68 + throw new RuntimeException("Unable to login. Please contact your Administrator!");
  69 + }
  70 + Optional<String> emailOpt = githubEmailsResponse.stream()
  71 + .filter(GithubEmailResponse::isPrimary)
  72 + .map(GithubEmailResponse::getEmail)
  73 + .findAny();
  74 + if (emailOpt.isPresent()){
  75 + return emailOpt.get();
  76 + } else {
  77 + log.error("Could not find primary email from {}.", githubEmailsResponse);
  78 + throw new RuntimeException("Unable to login. Please contact your Administrator!");
  79 + }
  80 + }
  81 + private static class GithubEmailsResponse extends ArrayList<GithubEmailResponse> {}
  82 +
  83 + @Data
  84 + @ToString
  85 + private static class GithubEmailResponse {
  86 + private String email;
  87 + private boolean verified;
  88 + private boolean primary;
  89 + private String visibility;
  90 + }
  91 +}
... ...
... ... @@ -16,9 +16,9 @@
16 16 package org.thingsboard.server.service.security.auth.oauth2;
17 17
18 18 import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
19   -import org.thingsboard.server.dao.oauth2.OAuth2ClientMapperConfig;
  19 +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
20 20 import org.thingsboard.server.service.security.model.SecurityUser;
21 21
22 22 public interface OAuth2ClientMapper {
23   - SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, OAuth2ClientMapperConfig config);
  23 + SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config);
24 24 }
... ...
... ... @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j;
19 19 import org.springframework.beans.factory.annotation.Autowired;
20 20 import org.springframework.beans.factory.annotation.Qualifier;
21 21 import org.springframework.stereotype.Component;
  22 +import org.thingsboard.server.common.data.oauth2.MapperType;
22 23
23 24 @Component
24 25 @Slf4j
... ... @@ -32,14 +33,20 @@ public class OAuth2ClientMapperProvider {
32 33 @Qualifier("customOAuth2ClientMapper")
33 34 private OAuth2ClientMapper customOAuth2ClientMapper;
34 35
35   - public OAuth2ClientMapper getOAuth2ClientMapperByType(String oauth2ClientType) {
36   - switch (oauth2ClientType) {
37   - case "custom":
  36 + @Autowired
  37 + @Qualifier("githubOAuth2ClientMapper")
  38 + private OAuth2ClientMapper githubOAuth2ClientMapper;
  39 +
  40 + public OAuth2ClientMapper getOAuth2ClientMapperByType(MapperType oauth2MapperType) {
  41 + switch (oauth2MapperType) {
  42 + case CUSTOM:
38 43 return customOAuth2ClientMapper;
39   - case "basic":
  44 + case BASIC:
40 45 return basicOAuth2ClientMapper;
  46 + case GITHUB:
  47 + return githubOAuth2ClientMapper;
41 48 default:
42   - throw new RuntimeException("OAuth2ClientMapper with type " + oauth2ClientType + " is not supported!");
  49 + throw new RuntimeException("OAuth2ClientRegistrationMapper with type " + oauth2MapperType + " is not supported!");
43 50 }
44 51 }
45 52 }
... ...
... ... @@ -16,13 +16,14 @@
16 16 package org.thingsboard.server.service.security.auth.oauth2;
17 17
18 18 import org.springframework.beans.factory.annotation.Autowired;
19   -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
20 19 import org.springframework.security.core.Authentication;
  20 +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
  21 +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
21 22 import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
22 23 import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
23 24 import org.springframework.stereotype.Component;
24   -import org.thingsboard.server.dao.oauth2.OAuth2Client;
25   -import org.thingsboard.server.dao.oauth2.OAuth2Configuration;
  25 +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
  26 +import org.thingsboard.server.dao.oauth2.OAuth2Service;
26 27 import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
27 28 import org.thingsboard.server.service.security.model.SecurityUser;
28 29 import org.thingsboard.server.service.security.model.token.JwtToken;
... ... @@ -34,25 +35,28 @@ import javax.servlet.http.HttpServletResponse;
34 35 import java.io.IOException;
35 36 import java.net.URLEncoder;
36 37 import java.nio.charset.StandardCharsets;
  38 +import java.util.UUID;
37 39
38 40 @Component(value = "oauth2AuthenticationSuccessHandler")
39   -@ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true")
40 41 public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
41 42
42 43 private final JwtTokenFactory tokenFactory;
43 44 private final RefreshTokenRepository refreshTokenRepository;
44 45 private final OAuth2ClientMapperProvider oauth2ClientMapperProvider;
45   - private final OAuth2Configuration oauth2Configuration;
  46 + private final OAuth2Service oAuth2Service;
  47 + private final OAuth2AuthorizedClientService oAuth2AuthorizedClientService;
46 48
47 49 @Autowired
48 50 public Oauth2AuthenticationSuccessHandler(final JwtTokenFactory tokenFactory,
49 51 final RefreshTokenRepository refreshTokenRepository,
50 52 final OAuth2ClientMapperProvider oauth2ClientMapperProvider,
51   - final OAuth2Configuration oauth2Configuration) {
  53 + final OAuth2Service oAuth2Service,
  54 + final OAuth2AuthorizedClientService oAuth2AuthorizedClientService) {
52 55 this.tokenFactory = tokenFactory;
53 56 this.refreshTokenRepository = refreshTokenRepository;
54 57 this.oauth2ClientMapperProvider = oauth2ClientMapperProvider;
55   - this.oauth2Configuration = oauth2Configuration;
  58 + this.oAuth2Service = oAuth2Service;
  59 + this.oAuth2AuthorizedClientService = oAuth2AuthorizedClientService;
56 60 }
57 61
58 62 @Override
... ... @@ -64,9 +68,13 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
64 68 try {
65 69 OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
66 70
67   - OAuth2Client oauth2Client = oauth2Configuration.getClientByRegistrationId(token.getAuthorizedClientRegistrationId());
68   - OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(oauth2Client.getMapperConfig().getType());
69   - SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, oauth2Client.getMapperConfig());
  71 + OAuth2ClientRegistrationInfo clientRegistration = oAuth2Service.findClientRegistrationInfo(UUID.fromString(token.getAuthorizedClientRegistrationId()));
  72 + OAuth2AuthorizedClient oAuth2AuthorizedClient = oAuth2AuthorizedClientService.loadAuthorizedClient(
  73 + token.getAuthorizedClientRegistrationId(),
  74 + token.getPrincipal().getName());
  75 + OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(clientRegistration.getMapperConfig().getType());
  76 + SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, oAuth2AuthorizedClient.getAccessToken().getTokenValue(),
  77 + clientRegistration.getMapperConfig());
70 78
71 79 JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
72 80 JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
... ...
... ... @@ -32,6 +32,8 @@ public enum Resource {
32 32 USER(EntityType.USER),
33 33 WIDGETS_BUNDLE(EntityType.WIDGETS_BUNDLE),
34 34 WIDGET_TYPE(EntityType.WIDGET_TYPE),
  35 + OAUTH2_CONFIGURATION_INFO(),
  36 + OAUTH2_CONFIGURATION_TEMPLATE(),
35 37 TENANT_PROFILE(EntityType.TENANT_PROFILE),
36 38 DEVICE_PROFILE(EntityType.DEVICE_PROFILE);
37 39
... ...
... ... @@ -19,14 +19,10 @@ import org.springframework.stereotype.Component;
19 19 import org.thingsboard.server.common.data.HasTenantId;
20 20 import org.thingsboard.server.common.data.User;
21 21 import org.thingsboard.server.common.data.id.EntityId;
22   -import org.thingsboard.server.common.data.id.TenantId;
23 22 import org.thingsboard.server.common.data.id.UserId;
24 23 import org.thingsboard.server.common.data.security.Authority;
25 24 import org.thingsboard.server.service.security.model.SecurityUser;
26 25
27   -import java.util.HashMap;
28   -import java.util.Optional;
29   -
30 26 @Component(value="sysAdminPermissions")
31 27 public class SysAdminPermissions extends AbstractPermissions {
32 28
... ... @@ -39,6 +35,8 @@ public class SysAdminPermissions extends AbstractPermissions {
39 35 put(Resource.USER, userPermissionChecker);
40 36 put(Resource.WIDGETS_BUNDLE, systemEntityPermissionChecker);
41 37 put(Resource.WIDGET_TYPE, systemEntityPermissionChecker);
  38 + put(Resource.OAUTH2_CONFIGURATION_INFO, PermissionChecker.allowAllPermissionChecker);
  39 + put(Resource.OAUTH2_CONFIGURATION_TEMPLATE, PermissionChecker.allowAllPermissionChecker);
42 40 put(Resource.TENANT_PROFILE, PermissionChecker.allowAllPermissionChecker);
43 41 }
44 42
... ...
... ... @@ -19,13 +19,10 @@ import org.springframework.stereotype.Component;
19 19 import org.thingsboard.server.common.data.HasTenantId;
20 20 import org.thingsboard.server.common.data.User;
21 21 import org.thingsboard.server.common.data.id.EntityId;
22   -import org.thingsboard.server.common.data.id.TenantId;
23 22 import org.thingsboard.server.common.data.id.UserId;
24 23 import org.thingsboard.server.common.data.security.Authority;
25 24 import org.thingsboard.server.service.security.model.SecurityUser;
26 25
27   -import java.util.HashMap;
28   -
29 26 @Component(value="tenantAdminPermissions")
30 27 public class TenantAdminPermissions extends AbstractPermissions {
31 28
... ...
... ... @@ -49,12 +49,27 @@ public class MiscUtils {
49 49 }
50 50
51 51 public static String constructBaseUrl(HttpServletRequest request) {
52   - String scheme = request.getScheme();
  52 + return String.format("%s://%s:%d",
  53 + getScheme(request),
  54 + getDomainName(request),
  55 + getPort(request));
  56 + }
53 57
  58 + public static String getScheme(HttpServletRequest request){
  59 + String scheme = request.getScheme();
54 60 String forwardedProto = request.getHeader("x-forwarded-proto");
55 61 if (forwardedProto != null) {
56 62 scheme = forwardedProto;
57 63 }
  64 + return scheme;
  65 + }
  66 +
  67 + public static String getDomainName(HttpServletRequest request){
  68 + return request.getServerName();
  69 + }
  70 +
  71 + public static int getPort(HttpServletRequest request){
  72 + String forwardedProto = request.getHeader("x-forwarded-proto");
58 73
59 74 int serverPort = request.getServerPort();
60 75 if (request.getHeader("x-forwarded-port") != null) {
... ... @@ -72,11 +87,6 @@ public class MiscUtils {
72 87 break;
73 88 }
74 89 }
75   -
76   - String baseUrl = String.format("%s://%s:%d",
77   - scheme,
78   - request.getServerName(),
79   - serverPort);
80   - return baseUrl;
  90 + return serverPort;
81 91 }
82 92 }
... ...
... ... @@ -113,62 +113,10 @@ security:
113 113 basic:
114 114 enabled: "${SECURITY_BASIC_ENABLED:false}"
115 115 oauth2:
116   - # Enable/disable OAuth 2 login functionality
117   - # For details please refer to https://thingsboard.io/docs/user-guide/oauth-2-support/
118   - enabled: "${SECURITY_OAUTH2_ENABLED:false}"
119 116 # Redirect URL where access code from external user management system will be processed
120 117 loginProcessingUrl: "${SECURITY_OAUTH2_LOGIN_PROCESSING_URL:/login/oauth2/code/}"
121   - # List of SSO clients
122   - clients:
123   - default:
124   - # Label that going to be show on login button - 'Login with {loginButtonLabel}'
125   - loginButtonLabel: "${SECURITY_OAUTH2_DEFAULT_LOGIN_BUTTON_LABEL:Default}"
126   - # Icon that going to be show on login button. Material design icon ID (https://material.angularjs.org/latest/api/directive/mdIcon)
127   - loginButtonIcon: "${SECURITY_OAUTH2_DEFAULT_LOGIN_BUTTON_ICON:}"
128   - clientName: "${SECURITY_OAUTH2_DEFAULT_CLIENT_NAME:ClientName}"
129   - clientId: "${SECURITY_OAUTH2_DEFAULT_CLIENT_ID:}"
130   - clientSecret: "${SECURITY_OAUTH2_DEFAULT_CLIENT_SECRET:}"
131   - accessTokenUri: "${SECURITY_OAUTH2_DEFAULT_ACCESS_TOKEN_URI:}"
132   - authorizationUri: "${SECURITY_OAUTH2_DEFAULT_AUTHORIZATION_URI:}"
133   - scope: "${SECURITY_OAUTH2_DEFAULT_SCOPE:}"
134   - # Redirect URL that must be in sync with 'security.oauth2.loginProcessingUrl', but domain name added
135   - redirectUriTemplate: "${SECURITY_OAUTH2_DEFAULT_REDIRECT_URI_TEMPLATE:http://localhost:8080/login/oauth2/code/}"
136   - jwkSetUri: "${SECURITY_OAUTH2_DEFAULT_JWK_SET_URI:}"
137   - # 'authorization_code', 'implicit', 'refresh_token' or 'client_credentials'
138   - authorizationGrantType: "${SECURITY_OAUTH2_DEFAULT_AUTHORIZATION_GRANT_TYPE:authorization_code}"
139   - clientAuthenticationMethod: "${SECURITY_OAUTH2_DEFAULT_CLIENT_AUTHENTICATION_METHOD:post}" # basic or post
140   - userInfoUri: "${SECURITY_OAUTH2_DEFAULT_USER_INFO_URI:}"
141   - userNameAttributeName: "${SECURITY_OAUTH2_DEFAULT_USER_NAME_ATTRIBUTE_NAME:email}"
142   - mapperConfig:
143   - # Allows to create user if it not exists
144   - allowUserCreation: "${SECURITY_OAUTH2_DEFAULT_MAPPER_ALLOW_USER_CREATION:true}"
145   - # Allows user to setup ThingsBoard internal password and login over default Login window
146   - activateUser: "${SECURITY_OAUTH2_DEFAULT_MAPPER_ACTIVATE_USER:false}"
147   - # Mapper type of converter from external user into internal - 'basic' or 'custom'
148   - type: "${SECURITY_OAUTH2_DEFAULT_MAPPER_TYPE:basic}"
149   - basic:
150   - # Key from attributes of external user object to use as email
151   - emailAttributeKey: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_EMAIL_ATTRIBUTE_KEY:email}"
152   - firstNameAttributeKey: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_FIRST_NAME_ATTRIBUTE_KEY:}"
153   - lastNameAttributeKey: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_LAST_NAME_ATTRIBUTE_KEY:}"
154   - # Strategy for generating Tenant from external user object - 'domain', 'email' or 'custom'
155   - # 'domain' - name of the Tenant will be extracted as domain from the email of the user
156   - # 'email' - name of the Tenant will email of the user
157   - # 'custom' - please configure 'tenantNamePattern' for custom mapping
158   - tenantNameStrategy: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_TENANT_NAME_STRATEGY:domain}"
159   - # %{attribute_key} as placeholder for attribute value of attributes of external user object
160   - tenantNamePattern: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_TENANT_NAME_PATTERN:}"
161   - # If this field is not empty, user will be created as a user under defined Customer
162   - # %{attribute_key} as placeholder for attribute value of attributes of external user object
163   - customerNamePattern: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_CUSTOMER_NAME_PATTERN:}"
164   - # If this field is not empty, user will be created with default defined Dashboard
165   - defaultDashboardName: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_DEFAULT_DASHBOARD_NAME:}"
166   - # If this field is set 'true' along with non-empty 'defaultDashboardName', user will start from the defined Dashboard in fullscreen mode
167   - alwaysFullScreen: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_ALWAYS_FULL_SCREEN:false}"
168   - custom:
169   - url: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_URL:}"
170   - username: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_USERNAME:}"
171   - password: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_PASSWORD:}"
  118 + githubMapper:
  119 + emailUrl: "${SECURITY_OAUTH2_GITHUB_MAPPER_EMAIL_URL_KEY:https://api.github.com/user/emails}"
172 120
173 121 # Dashboard parameters
174 122 dashboard:
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.oauth2;
  17 +
  18 +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId;
  19 +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate;
  20 +
  21 +import java.util.List;
  22 +
  23 +public interface OAuth2ConfigTemplateService {
  24 + OAuth2ClientRegistrationTemplate saveClientRegistrationTemplate(OAuth2ClientRegistrationTemplate clientRegistrationTemplate);
  25 +
  26 + OAuth2ClientRegistrationTemplate findClientRegistrationTemplateById(OAuth2ClientRegistrationTemplateId templateId);
  27 +
  28 + List<OAuth2ClientRegistrationTemplate> findAllClientRegistrationTemplates();
  29 +
  30 + void deleteClientRegistrationTemplateById(OAuth2ClientRegistrationTemplateId templateId);
  31 +}
... ...
... ... @@ -16,10 +16,20 @@
16 16 package org.thingsboard.server.dao.oauth2;
17 17
18 18 import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
  19 +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
  20 +import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams;
19 21
20 22 import java.util.List;
  23 +import java.util.UUID;
21 24
22 25 public interface OAuth2Service {
  26 + List<OAuth2ClientInfo> getOAuth2Clients(String domainScheme, String domainName);
23 27
24   - List<OAuth2ClientInfo> getOAuth2Clients();
  28 + void saveOAuth2Params(OAuth2ClientsParams oauth2Params);
  29 +
  30 + OAuth2ClientsParams findOAuth2Params();
  31 +
  32 + OAuth2ClientRegistrationInfo findClientRegistrationInfo(UUID id);
  33 +
  34 + List<OAuth2ClientRegistrationInfo> findAllClientRegistrationInfos();
25 35 }
... ...
... ... @@ -24,7 +24,7 @@ public interface AdminSettingsService {
24 24 AdminSettings findAdminSettingsById(TenantId tenantId, AdminSettingsId adminSettingsId);
25 25
26 26 AdminSettings findAdminSettingsByKey(TenantId tenantId, String key);
27   -
  27 +
28 28 AdminSettings saveAdminSettings(TenantId tenantId, AdminSettings adminSettings);
29   -
  29 +
30 30 }
... ...
common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationId.java renamed from common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2IntegrationId.java
... ... @@ -20,16 +20,14 @@ import com.fasterxml.jackson.annotation.JsonProperty;
20 20
21 21 import java.util.UUID;
22 22
23   -public class OAuth2IntegrationId extends UUIDBased {
24   -
25   - private static final long serialVersionUID = 1L;
  23 +public class OAuth2ClientRegistrationId extends UUIDBased {
26 24
27 25 @JsonCreator
28   - public OAuth2IntegrationId(@JsonProperty("id") UUID id) {
  26 + public OAuth2ClientRegistrationId(@JsonProperty("id") UUID id) {
29 27 super(id);
30 28 }
31 29
32   - public static OAuth2IntegrationId fromString(String oauth2IntegrationId) {
33   - return new OAuth2IntegrationId(UUID.fromString(oauth2IntegrationId));
  30 + public static OAuth2ClientRegistrationId fromString(String clientRegistrationId) {
  31 + return new OAuth2ClientRegistrationId(UUID.fromString(clientRegistrationId));
34 32 }
35 33 }
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.id;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonCreator;
  19 +import com.fasterxml.jackson.annotation.JsonProperty;
  20 +
  21 +import java.util.UUID;
  22 +
  23 +public class OAuth2ClientRegistrationInfoId extends UUIDBased {
  24 +
  25 + @JsonCreator
  26 + public OAuth2ClientRegistrationInfoId(@JsonProperty("id") UUID id) {
  27 + super(id);
  28 + }
  29 +
  30 + public static OAuth2ClientRegistrationInfoId fromString(String clientRegistrationInfoId) {
  31 + return new OAuth2ClientRegistrationInfoId(UUID.fromString(clientRegistrationInfoId));
  32 + }
  33 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.id;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonCreator;
  19 +import com.fasterxml.jackson.annotation.JsonProperty;
  20 +
  21 +import java.util.UUID;
  22 +
  23 +public class OAuth2ClientRegistrationTemplateId extends UUIDBased {
  24 +
  25 + @JsonCreator
  26 + public OAuth2ClientRegistrationTemplateId(@JsonProperty("id") UUID id) {
  27 + super(id);
  28 + }
  29 +
  30 + public static OAuth2ClientRegistrationTemplateId fromString(String clientRegistrationTemplateId) {
  31 + return new OAuth2ClientRegistrationTemplateId(UUID.fromString(clientRegistrationTemplateId));
  32 + }
  33 +}
... ...
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java renamed from dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Client.java
... ... @@ -13,27 +13,32 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.dao.oauth2;
  16 +package org.thingsboard.server.common.data.oauth2;
17 17
18   -import lombok.Data;
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import lombok.*;
  20 +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId;
19 21
20   -@Data
21   -public class OAuth2Client {
  22 +import java.util.List;
22 23
23   - private String loginButtonLabel;
24   - private String loginButtonIcon;
25   - private String clientName;
  24 +@EqualsAndHashCode
  25 +@Data
  26 +@ToString(exclude = {"clientSecret"})
  27 +@NoArgsConstructor
  28 +@AllArgsConstructor
  29 +@Builder
  30 +public class ClientRegistrationDto {
  31 + private OAuth2MapperConfig mapperConfig;
26 32 private String clientId;
27 33 private String clientSecret;
28   - private String accessTokenUri;
29 34 private String authorizationUri;
30   - private String scope;
31   - private String redirectUriTemplate;
32   - private String jwkSetUri;
33   - private String authorizationGrantType;
34   - private String clientAuthenticationMethod;
  35 + private String accessTokenUri;
  36 + private List<String> scope;
35 37 private String userInfoUri;
36 38 private String userNameAttributeName;
37   - private OAuth2ClientMapperConfig mapperConfig;
38   -
  39 + private String jwkSetUri;
  40 + private String clientAuthenticationMethod;
  41 + private String loginButtonLabel;
  42 + private String loginButtonIcon;
  43 + private JsonNode additionalInfo;
39 44 }
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +import lombok.*;
  19 +
  20 +@EqualsAndHashCode
  21 +@Data
  22 +@ToString
  23 +@NoArgsConstructor
  24 +@AllArgsConstructor
  25 +@Builder
  26 +public class DomainInfo {
  27 + private SchemeType scheme;
  28 + private String name;
  29 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +import lombok.Data;
  19 +import lombok.EqualsAndHashCode;
  20 +
  21 +@EqualsAndHashCode(callSuper = true)
  22 +@Data
  23 +public class ExtendedOAuth2ClientRegistrationInfo extends OAuth2ClientRegistrationInfo {
  24 +
  25 + private String domainName;
  26 + private SchemeType domainScheme;
  27 +
  28 + public ExtendedOAuth2ClientRegistrationInfo() {
  29 + super();
  30 + }
  31 +
  32 + public ExtendedOAuth2ClientRegistrationInfo(OAuth2ClientRegistrationInfo oAuth2ClientRegistrationInfo,
  33 + SchemeType domainScheme,
  34 + String domainName) {
  35 + super(oAuth2ClientRegistrationInfo);
  36 + this.domainScheme = domainScheme;
  37 + this.domainName = domainName;
  38 + }
  39 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +public enum MapperType {
  19 + BASIC, CUSTOM, GITHUB;
  20 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +import lombok.*;
  19 +
  20 +@Builder(toBuilder = true)
  21 +@EqualsAndHashCode
  22 +@Data
  23 +@ToString
  24 +public class OAuth2BasicMapperConfig {
  25 + private final String emailAttributeKey;
  26 + private final String firstNameAttributeKey;
  27 + private final String lastNameAttributeKey;
  28 + private final TenantNameStrategyType tenantNameStrategy;
  29 + private final String tenantNamePattern;
  30 + private final String customerNamePattern;
  31 + private final String defaultDashboardName;
  32 + private final boolean alwaysFullScreen;
  33 +}
... ...
... ... @@ -17,27 +17,20 @@ package org.thingsboard.server.common.data.oauth2;
17 17
18 18 import lombok.Data;
19 19 import lombok.EqualsAndHashCode;
20   -import org.thingsboard.server.common.data.BaseData;
21   -import org.thingsboard.server.common.data.id.OAuth2IntegrationId;
  20 +import lombok.NoArgsConstructor;
  21 +import lombok.AllArgsConstructor;
22 22
23   -@EqualsAndHashCode(callSuper = true)
  23 +@EqualsAndHashCode
24 24 @Data
25   -public class OAuth2ClientInfo extends BaseData<OAuth2IntegrationId> {
  25 +@NoArgsConstructor
  26 +@AllArgsConstructor
  27 +public class OAuth2ClientInfo {
26 28
27 29 private String name;
28 30 private String icon;
29 31 private String url;
30 32
31   - public OAuth2ClientInfo() {
32   - super();
33   - }
34   -
35   - public OAuth2ClientInfo(OAuth2IntegrationId id) {
36   - super(id);
37   - }
38   -
39 33 public OAuth2ClientInfo(OAuth2ClientInfo oauth2ClientInfo) {
40   - super(oauth2ClientInfo);
41 34 this.name = oauth2ClientInfo.getName();
42 35 this.icon = oauth2ClientInfo.getIcon();
43 36 this.url = oauth2ClientInfo.getUrl();
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +import lombok.Data;
  19 +import lombok.EqualsAndHashCode;
  20 +import lombok.NoArgsConstructor;
  21 +import lombok.ToString;
  22 +import org.thingsboard.server.common.data.BaseData;
  23 +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId;
  24 +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId;
  25 +
  26 +@EqualsAndHashCode(callSuper = true)
  27 +@Data
  28 +@ToString
  29 +@NoArgsConstructor
  30 +public class OAuth2ClientRegistration extends BaseData<OAuth2ClientRegistrationId> {
  31 +
  32 + private OAuth2ClientRegistrationInfoId clientRegistrationId;
  33 + private String domainName;
  34 + private SchemeType domainScheme;
  35 +
  36 + public OAuth2ClientRegistration(OAuth2ClientRegistration clientRegistration) {
  37 + super(clientRegistration);
  38 + this.clientRegistrationId = clientRegistration.clientRegistrationId;
  39 + this.domainName = clientRegistration.domainName;
  40 + this.domainScheme = clientRegistration.domainScheme;
  41 + }
  42 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonProperty;
  19 +import lombok.Data;
  20 +import lombok.EqualsAndHashCode;
  21 +import lombok.NoArgsConstructor;
  22 +import lombok.ToString;
  23 +import org.thingsboard.server.common.data.HasName;
  24 +import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
  25 +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId;
  26 +
  27 +import java.util.List;
  28 +
  29 +@EqualsAndHashCode(callSuper = true)
  30 +@Data
  31 +@ToString(exclude = {"clientSecret"})
  32 +@NoArgsConstructor
  33 +public class OAuth2ClientRegistrationInfo extends SearchTextBasedWithAdditionalInfo<OAuth2ClientRegistrationInfoId> implements HasName {
  34 +
  35 + private boolean enabled;
  36 + private OAuth2MapperConfig mapperConfig;
  37 + private String clientId;
  38 + private String clientSecret;
  39 + private String authorizationUri;
  40 + private String accessTokenUri;
  41 + private List<String> scope;
  42 + private String userInfoUri;
  43 + private String userNameAttributeName;
  44 + private String jwkSetUri;
  45 + private String clientAuthenticationMethod;
  46 + private String loginButtonLabel;
  47 + private String loginButtonIcon;
  48 +
  49 + public OAuth2ClientRegistrationInfo(OAuth2ClientRegistrationInfo clientRegistration) {
  50 + super(clientRegistration);
  51 + this.enabled = clientRegistration.enabled;
  52 + this.mapperConfig = clientRegistration.mapperConfig;
  53 + this.clientId = clientRegistration.clientId;
  54 + this.clientSecret = clientRegistration.clientSecret;
  55 + this.authorizationUri = clientRegistration.authorizationUri;
  56 + this.accessTokenUri = clientRegistration.accessTokenUri;
  57 + this.scope = clientRegistration.scope;
  58 + this.userInfoUri = clientRegistration.userInfoUri;
  59 + this.userNameAttributeName = clientRegistration.userNameAttributeName;
  60 + this.jwkSetUri = clientRegistration.jwkSetUri;
  61 + this.clientAuthenticationMethod = clientRegistration.clientAuthenticationMethod;
  62 + this.loginButtonLabel = clientRegistration.loginButtonLabel;
  63 + this.loginButtonIcon = clientRegistration.loginButtonIcon;
  64 + }
  65 +
  66 + @Override
  67 + @JsonProperty(access = JsonProperty.Access.READ_ONLY)
  68 + public String getName() {
  69 + return loginButtonLabel;
  70 + }
  71 +
  72 + @Override
  73 + public String getSearchText() {
  74 + return getName();
  75 + }
  76 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +import lombok.Data;
  19 +import lombok.EqualsAndHashCode;
  20 +import lombok.NoArgsConstructor;
  21 +import lombok.ToString;
  22 +import org.thingsboard.server.common.data.HasName;
  23 +import org.thingsboard.server.common.data.HasTenantId;
  24 +import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
  25 +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId;
  26 +import org.thingsboard.server.common.data.id.TenantId;
  27 +
  28 +import java.util.List;
  29 +
  30 +@EqualsAndHashCode(callSuper = true)
  31 +@Data
  32 +@ToString
  33 +@NoArgsConstructor
  34 +public class OAuth2ClientRegistrationTemplate extends SearchTextBasedWithAdditionalInfo<OAuth2ClientRegistrationTemplateId> implements HasName {
  35 +
  36 + private String providerId;
  37 + private MapperType mapperType;
  38 + private OAuth2BasicMapperConfig basic;
  39 + private String authorizationUri;
  40 + private String accessTokenUri;
  41 + private List<String> scope;
  42 + private String userInfoUri;
  43 + private String userNameAttributeName;
  44 + private String jwkSetUri;
  45 + private String clientAuthenticationMethod;
  46 + private String comment;
  47 + private String loginButtonIcon;
  48 + private String loginButtonLabel;
  49 + private String helpLink;
  50 +
  51 + public OAuth2ClientRegistrationTemplate(OAuth2ClientRegistrationTemplate clientRegistrationTemplate) {
  52 + super(clientRegistrationTemplate);
  53 + this.providerId = clientRegistrationTemplate.providerId;
  54 + this.mapperType = clientRegistrationTemplate.mapperType;
  55 + this.basic = clientRegistrationTemplate.basic;
  56 + this.authorizationUri = clientRegistrationTemplate.authorizationUri;
  57 + this.accessTokenUri = clientRegistrationTemplate.accessTokenUri;
  58 + this.scope = clientRegistrationTemplate.scope;
  59 + this.userInfoUri = clientRegistrationTemplate.userInfoUri;
  60 + this.userNameAttributeName = clientRegistrationTemplate.userNameAttributeName;
  61 + this.jwkSetUri = clientRegistrationTemplate.jwkSetUri;
  62 + this.clientAuthenticationMethod = clientRegistrationTemplate.clientAuthenticationMethod;
  63 + this.comment = clientRegistrationTemplate.comment;
  64 + this.loginButtonIcon = clientRegistrationTemplate.loginButtonIcon;
  65 + this.loginButtonLabel = clientRegistrationTemplate.loginButtonLabel;
  66 + this.helpLink = clientRegistrationTemplate.helpLink;
  67 + }
  68 +
  69 + @Override
  70 + public String getName() {
  71 + return providerId;
  72 + }
  73 +
  74 + @Override
  75 + public String getSearchText() {
  76 + return getName();
  77 + }
  78 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +import lombok.*;
  19 +
  20 +import java.util.List;
  21 +import java.util.Set;
  22 +
  23 +@EqualsAndHashCode
  24 +@Data
  25 +@ToString
  26 +@Builder(toBuilder = true)
  27 +@NoArgsConstructor
  28 +@AllArgsConstructor
  29 +public class OAuth2ClientsDomainParams {
  30 + private Set<DomainInfo> domainInfos;
  31 + private Set<ClientRegistrationDto> clientRegistrations;
  32 +}
\ No newline at end of file
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +import lombok.*;
  19 +import java.util.Set;
  20 +
  21 +@EqualsAndHashCode
  22 +@Data
  23 +@ToString
  24 +@Builder(toBuilder = true)
  25 +@NoArgsConstructor
  26 +@AllArgsConstructor
  27 +public class OAuth2ClientsParams {
  28 + private boolean enabled;
  29 + private Set<OAuth2ClientsDomainParams> domainsParams;
  30 +}
\ No newline at end of file
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +import lombok.*;
  19 +
  20 +@Builder(toBuilder = true)
  21 +@EqualsAndHashCode
  22 +@Data
  23 +@ToString(exclude = {"password"})
  24 +public class OAuth2CustomMapperConfig {
  25 + private final String url;
  26 + private final String username;
  27 + private final String password;
  28 + private final boolean sendToken;
  29 +}
... ...
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java renamed from dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientMapperConfig.java
... ... @@ -13,35 +13,21 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.dao.oauth2;
  16 +package org.thingsboard.server.common.data.oauth2;
17 17
  18 +import lombok.Builder;
18 19 import lombok.Data;
  20 +import lombok.EqualsAndHashCode;
  21 +import lombok.ToString;
19 22
  23 +@Builder(toBuilder = true)
  24 +@EqualsAndHashCode
20 25 @Data
21   -public class OAuth2ClientMapperConfig {
22   -
  26 +@ToString
  27 +public class OAuth2MapperConfig {
23 28 private boolean allowUserCreation;
24 29 private boolean activateUser;
25   - private String type;
26   - private BasicOAuth2ClientMapperConfig basic;
27   - private CustomOAuth2ClientMapperConfig custom;
28   -
29   - @Data
30   - public static class BasicOAuth2ClientMapperConfig {
31   - private String emailAttributeKey;
32   - private String firstNameAttributeKey;
33   - private String lastNameAttributeKey;
34   - private String tenantNameStrategy;
35   - private String tenantNamePattern;
36   - private String customerNamePattern;
37   - private boolean alwaysFullScreen;
38   - private String defaultDashboardName;
39   - }
40   -
41   - @Data
42   - public static class CustomOAuth2ClientMapperConfig {
43   - private String url;
44   - private String username;
45   - private String password;
46   - }
  30 + private MapperType type;
  31 + private OAuth2BasicMapperConfig basic;
  32 + private OAuth2CustomMapperConfig custom;
47 33 }
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +public enum SchemeType {
  19 + HTTP, HTTPS, MIXED;
  20 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +public enum TenantNameStrategyType {
  19 + DOMAIN, EMAIL, CUSTOM;
  20 +}
... ...
... ... @@ -390,6 +390,53 @@ public class ModelConstants {
390 390 public static final String RULE_NODE_STATE_DATA_PROPERTY = "state_data";
391 391
392 392 /**
  393 + * OAuth2 client registration constants.
  394 + */
  395 + public static final String OAUTH2_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
  396 + public static final String OAUTH2_CLIENT_REGISTRATION_INFO_COLUMN_FAMILY_NAME = "oauth2_client_registration_info";
  397 + public static final String OAUTH2_CLIENT_REGISTRATION_COLUMN_FAMILY_NAME = "oauth2_client_registration";
  398 + public static final String OAUTH2_CLIENT_REGISTRATION_TO_DOMAIN_COLUMN_FAMILY_NAME = "oauth2_client_registration_to_domain";
  399 + public static final String OAUTH2_CLIENT_REGISTRATION_TEMPLATE_COLUMN_FAMILY_NAME = "oauth2_client_registration_template";
  400 + public static final String OAUTH2_ENABLED_PROPERTY = "enabled";
  401 + public static final String OAUTH2_TEMPLATE_PROVIDER_ID_PROPERTY = "provider_id";
  402 + public static final String OAUTH2_CLIENT_REGISTRATION_INFO_ID_PROPERTY = "client_registration_info_id";
  403 + public static final String OAUTH2_DOMAIN_NAME_PROPERTY = "domain_name";
  404 + public static final String OAUTH2_DOMAIN_SCHEME_PROPERTY = "domain_scheme";
  405 + public static final String OAUTH2_CLIENT_ID_PROPERTY = "client_id";
  406 + public static final String OAUTH2_CLIENT_SECRET_PROPERTY = "client_secret";
  407 + public static final String OAUTH2_AUTHORIZATION_URI_PROPERTY = "authorization_uri";
  408 + public static final String OAUTH2_TOKEN_URI_PROPERTY = "token_uri";
  409 + public static final String OAUTH2_REDIRECT_URI_TEMPLATE_PROPERTY = "redirect_uri_template";
  410 + public static final String OAUTH2_SCOPE_PROPERTY = "scope";
  411 + public static final String OAUTH2_USER_INFO_URI_PROPERTY = "user_info_uri";
  412 + public static final String OAUTH2_USER_NAME_ATTRIBUTE_NAME_PROPERTY = "user_name_attribute_name";
  413 + public static final String OAUTH2_JWK_SET_URI_PROPERTY = "jwk_set_uri";
  414 + public static final String OAUTH2_CLIENT_AUTHENTICATION_METHOD_PROPERTY = "client_authentication_method";
  415 + public static final String OAUTH2_LOGIN_BUTTON_LABEL_PROPERTY = "login_button_label";
  416 + public static final String OAUTH2_LOGIN_BUTTON_ICON_PROPERTY = "login_button_icon";
  417 + public static final String OAUTH2_ALLOW_USER_CREATION_PROPERTY = "allow_user_creation";
  418 + public static final String OAUTH2_ACTIVATE_USER_PROPERTY = "activate_user";
  419 + public static final String OAUTH2_MAPPER_TYPE_PROPERTY = "type";
  420 + public static final String OAUTH2_EMAIL_ATTRIBUTE_KEY_PROPERTY = "basic_email_attribute_key";
  421 + public static final String OAUTH2_FIRST_NAME_ATTRIBUTE_KEY_PROPERTY = "basic_first_name_attribute_key";
  422 + public static final String OAUTH2_LAST_NAME_ATTRIBUTE_KEY_PROPERTY = "basic_last_name_attribute_key";
  423 + public static final String OAUTH2_TENANT_NAME_STRATEGY_PROPERTY = "basic_tenant_name_strategy";
  424 + public static final String OAUTH2_TENANT_NAME_PATTERN_PROPERTY = "basic_tenant_name_pattern";
  425 + public static final String OAUTH2_CUSTOMER_NAME_PATTERN_PROPERTY = "basic_customer_name_pattern";
  426 + public static final String OAUTH2_DEFAULT_DASHBOARD_NAME_PROPERTY = "basic_default_dashboard_name";
  427 + public static final String OAUTH2_ALWAYS_FULL_SCREEN_PROPERTY = "basic_always_full_screen";
  428 + public static final String OAUTH2_MAPPER_URL_PROPERTY = "custom_url";
  429 + public static final String OAUTH2_MAPPER_USERNAME_PROPERTY = "custom_username";
  430 + public static final String OAUTH2_MAPPER_PASSWORD_PROPERTY = "custom_password";
  431 + public static final String OAUTH2_MAPPER_SEND_TOKEN_PROPERTY = "custom_send_token";
  432 + public static final String OAUTH2_TEMPLATE_COMMENT_PROPERTY = "comment";
  433 + public static final String OAUTH2_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
  434 + public static final String OAUTH2_TEMPLATE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
  435 + public static final String OAUTH2_TEMPLATE_LOGIN_BUTTON_ICON_PROPERTY = OAUTH2_LOGIN_BUTTON_ICON_PROPERTY;
  436 + public static final String OAUTH2_TEMPLATE_LOGIN_BUTTON_LABEL_PROPERTY = OAUTH2_LOGIN_BUTTON_LABEL_PROPERTY;
  437 + public static final String OAUTH2_TEMPLATE_HELP_LINK_PROPERTY = "help_link";
  438 +
  439 + /**
393 440 * Cassandra attributes and timeseries constants.
394 441 */
395 442 public static final String ATTRIBUTES_KV_CF = "attributes_kv_cf";
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.model.sql;
  17 +
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import lombok.Data;
  20 +import lombok.EqualsAndHashCode;
  21 +import org.hibernate.annotations.Type;
  22 +import org.hibernate.annotations.TypeDef;
  23 +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId;
  24 +import org.thingsboard.server.common.data.oauth2.*;
  25 +import org.thingsboard.server.dao.model.BaseSqlEntity;
  26 +import org.thingsboard.server.dao.model.ModelConstants;
  27 +import org.thingsboard.server.dao.util.mapping.JsonStringType;
  28 +
  29 +import javax.persistence.*;
  30 +import java.util.Arrays;
  31 +
  32 +@Data
  33 +@EqualsAndHashCode(callSuper = true)
  34 +@TypeDef(name = "json", typeClass = JsonStringType.class)
  35 +@MappedSuperclass
  36 +public abstract class AbstractOAuth2ClientRegistrationInfoEntity<T extends OAuth2ClientRegistrationInfo> extends BaseSqlEntity<T> {
  37 +
  38 + @Column(name = ModelConstants.OAUTH2_ENABLED_PROPERTY)
  39 + private Boolean enabled;
  40 + @Column(name = ModelConstants.OAUTH2_CLIENT_ID_PROPERTY)
  41 + private String clientId;
  42 + @Column(name = ModelConstants.OAUTH2_CLIENT_SECRET_PROPERTY)
  43 + private String clientSecret;
  44 + @Column(name = ModelConstants.OAUTH2_AUTHORIZATION_URI_PROPERTY)
  45 + private String authorizationUri;
  46 + @Column(name = ModelConstants.OAUTH2_TOKEN_URI_PROPERTY)
  47 + private String tokenUri;
  48 + @Column(name = ModelConstants.OAUTH2_SCOPE_PROPERTY)
  49 + private String scope;
  50 + @Column(name = ModelConstants.OAUTH2_USER_INFO_URI_PROPERTY)
  51 + private String userInfoUri;
  52 + @Column(name = ModelConstants.OAUTH2_USER_NAME_ATTRIBUTE_NAME_PROPERTY)
  53 + private String userNameAttributeName;
  54 + @Column(name = ModelConstants.OAUTH2_JWK_SET_URI_PROPERTY)
  55 + private String jwkSetUri;
  56 + @Column(name = ModelConstants.OAUTH2_CLIENT_AUTHENTICATION_METHOD_PROPERTY)
  57 + private String clientAuthenticationMethod;
  58 + @Column(name = ModelConstants.OAUTH2_LOGIN_BUTTON_LABEL_PROPERTY)
  59 + private String loginButtonLabel;
  60 + @Column(name = ModelConstants.OAUTH2_LOGIN_BUTTON_ICON_PROPERTY)
  61 + private String loginButtonIcon;
  62 + @Column(name = ModelConstants.OAUTH2_ALLOW_USER_CREATION_PROPERTY)
  63 + private Boolean allowUserCreation;
  64 + @Column(name = ModelConstants.OAUTH2_ACTIVATE_USER_PROPERTY)
  65 + private Boolean activateUser;
  66 + @Enumerated(EnumType.STRING)
  67 + @Column(name = ModelConstants.OAUTH2_MAPPER_TYPE_PROPERTY)
  68 + private MapperType type;
  69 + @Column(name = ModelConstants.OAUTH2_EMAIL_ATTRIBUTE_KEY_PROPERTY)
  70 + private String emailAttributeKey;
  71 + @Column(name = ModelConstants.OAUTH2_FIRST_NAME_ATTRIBUTE_KEY_PROPERTY)
  72 + private String firstNameAttributeKey;
  73 + @Column(name = ModelConstants.OAUTH2_LAST_NAME_ATTRIBUTE_KEY_PROPERTY)
  74 + private String lastNameAttributeKey;
  75 + @Enumerated(EnumType.STRING)
  76 + @Column(name = ModelConstants.OAUTH2_TENANT_NAME_STRATEGY_PROPERTY)
  77 + private TenantNameStrategyType tenantNameStrategy;
  78 + @Column(name = ModelConstants.OAUTH2_TENANT_NAME_PATTERN_PROPERTY)
  79 + private String tenantNamePattern;
  80 + @Column(name = ModelConstants.OAUTH2_CUSTOMER_NAME_PATTERN_PROPERTY)
  81 + private String customerNamePattern;
  82 + @Column(name = ModelConstants.OAUTH2_DEFAULT_DASHBOARD_NAME_PROPERTY)
  83 + private String defaultDashboardName;
  84 + @Column(name = ModelConstants.OAUTH2_ALWAYS_FULL_SCREEN_PROPERTY)
  85 + private Boolean alwaysFullScreen;
  86 + @Column(name = ModelConstants.OAUTH2_MAPPER_URL_PROPERTY)
  87 + private String url;
  88 + @Column(name = ModelConstants.OAUTH2_MAPPER_USERNAME_PROPERTY)
  89 + private String username;
  90 + @Column(name = ModelConstants.OAUTH2_MAPPER_PASSWORD_PROPERTY)
  91 + private String password;
  92 + @Column(name = ModelConstants.OAUTH2_MAPPER_SEND_TOKEN_PROPERTY)
  93 + private Boolean sendToken;
  94 +
  95 + @Type(type = "json")
  96 + @Column(name = ModelConstants.OAUTH2_ADDITIONAL_INFO_PROPERTY)
  97 + private JsonNode additionalInfo;
  98 +
  99 + public AbstractOAuth2ClientRegistrationInfoEntity() {
  100 + super();
  101 + }
  102 +
  103 + public AbstractOAuth2ClientRegistrationInfoEntity(OAuth2ClientRegistrationInfo clientRegistrationInfo) {
  104 + if (clientRegistrationInfo.getId() != null) {
  105 + this.setUuid(clientRegistrationInfo.getId().getId());
  106 + }
  107 + this.createdTime = clientRegistrationInfo.getCreatedTime();
  108 + this.enabled = clientRegistrationInfo.isEnabled();
  109 + this.clientId = clientRegistrationInfo.getClientId();
  110 + this.clientSecret = clientRegistrationInfo.getClientSecret();
  111 + this.authorizationUri = clientRegistrationInfo.getAuthorizationUri();
  112 + this.tokenUri = clientRegistrationInfo.getAccessTokenUri();
  113 + this.scope = clientRegistrationInfo.getScope().stream().reduce((result, element) -> result + "," + element).orElse("");
  114 + this.userInfoUri = clientRegistrationInfo.getUserInfoUri();
  115 + this.userNameAttributeName = clientRegistrationInfo.getUserNameAttributeName();
  116 + this.jwkSetUri = clientRegistrationInfo.getJwkSetUri();
  117 + this.clientAuthenticationMethod = clientRegistrationInfo.getClientAuthenticationMethod();
  118 + this.loginButtonLabel = clientRegistrationInfo.getLoginButtonLabel();
  119 + this.loginButtonIcon = clientRegistrationInfo.getLoginButtonIcon();
  120 + this.additionalInfo = clientRegistrationInfo.getAdditionalInfo();
  121 + OAuth2MapperConfig mapperConfig = clientRegistrationInfo.getMapperConfig();
  122 + if (mapperConfig != null) {
  123 + this.allowUserCreation = mapperConfig.isAllowUserCreation();
  124 + this.activateUser = mapperConfig.isActivateUser();
  125 + this.type = mapperConfig.getType();
  126 + OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic();
  127 + if (basicConfig != null) {
  128 + this.emailAttributeKey = basicConfig.getEmailAttributeKey();
  129 + this.firstNameAttributeKey = basicConfig.getFirstNameAttributeKey();
  130 + this.lastNameAttributeKey = basicConfig.getLastNameAttributeKey();
  131 + this.tenantNameStrategy = basicConfig.getTenantNameStrategy();
  132 + this.tenantNamePattern = basicConfig.getTenantNamePattern();
  133 + this.customerNamePattern = basicConfig.getCustomerNamePattern();
  134 + this.defaultDashboardName = basicConfig.getDefaultDashboardName();
  135 + this.alwaysFullScreen = basicConfig.isAlwaysFullScreen();
  136 + }
  137 + OAuth2CustomMapperConfig customConfig = mapperConfig.getCustom();
  138 + if (customConfig != null) {
  139 + this.url = customConfig.getUrl();
  140 + this.username = customConfig.getUsername();
  141 + this.password = customConfig.getPassword();
  142 + this.sendToken = customConfig.isSendToken();
  143 + }
  144 + }
  145 + }
  146 +
  147 + public AbstractOAuth2ClientRegistrationInfoEntity(OAuth2ClientRegistrationInfoEntity oAuth2ClientRegistrationInfoEntity) {
  148 + this.setId(oAuth2ClientRegistrationInfoEntity.getId());
  149 + this.setCreatedTime(oAuth2ClientRegistrationInfoEntity.getCreatedTime());
  150 + this.enabled = oAuth2ClientRegistrationInfoEntity.getEnabled();
  151 + this.clientId = oAuth2ClientRegistrationInfoEntity.getClientId();
  152 + this.clientSecret = oAuth2ClientRegistrationInfoEntity.getClientSecret();
  153 + this.authorizationUri = oAuth2ClientRegistrationInfoEntity.getAuthorizationUri();
  154 + this.tokenUri = oAuth2ClientRegistrationInfoEntity.getTokenUri();
  155 + this.scope = oAuth2ClientRegistrationInfoEntity.getScope();
  156 + this.userInfoUri = oAuth2ClientRegistrationInfoEntity.getUserInfoUri();
  157 + this.userNameAttributeName = oAuth2ClientRegistrationInfoEntity.getUserNameAttributeName();
  158 + this.jwkSetUri = oAuth2ClientRegistrationInfoEntity.getJwkSetUri();
  159 + this.clientAuthenticationMethod = oAuth2ClientRegistrationInfoEntity.getClientAuthenticationMethod();
  160 + this.loginButtonLabel = oAuth2ClientRegistrationInfoEntity.getLoginButtonLabel();
  161 + this.loginButtonIcon = oAuth2ClientRegistrationInfoEntity.getLoginButtonIcon();
  162 + this.additionalInfo = oAuth2ClientRegistrationInfoEntity.getAdditionalInfo();
  163 + this.allowUserCreation = oAuth2ClientRegistrationInfoEntity.getAllowUserCreation();
  164 + this.activateUser = oAuth2ClientRegistrationInfoEntity.getActivateUser();
  165 + this.type = oAuth2ClientRegistrationInfoEntity.getType();
  166 + this.emailAttributeKey = oAuth2ClientRegistrationInfoEntity.getEmailAttributeKey();
  167 + this.firstNameAttributeKey = oAuth2ClientRegistrationInfoEntity.getFirstNameAttributeKey();
  168 + this.lastNameAttributeKey = oAuth2ClientRegistrationInfoEntity.getLastNameAttributeKey();
  169 + this.tenantNameStrategy = oAuth2ClientRegistrationInfoEntity.getTenantNameStrategy();
  170 + this.tenantNamePattern = oAuth2ClientRegistrationInfoEntity.getTenantNamePattern();
  171 + this.customerNamePattern = oAuth2ClientRegistrationInfoEntity.getCustomerNamePattern();
  172 + this.defaultDashboardName = oAuth2ClientRegistrationInfoEntity.getDefaultDashboardName();
  173 + this.alwaysFullScreen = oAuth2ClientRegistrationInfoEntity.getAlwaysFullScreen();
  174 + this.url = oAuth2ClientRegistrationInfoEntity.getUrl();
  175 + this.username = oAuth2ClientRegistrationInfoEntity.getUsername();
  176 + this.password = oAuth2ClientRegistrationInfoEntity.getPassword();
  177 + this.sendToken = oAuth2ClientRegistrationInfoEntity.getSendToken();
  178 + }
  179 +
  180 +
  181 + protected OAuth2ClientRegistrationInfo toOAuth2ClientRegistrationInfo() {
  182 + OAuth2ClientRegistrationInfo clientRegistrationInfo = new OAuth2ClientRegistrationInfo();
  183 + clientRegistrationInfo.setId(new OAuth2ClientRegistrationInfoId(id));
  184 + clientRegistrationInfo.setEnabled(enabled);
  185 + clientRegistrationInfo.setCreatedTime(createdTime);
  186 + clientRegistrationInfo.setAdditionalInfo(additionalInfo);
  187 + clientRegistrationInfo.setMapperConfig(
  188 + OAuth2MapperConfig.builder()
  189 + .allowUserCreation(allowUserCreation)
  190 + .activateUser(activateUser)
  191 + .type(type)
  192 + .basic(
  193 + (type == MapperType.BASIC || type == MapperType.GITHUB) ?
  194 + OAuth2BasicMapperConfig.builder()
  195 + .emailAttributeKey(emailAttributeKey)
  196 + .firstNameAttributeKey(firstNameAttributeKey)
  197 + .lastNameAttributeKey(lastNameAttributeKey)
  198 + .tenantNameStrategy(tenantNameStrategy)
  199 + .tenantNamePattern(tenantNamePattern)
  200 + .customerNamePattern(customerNamePattern)
  201 + .defaultDashboardName(defaultDashboardName)
  202 + .alwaysFullScreen(alwaysFullScreen)
  203 + .build()
  204 + : null
  205 + )
  206 + .custom(
  207 + type == MapperType.CUSTOM ?
  208 + OAuth2CustomMapperConfig.builder()
  209 + .url(url)
  210 + .username(username)
  211 + .password(password)
  212 + .sendToken(sendToken)
  213 + .build()
  214 + : null
  215 + )
  216 + .build()
  217 + );
  218 + clientRegistrationInfo.setClientId(clientId);
  219 + clientRegistrationInfo.setClientSecret(clientSecret);
  220 + clientRegistrationInfo.setAuthorizationUri(authorizationUri);
  221 + clientRegistrationInfo.setAccessTokenUri(tokenUri);
  222 + clientRegistrationInfo.setScope(Arrays.asList(scope.split(",")));
  223 + clientRegistrationInfo.setUserInfoUri(userInfoUri);
  224 + clientRegistrationInfo.setUserNameAttributeName(userNameAttributeName);
  225 + clientRegistrationInfo.setJwkSetUri(jwkSetUri);
  226 + clientRegistrationInfo.setClientAuthenticationMethod(clientAuthenticationMethod);
  227 + clientRegistrationInfo.setLoginButtonLabel(loginButtonLabel);
  228 + clientRegistrationInfo.setLoginButtonIcon(loginButtonIcon);
  229 + return clientRegistrationInfo;
  230 + }
  231 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.model.sql;
  17 +
  18 +import lombok.Data;
  19 +import lombok.EqualsAndHashCode;
  20 +import org.thingsboard.server.common.data.oauth2.ExtendedOAuth2ClientRegistrationInfo;
  21 +import org.thingsboard.server.common.data.oauth2.SchemeType;
  22 +
  23 +@Data
  24 +@EqualsAndHashCode(callSuper = true)
  25 +public class ExtendedOAuth2ClientRegistrationInfoEntity extends AbstractOAuth2ClientRegistrationInfoEntity<ExtendedOAuth2ClientRegistrationInfo> {
  26 +
  27 + private String domainName;
  28 + private SchemeType domainScheme;
  29 +
  30 + public ExtendedOAuth2ClientRegistrationInfoEntity() {
  31 + super();
  32 + }
  33 +
  34 + public ExtendedOAuth2ClientRegistrationInfoEntity(OAuth2ClientRegistrationInfoEntity oAuth2ClientRegistrationInfoEntity,
  35 + String domainName,
  36 + SchemeType domainScheme) {
  37 + super(oAuth2ClientRegistrationInfoEntity);
  38 + this.domainName = domainName;
  39 + this.domainScheme = domainScheme;
  40 + }
  41 +
  42 + @Override
  43 + public ExtendedOAuth2ClientRegistrationInfo toData() {
  44 + return new ExtendedOAuth2ClientRegistrationInfo(super.toOAuth2ClientRegistrationInfo(),
  45 + domainScheme,
  46 + domainName);
  47 + }
  48 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.model.sql;
  17 +
  18 +import lombok.Data;
  19 +import lombok.EqualsAndHashCode;
  20 +import org.hibernate.annotations.TypeDef;
  21 +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId;
  22 +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId;
  23 +import org.thingsboard.server.common.data.oauth2.*;
  24 +import org.thingsboard.server.dao.model.BaseSqlEntity;
  25 +import org.thingsboard.server.dao.model.ModelConstants;
  26 +import org.thingsboard.server.dao.util.mapping.JsonStringType;
  27 +
  28 +import javax.persistence.*;
  29 +import java.util.Arrays;
  30 +import java.util.UUID;
  31 +
  32 +@Data
  33 +@EqualsAndHashCode(callSuper = true)
  34 +@Entity
  35 +@TypeDef(name = "json", typeClass = JsonStringType.class)
  36 +@Table(name = ModelConstants.OAUTH2_CLIENT_REGISTRATION_COLUMN_FAMILY_NAME)
  37 +public class OAuth2ClientRegistrationEntity extends BaseSqlEntity<OAuth2ClientRegistration> {
  38 +
  39 + @Column(name = ModelConstants.OAUTH2_CLIENT_REGISTRATION_INFO_ID_PROPERTY, columnDefinition = "uuid")
  40 + private UUID clientRegistrationInfoId;
  41 +
  42 + @Column(name = ModelConstants.OAUTH2_DOMAIN_NAME_PROPERTY)
  43 + private String domainName;
  44 +
  45 + @Enumerated(EnumType.STRING)
  46 + @Column(name = ModelConstants.OAUTH2_DOMAIN_SCHEME_PROPERTY)
  47 + private SchemeType domainScheme;
  48 +
  49 + public OAuth2ClientRegistrationEntity() {
  50 + super();
  51 + }
  52 +
  53 + public OAuth2ClientRegistrationEntity(OAuth2ClientRegistration clientRegistration) {
  54 + if (clientRegistration.getId() != null) {
  55 + this.setUuid(clientRegistration.getId().getId());
  56 + }
  57 + if (clientRegistration.getClientRegistrationId() != null){
  58 + this.clientRegistrationInfoId = clientRegistration.getClientRegistrationId().getId();
  59 + }
  60 + this.createdTime = clientRegistration.getCreatedTime();
  61 + this.domainName = clientRegistration.getDomainName();
  62 + this.domainScheme = clientRegistration.getDomainScheme();
  63 + }
  64 +
  65 + @Override
  66 + public OAuth2ClientRegistration toData() {
  67 + OAuth2ClientRegistration clientRegistration = new OAuth2ClientRegistration();
  68 + clientRegistration.setId(new OAuth2ClientRegistrationId(id));
  69 + clientRegistration.setClientRegistrationId(new OAuth2ClientRegistrationInfoId(clientRegistrationInfoId));
  70 + clientRegistration.setCreatedTime(createdTime);
  71 + clientRegistration.setDomainName(domainName);
  72 + clientRegistration.setDomainScheme(domainScheme);
  73 + return clientRegistration;
  74 + }
  75 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.model.sql;
  17 +
  18 +import lombok.Data;
  19 +import lombok.EqualsAndHashCode;
  20 +import org.hibernate.annotations.TypeDef;
  21 +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
  22 +import org.thingsboard.server.dao.model.ModelConstants;
  23 +import org.thingsboard.server.dao.util.mapping.JsonStringType;
  24 +
  25 +import javax.persistence.Entity;
  26 +import javax.persistence.Table;
  27 +
  28 +@Data
  29 +@EqualsAndHashCode(callSuper = true)
  30 +@Entity
  31 +@TypeDef(name = "json", typeClass = JsonStringType.class)
  32 +@Table(name = ModelConstants.OAUTH2_CLIENT_REGISTRATION_INFO_COLUMN_FAMILY_NAME)
  33 +public class OAuth2ClientRegistrationInfoEntity extends AbstractOAuth2ClientRegistrationInfoEntity<OAuth2ClientRegistrationInfo> {
  34 +
  35 + public OAuth2ClientRegistrationInfoEntity() {
  36 + super();
  37 + }
  38 +
  39 + public OAuth2ClientRegistrationInfoEntity(OAuth2ClientRegistrationInfo clientRegistration) {
  40 + super(clientRegistration);
  41 + }
  42 +
  43 + public OAuth2ClientRegistrationInfoEntity(OAuth2ClientRegistrationInfoEntity oAuth2ClientRegistrationInfoEntity) {
  44 + super(oAuth2ClientRegistrationInfoEntity);
  45 + }
  46 +
  47 + @Override
  48 + public OAuth2ClientRegistrationInfo toData() {
  49 + return super.toOAuth2ClientRegistrationInfo();
  50 + }
  51 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.model.sql;
  17 +
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import lombok.Data;
  20 +import lombok.EqualsAndHashCode;
  21 +import org.hibernate.annotations.Type;
  22 +import org.hibernate.annotations.TypeDef;
  23 +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId;
  24 +import org.thingsboard.server.common.data.id.TenantId;
  25 +import org.thingsboard.server.common.data.oauth2.*;
  26 +import org.thingsboard.server.dao.model.BaseSqlEntity;
  27 +import org.thingsboard.server.dao.model.ModelConstants;
  28 +import org.thingsboard.server.dao.util.mapping.JsonStringType;
  29 +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate;
  30 +
  31 +import javax.persistence.*;
  32 +import java.util.Arrays;
  33 +import java.util.UUID;
  34 +
  35 +@Data
  36 +@EqualsAndHashCode(callSuper = true)
  37 +@Entity
  38 +@TypeDef(name = "json", typeClass = JsonStringType.class)
  39 +@Table(name = ModelConstants.OAUTH2_CLIENT_REGISTRATION_TEMPLATE_COLUMN_FAMILY_NAME)
  40 +public class OAuth2ClientRegistrationTemplateEntity extends BaseSqlEntity<OAuth2ClientRegistrationTemplate> {
  41 +
  42 + @Column(name = ModelConstants.OAUTH2_TEMPLATE_PROVIDER_ID_PROPERTY)
  43 + private String providerId;
  44 + @Column(name = ModelConstants.OAUTH2_AUTHORIZATION_URI_PROPERTY)
  45 + private String authorizationUri;
  46 + @Column(name = ModelConstants.OAUTH2_TOKEN_URI_PROPERTY)
  47 + private String tokenUri;
  48 + @Column(name = ModelConstants.OAUTH2_SCOPE_PROPERTY)
  49 + private String scope;
  50 + @Column(name = ModelConstants.OAUTH2_USER_INFO_URI_PROPERTY)
  51 + private String userInfoUri;
  52 + @Column(name = ModelConstants.OAUTH2_USER_NAME_ATTRIBUTE_NAME_PROPERTY)
  53 + private String userNameAttributeName;
  54 + @Column(name = ModelConstants.OAUTH2_JWK_SET_URI_PROPERTY)
  55 + private String jwkSetUri;
  56 + @Column(name = ModelConstants.OAUTH2_CLIENT_AUTHENTICATION_METHOD_PROPERTY)
  57 + private String clientAuthenticationMethod;
  58 + @Enumerated(EnumType.STRING)
  59 + @Column(name = ModelConstants.OAUTH2_MAPPER_TYPE_PROPERTY)
  60 + private MapperType type;
  61 + @Column(name = ModelConstants.OAUTH2_EMAIL_ATTRIBUTE_KEY_PROPERTY)
  62 + private String emailAttributeKey;
  63 + @Column(name = ModelConstants.OAUTH2_FIRST_NAME_ATTRIBUTE_KEY_PROPERTY)
  64 + private String firstNameAttributeKey;
  65 + @Column(name = ModelConstants.OAUTH2_LAST_NAME_ATTRIBUTE_KEY_PROPERTY)
  66 + private String lastNameAttributeKey;
  67 + @Enumerated(EnumType.STRING)
  68 + @Column(name = ModelConstants.OAUTH2_TENANT_NAME_STRATEGY_PROPERTY)
  69 + private TenantNameStrategyType tenantNameStrategy;
  70 + @Column(name = ModelConstants.OAUTH2_TENANT_NAME_PATTERN_PROPERTY)
  71 + private String tenantNamePattern;
  72 + @Column(name = ModelConstants.OAUTH2_CUSTOMER_NAME_PATTERN_PROPERTY)
  73 + private String customerNamePattern;
  74 + @Column(name = ModelConstants.OAUTH2_DEFAULT_DASHBOARD_NAME_PROPERTY)
  75 + private String defaultDashboardName;
  76 + @Column(name = ModelConstants.OAUTH2_ALWAYS_FULL_SCREEN_PROPERTY)
  77 + private Boolean alwaysFullScreen;
  78 + @Column(name = ModelConstants.OAUTH2_TEMPLATE_COMMENT_PROPERTY)
  79 + private String comment;
  80 + @Column(name = ModelConstants.OAUTH2_TEMPLATE_LOGIN_BUTTON_ICON_PROPERTY)
  81 + private String loginButtonIcon;
  82 + @Column(name = ModelConstants.OAUTH2_TEMPLATE_LOGIN_BUTTON_LABEL_PROPERTY)
  83 + private String loginButtonLabel;
  84 + @Column(name = ModelConstants.OAUTH2_TEMPLATE_HELP_LINK_PROPERTY)
  85 + private String helpLink;
  86 +
  87 + @Type(type = "json")
  88 + @Column(name = ModelConstants.OAUTH2_TEMPLATE_ADDITIONAL_INFO_PROPERTY)
  89 + private JsonNode additionalInfo;
  90 +
  91 + public OAuth2ClientRegistrationTemplateEntity() {
  92 + }
  93 +
  94 + public OAuth2ClientRegistrationTemplateEntity(OAuth2ClientRegistrationTemplate clientRegistrationTemplate) {
  95 + if (clientRegistrationTemplate.getId() != null) {
  96 + this.setUuid(clientRegistrationTemplate.getId().getId());
  97 + }
  98 + this.createdTime = clientRegistrationTemplate.getCreatedTime();
  99 + this.providerId = clientRegistrationTemplate.getProviderId();
  100 + this.authorizationUri = clientRegistrationTemplate.getAuthorizationUri();
  101 + this.tokenUri = clientRegistrationTemplate.getAccessTokenUri();
  102 + this.scope = clientRegistrationTemplate.getScope().stream().reduce((result, element) -> result + "," + element).orElse("");
  103 + this.userInfoUri = clientRegistrationTemplate.getUserInfoUri();
  104 + this.userNameAttributeName = clientRegistrationTemplate.getUserNameAttributeName();
  105 + this.jwkSetUri = clientRegistrationTemplate.getJwkSetUri();
  106 + this.clientAuthenticationMethod = clientRegistrationTemplate.getClientAuthenticationMethod();
  107 + this.comment = clientRegistrationTemplate.getComment();
  108 + this.loginButtonIcon = clientRegistrationTemplate.getLoginButtonIcon();
  109 + this.loginButtonLabel = clientRegistrationTemplate.getLoginButtonLabel();
  110 + this.helpLink = clientRegistrationTemplate.getHelpLink();
  111 + this.additionalInfo = clientRegistrationTemplate.getAdditionalInfo();
  112 + this.type = clientRegistrationTemplate.getMapperType();
  113 + OAuth2BasicMapperConfig basicConfig = clientRegistrationTemplate.getBasic();
  114 + if (basicConfig != null) {
  115 + this.emailAttributeKey = basicConfig.getEmailAttributeKey();
  116 + this.firstNameAttributeKey = basicConfig.getFirstNameAttributeKey();
  117 + this.lastNameAttributeKey = basicConfig.getLastNameAttributeKey();
  118 + this.tenantNameStrategy = basicConfig.getTenantNameStrategy();
  119 + this.tenantNamePattern = basicConfig.getTenantNamePattern();
  120 + this.customerNamePattern = basicConfig.getCustomerNamePattern();
  121 + this.defaultDashboardName = basicConfig.getDefaultDashboardName();
  122 + this.alwaysFullScreen = basicConfig.isAlwaysFullScreen();
  123 + }
  124 + }
  125 +
  126 + @Override
  127 + public OAuth2ClientRegistrationTemplate toData() {
  128 + OAuth2ClientRegistrationTemplate clientRegistrationTemplate = new OAuth2ClientRegistrationTemplate();
  129 + clientRegistrationTemplate.setId(new OAuth2ClientRegistrationTemplateId(id));
  130 + clientRegistrationTemplate.setCreatedTime(createdTime);
  131 + clientRegistrationTemplate.setAdditionalInfo(additionalInfo);
  132 +
  133 + clientRegistrationTemplate.setMapperType(type);
  134 + clientRegistrationTemplate.setProviderId(providerId);
  135 + clientRegistrationTemplate.setBasic(
  136 + OAuth2BasicMapperConfig.builder()
  137 + .emailAttributeKey(emailAttributeKey)
  138 + .firstNameAttributeKey(firstNameAttributeKey)
  139 + .lastNameAttributeKey(lastNameAttributeKey)
  140 + .tenantNameStrategy(tenantNameStrategy)
  141 + .tenantNamePattern(tenantNamePattern)
  142 + .customerNamePattern(customerNamePattern)
  143 + .defaultDashboardName(defaultDashboardName)
  144 + .alwaysFullScreen(alwaysFullScreen)
  145 + .build()
  146 + );
  147 + clientRegistrationTemplate.setAuthorizationUri(authorizationUri);
  148 + clientRegistrationTemplate.setAccessTokenUri(tokenUri);
  149 + clientRegistrationTemplate.setScope(Arrays.asList(scope.split(",")));
  150 + clientRegistrationTemplate.setUserInfoUri(userInfoUri);
  151 + clientRegistrationTemplate.setUserNameAttributeName(userNameAttributeName);
  152 + clientRegistrationTemplate.setJwkSetUri(jwkSetUri);
  153 + clientRegistrationTemplate.setClientAuthenticationMethod(clientAuthenticationMethod);
  154 + clientRegistrationTemplate.setComment(comment);
  155 + clientRegistrationTemplate.setLoginButtonIcon(loginButtonIcon);
  156 + clientRegistrationTemplate.setLoginButtonLabel(loginButtonLabel);
  157 + clientRegistrationTemplate.setHelpLink(helpLink);
  158 + return clientRegistrationTemplate;
  159 + }
  160 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.oauth2;
  17 +
  18 +import org.springframework.beans.factory.annotation.Autowired;
  19 +import org.springframework.security.oauth2.client.registration.ClientRegistration;
  20 +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
  21 +import org.springframework.security.oauth2.core.AuthorizationGrantType;
  22 +import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
  23 +import org.springframework.stereotype.Component;
  24 +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
  25 +
  26 +import java.util.UUID;
  27 +
  28 +@Component
  29 +public class HybridClientRegistrationRepository implements ClientRegistrationRepository {
  30 + private static final String defaultRedirectUriTemplate = "{baseUrl}/login/oauth2/code/{registrationId}";
  31 +
  32 + @Autowired
  33 + private OAuth2Service oAuth2Service;
  34 +
  35 + @Override
  36 + public ClientRegistration findByRegistrationId(String registrationId) {
  37 + OAuth2ClientRegistrationInfo oAuth2ClientRegistrationInfo = oAuth2Service.findClientRegistrationInfo(UUID.fromString(registrationId));
  38 + return oAuth2ClientRegistrationInfo == null ?
  39 + null : toSpringClientRegistration(oAuth2ClientRegistrationInfo);
  40 + }
  41 +
  42 + private ClientRegistration toSpringClientRegistration(OAuth2ClientRegistrationInfo localClientRegistration){
  43 + String registrationId = localClientRegistration.getUuidId().toString();
  44 + return ClientRegistration.withRegistrationId(registrationId)
  45 + .clientName(localClientRegistration.getName())
  46 + .clientId(localClientRegistration.getClientId())
  47 + .authorizationUri(localClientRegistration.getAuthorizationUri())
  48 + .clientSecret(localClientRegistration.getClientSecret())
  49 + .tokenUri(localClientRegistration.getAccessTokenUri())
  50 + .scope(localClientRegistration.getScope())
  51 + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
  52 + .userInfoUri(localClientRegistration.getUserInfoUri())
  53 + .userNameAttributeName(localClientRegistration.getUserNameAttributeName())
  54 + .jwkSetUri(localClientRegistration.getJwkSetUri())
  55 + .clientAuthenticationMethod(new ClientAuthenticationMethod(localClientRegistration.getClientAuthenticationMethod()))
  56 + .redirectUriTemplate(defaultRedirectUriTemplate)
  57 + .build();
  58 + }
  59 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.oauth2;
  17 +
  18 +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration;
  19 +import org.thingsboard.server.dao.Dao;
  20 +
  21 +public interface OAuth2ClientRegistrationDao extends Dao<OAuth2ClientRegistration> {
  22 + void deleteAll();
  23 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.oauth2;
  17 +
  18 +import org.thingsboard.server.common.data.oauth2.ExtendedOAuth2ClientRegistrationInfo;
  19 +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
  20 +import org.thingsboard.server.common.data.oauth2.SchemeType;
  21 +import org.thingsboard.server.dao.Dao;
  22 +
  23 +import java.util.List;
  24 +import java.util.Set;
  25 +
  26 +public interface OAuth2ClientRegistrationInfoDao extends Dao<OAuth2ClientRegistrationInfo> {
  27 + List<OAuth2ClientRegistrationInfo> findAll();
  28 +
  29 + List<ExtendedOAuth2ClientRegistrationInfo> findAllExtended();
  30 +
  31 + List<OAuth2ClientRegistrationInfo> findByDomainSchemesAndDomainName(List<SchemeType> domainSchemes, String domainName);
  32 +
  33 + void deleteAll();
  34 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.oauth2;
  17 +
  18 +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate;
  19 +import org.thingsboard.server.dao.Dao;
  20 +
  21 +import java.util.List;
  22 +
  23 +public interface OAuth2ClientRegistrationTemplateDao extends Dao<OAuth2ClientRegistrationTemplate> {
  24 + List<OAuth2ClientRegistrationTemplate> findAll();
  25 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.oauth2;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.hibernate.exception.ConstraintViolationException;
  20 +import org.springframework.beans.factory.annotation.Autowired;
  21 +import org.springframework.stereotype.Service;
  22 +import org.springframework.util.StringUtils;
  23 +import org.thingsboard.server.common.data.Tenant;
  24 +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId;
  25 +import org.thingsboard.server.common.data.id.TenantId;
  26 +import org.thingsboard.server.common.data.oauth2.*;
  27 +import org.thingsboard.server.dao.entity.AbstractEntityService;
  28 +import org.thingsboard.server.dao.exception.DataValidationException;
  29 +import org.thingsboard.server.dao.service.DataValidator;
  30 +
  31 +import java.util.List;
  32 +
  33 +import static org.thingsboard.server.dao.service.Validator.validateId;
  34 +import static org.thingsboard.server.dao.service.Validator.validateString;
  35 +
  36 +@Slf4j
  37 +@Service
  38 +public class OAuth2ConfigTemplateServiceImpl extends AbstractEntityService implements OAuth2ConfigTemplateService {
  39 + public static final String INCORRECT_CLIENT_REGISTRATION_TEMPLATE_ID = "Incorrect clientRegistrationTemplateId ";
  40 +
  41 + @Autowired
  42 + private OAuth2ClientRegistrationTemplateDao clientRegistrationTemplateDao;
  43 +
  44 + @Override
  45 + public OAuth2ClientRegistrationTemplate saveClientRegistrationTemplate(OAuth2ClientRegistrationTemplate clientRegistrationTemplate) {
  46 + log.trace("Executing saveClientRegistrationTemplate [{}]", clientRegistrationTemplate);
  47 + clientRegistrationTemplateValidator.validate(clientRegistrationTemplate, o -> TenantId.SYS_TENANT_ID);
  48 + OAuth2ClientRegistrationTemplate savedClientRegistrationTemplate;
  49 + try {
  50 + savedClientRegistrationTemplate = clientRegistrationTemplateDao.save(TenantId.SYS_TENANT_ID, clientRegistrationTemplate);
  51 + } catch (Exception t) {
  52 + ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
  53 + if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("oauth2_template_provider_id_unq_key")) {
  54 + throw new DataValidationException("Client registration template with such providerId already exists!");
  55 + } else {
  56 + throw t;
  57 + }
  58 + }
  59 + return savedClientRegistrationTemplate;
  60 + }
  61 +
  62 + @Override
  63 + public OAuth2ClientRegistrationTemplate findClientRegistrationTemplateById(OAuth2ClientRegistrationTemplateId templateId) {
  64 + log.trace("Executing findClientRegistrationTemplateById [{}]", templateId);
  65 + validateId(templateId, INCORRECT_CLIENT_REGISTRATION_TEMPLATE_ID + templateId);
  66 + return clientRegistrationTemplateDao.findById(TenantId.SYS_TENANT_ID, templateId.getId());
  67 + }
  68 +
  69 + @Override
  70 + public List<OAuth2ClientRegistrationTemplate> findAllClientRegistrationTemplates() {
  71 + log.trace("Executing findAllClientRegistrationTemplates");
  72 + return clientRegistrationTemplateDao.findAll();
  73 + }
  74 +
  75 + @Override
  76 + public void deleteClientRegistrationTemplateById(OAuth2ClientRegistrationTemplateId templateId) {
  77 + log.trace("Executing deleteClientRegistrationTemplateById [{}]", templateId);
  78 + validateId(templateId, INCORRECT_CLIENT_REGISTRATION_TEMPLATE_ID + templateId);
  79 + clientRegistrationTemplateDao.removeById(TenantId.SYS_TENANT_ID, templateId.getId());
  80 + }
  81 +
  82 + private final DataValidator<OAuth2ClientRegistrationTemplate> clientRegistrationTemplateValidator =
  83 + new DataValidator<OAuth2ClientRegistrationTemplate>() {
  84 +
  85 + @Override
  86 + protected void validateCreate(TenantId tenantId, OAuth2ClientRegistrationTemplate clientRegistrationTemplate) {
  87 + }
  88 +
  89 + @Override
  90 + protected void validateUpdate(TenantId tenantId, OAuth2ClientRegistrationTemplate clientRegistrationTemplate) {
  91 + }
  92 +
  93 + @Override
  94 + protected void validateDataImpl(TenantId tenantId, OAuth2ClientRegistrationTemplate clientRegistrationTemplate) {
  95 + if (StringUtils.isEmpty(clientRegistrationTemplate.getProviderId())) {
  96 + throw new DataValidationException("Provider ID should be specified!");
  97 + }
  98 + if (clientRegistrationTemplate.getBasic() == null) {
  99 + throw new DataValidationException("Basic mapper config should be specified!");
  100 + }
  101 + }
  102 + };
  103 +}
... ...
... ... @@ -17,66 +17,15 @@ package org.thingsboard.server.dao.oauth2;
17 17
18 18 import lombok.Data;
19 19 import lombok.extern.slf4j.Slf4j;
20   -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
21 20 import org.springframework.boot.context.properties.ConfigurationProperties;
22   -import org.springframework.context.annotation.Bean;
23 21 import org.springframework.context.annotation.Configuration;
24   -import org.springframework.security.oauth2.client.registration.ClientRegistration;
25   -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
26   -import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
27   -import org.springframework.security.oauth2.core.AuthorizationGrantType;
28   -import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
29 22
30   -import java.util.ArrayList;
31   -import java.util.HashMap;
32   -import java.util.List;
33 23 import java.util.Map;
34 24
35 25 @Configuration
36   -@ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true")
37 26 @ConfigurationProperties(prefix = "security.oauth2")
38 27 @Data
39   -@Slf4j
40 28 public class OAuth2Configuration {
41   -
42   - private boolean enabled;
43 29 private String loginProcessingUrl;
44   - private Map<String, OAuth2Client> clients = new HashMap<>();
45   -
46   - @Bean
47   - public ClientRegistrationRepository clientRegistrationRepository() {
48   - List<ClientRegistration> result = new ArrayList<>();
49   - for (Map.Entry<String, OAuth2Client> entry : clients.entrySet()) {
50   - OAuth2Client client = entry.getValue();
51   - ClientRegistration registration = ClientRegistration.withRegistrationId(entry.getKey())
52   - .clientId(client.getClientId())
53   - .authorizationUri(client.getAuthorizationUri())
54   - .clientSecret(client.getClientSecret())
55   - .tokenUri(client.getAccessTokenUri())
56   - .redirectUriTemplate(client.getRedirectUriTemplate())
57   - .scope(client.getScope().split(","))
58   - .clientName(client.getClientName())
59   - .authorizationGrantType(new AuthorizationGrantType(client.getAuthorizationGrantType()))
60   - .userInfoUri(client.getUserInfoUri())
61   - .userNameAttributeName(client.getUserNameAttributeName())
62   - .jwkSetUri(client.getJwkSetUri())
63   - .clientAuthenticationMethod(new ClientAuthenticationMethod(client.getClientAuthenticationMethod()))
64   - .build();
65   - result.add(registration);
66   - }
67   - return new InMemoryClientRegistrationRepository(result);
68   - }
69   -
70   - public OAuth2Client getClientByRegistrationId(String registrationId) {
71   - OAuth2Client result = null;
72   - if (clients != null && !clients.isEmpty()) {
73   - for (String key : clients.keySet()) {
74   - if (key.equals(registrationId)) {
75   - result = clients.get(key);
76   - break;
77   - }
78   - }
79   - }
80   - return result;
81   - }
  30 + private Map<String, String> githubMapper;
82 31 }
... ...
... ... @@ -18,33 +18,198 @@ package org.thingsboard.server.dao.oauth2;
18 18 import lombok.extern.slf4j.Slf4j;
19 19 import org.springframework.beans.factory.annotation.Autowired;
20 20 import org.springframework.stereotype.Service;
21   -import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
  21 +import org.springframework.util.StringUtils;
  22 +import org.thingsboard.server.common.data.id.TenantId;
  23 +import org.thingsboard.server.common.data.oauth2.*;
  24 +import org.thingsboard.server.dao.entity.AbstractEntityService;
  25 +import org.thingsboard.server.dao.exception.DataValidationException;
  26 +import org.thingsboard.server.dao.exception.IncorrectParameterException;
22 27
23   -import java.util.ArrayList;
24   -import java.util.Collections;
25   -import java.util.List;
26   -import java.util.Map;
  28 +import javax.transaction.Transactional;
  29 +import java.util.*;
  30 +import java.util.function.Consumer;
  31 +import java.util.stream.Collectors;
  32 +
  33 +import static org.thingsboard.server.dao.service.Validator.validateId;
  34 +import static org.thingsboard.server.dao.service.Validator.validateString;
27 35
28 36 @Slf4j
29 37 @Service
30   -public class OAuth2ServiceImpl implements OAuth2Service {
  38 +public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Service {
  39 + public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
  40 + public static final String INCORRECT_CLIENT_REGISTRATION_ID = "Incorrect clientRegistrationId ";
  41 + public static final String INCORRECT_DOMAIN_NAME = "Incorrect domainName ";
  42 + public static final String INCORRECT_DOMAIN_SCHEME = "Incorrect domainScheme ";
31 43
32   - @Autowired(required = false)
33   - OAuth2Configuration oauth2Configuration;
  44 + @Autowired
  45 + private OAuth2ClientRegistrationInfoDao clientRegistrationInfoDao;
  46 + @Autowired
  47 + private OAuth2ClientRegistrationDao clientRegistrationDao;
34 48
35 49 @Override
36   - public List<OAuth2ClientInfo> getOAuth2Clients() {
37   - if (oauth2Configuration == null || !oauth2Configuration.isEnabled()) {
38   - return Collections.emptyList();
  50 + public List<OAuth2ClientInfo> getOAuth2Clients(String domainSchemeStr, String domainName) {
  51 + log.trace("Executing getOAuth2Clients [{}://{}]", domainSchemeStr, domainName);
  52 + if (domainSchemeStr == null) {
  53 + throw new IncorrectParameterException(INCORRECT_DOMAIN_SCHEME);
39 54 }
40   - List<OAuth2ClientInfo> result = new ArrayList<>();
41   - for (Map.Entry<String, OAuth2Client> entry : oauth2Configuration.getClients().entrySet()) {
42   - OAuth2ClientInfo client = new OAuth2ClientInfo();
43   - client.setName(entry.getValue().getLoginButtonLabel());
44   - client.setUrl(String.format("/oauth2/authorization/%s", entry.getKey()));
45   - client.setIcon(entry.getValue().getLoginButtonIcon());
46   - result.add(client);
  55 + SchemeType domainScheme;
  56 + try {
  57 + domainScheme = SchemeType.valueOf(domainSchemeStr.toUpperCase());
  58 + } catch (IllegalArgumentException e){
  59 + throw new IncorrectParameterException(INCORRECT_DOMAIN_SCHEME);
47 60 }
48   - return result;
  61 + validateString(domainName, INCORRECT_DOMAIN_NAME + domainName);
  62 + return clientRegistrationInfoDao.findByDomainSchemesAndDomainName(Arrays.asList(domainScheme, SchemeType.MIXED), domainName).stream()
  63 + .filter(OAuth2ClientRegistrationInfo::isEnabled)
  64 + .map(OAuth2Utils::toClientInfo)
  65 + .collect(Collectors.toList());
  66 + }
  67 +
  68 + @Override
  69 + @Transactional
  70 + public void saveOAuth2Params(OAuth2ClientsParams oauth2Params) {
  71 + log.trace("Executing saveOAuth2Params [{}]", oauth2Params);
  72 + clientParamsValidator.accept(oauth2Params);
  73 + clientRegistrationDao.deleteAll();
  74 + clientRegistrationInfoDao.deleteAll();
  75 + oauth2Params.getDomainsParams().forEach(domainParams -> {
  76 + domainParams.getClientRegistrations().forEach(clientRegistrationDto -> {
  77 + OAuth2ClientRegistrationInfo oAuth2ClientRegistrationInfo = OAuth2Utils.toClientRegistrationInfo(oauth2Params.isEnabled(), clientRegistrationDto);
  78 + OAuth2ClientRegistrationInfo savedClientRegistrationInfo = clientRegistrationInfoDao.save(TenantId.SYS_TENANT_ID, oAuth2ClientRegistrationInfo);
  79 + domainParams.getDomainInfos().forEach(domainInfo -> {
  80 + OAuth2ClientRegistration oAuth2ClientRegistration = OAuth2Utils.toClientRegistration(savedClientRegistrationInfo.getId(),
  81 + domainInfo.getScheme(), domainInfo.getName());
  82 + clientRegistrationDao.save(TenantId.SYS_TENANT_ID, oAuth2ClientRegistration);
  83 + });
  84 + });
  85 + });
  86 + }
  87 +
  88 + @Override
  89 + public OAuth2ClientsParams findOAuth2Params() {
  90 + log.trace("Executing findOAuth2Params");
  91 + List<ExtendedOAuth2ClientRegistrationInfo> extendedInfos = clientRegistrationInfoDao.findAllExtended();
  92 + return OAuth2Utils.toOAuth2Params(extendedInfos);
  93 + }
  94 +
  95 + @Override
  96 + public OAuth2ClientRegistrationInfo findClientRegistrationInfo(UUID id) {
  97 + log.trace("Executing findClientRegistrationInfo [{}]", id);
  98 + validateId(id, INCORRECT_CLIENT_REGISTRATION_ID + id);
  99 + return clientRegistrationInfoDao.findById(null, id);
  100 + }
  101 +
  102 + @Override
  103 + public List<OAuth2ClientRegistrationInfo> findAllClientRegistrationInfos() {
  104 + log.trace("Executing findAllClientRegistrationInfos");
  105 + return clientRegistrationInfoDao.findAll();
49 106 }
  107 +
  108 + private final Consumer<OAuth2ClientsParams> clientParamsValidator = oauth2Params -> {
  109 + if (oauth2Params == null
  110 + || oauth2Params.getDomainsParams() == null) {
  111 + throw new DataValidationException("Domain params should be specified!");
  112 + }
  113 + for (OAuth2ClientsDomainParams domainParams : oauth2Params.getDomainsParams()) {
  114 + if (domainParams.getDomainInfos() == null
  115 + || domainParams.getDomainInfos().isEmpty()) {
  116 + throw new DataValidationException("List of domain configuration should be specified!");
  117 + }
  118 + for (DomainInfo domainInfo : domainParams.getDomainInfos()) {
  119 + if (StringUtils.isEmpty(domainInfo.getName())) {
  120 + throw new DataValidationException("Domain name should be specified!");
  121 + }
  122 + if (domainInfo.getScheme() == null) {
  123 + throw new DataValidationException("Domain scheme should be specified!");
  124 + }
  125 + }
  126 + domainParams.getDomainInfos().stream()
  127 + .collect(Collectors.groupingBy(DomainInfo::getName))
  128 + .forEach((domainName, domainInfos) -> {
  129 + if (domainInfos.size() > 1 && domainInfos.stream().anyMatch(domainInfo -> domainInfo.getScheme() == SchemeType.MIXED)) {
  130 + throw new DataValidationException("MIXED scheme type shouldn't be combined with another scheme type!");
  131 + }
  132 + });
  133 + if (domainParams.getClientRegistrations() == null || domainParams.getClientRegistrations().isEmpty()) {
  134 + throw new DataValidationException("Client registrations should be specified!");
  135 + }
  136 + for (ClientRegistrationDto clientRegistration : domainParams.getClientRegistrations()) {
  137 + if (StringUtils.isEmpty(clientRegistration.getClientId())) {
  138 + throw new DataValidationException("Client ID should be specified!");
  139 + }
  140 + if (StringUtils.isEmpty(clientRegistration.getClientSecret())) {
  141 + throw new DataValidationException("Client secret should be specified!");
  142 + }
  143 + if (StringUtils.isEmpty(clientRegistration.getAuthorizationUri())) {
  144 + throw new DataValidationException("Authorization uri should be specified!");
  145 + }
  146 + if (StringUtils.isEmpty(clientRegistration.getAccessTokenUri())) {
  147 + throw new DataValidationException("Token uri should be specified!");
  148 + }
  149 + if (StringUtils.isEmpty(clientRegistration.getScope())) {
  150 + throw new DataValidationException("Scope should be specified!");
  151 + }
  152 + if (StringUtils.isEmpty(clientRegistration.getUserInfoUri())) {
  153 + throw new DataValidationException("User info uri should be specified!");
  154 + }
  155 + if (StringUtils.isEmpty(clientRegistration.getUserNameAttributeName())) {
  156 + throw new DataValidationException("User name attribute name should be specified!");
  157 + }
  158 + if (StringUtils.isEmpty(clientRegistration.getClientAuthenticationMethod())) {
  159 + throw new DataValidationException("Client authentication method should be specified!");
  160 + }
  161 + if (StringUtils.isEmpty(clientRegistration.getLoginButtonLabel())) {
  162 + throw new DataValidationException("Login button label should be specified!");
  163 + }
  164 + OAuth2MapperConfig mapperConfig = clientRegistration.getMapperConfig();
  165 + if (mapperConfig == null) {
  166 + throw new DataValidationException("Mapper config should be specified!");
  167 + }
  168 + if (mapperConfig.getType() == null) {
  169 + throw new DataValidationException("Mapper config type should be specified!");
  170 + }
  171 + if (mapperConfig.getType() == MapperType.BASIC) {
  172 + OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic();
  173 + if (basicConfig == null) {
  174 + throw new DataValidationException("Basic config should be specified!");
  175 + }
  176 + if (StringUtils.isEmpty(basicConfig.getEmailAttributeKey())) {
  177 + throw new DataValidationException("Email attribute key should be specified!");
  178 + }
  179 + if (basicConfig.getTenantNameStrategy() == null) {
  180 + throw new DataValidationException("Tenant name strategy should be specified!");
  181 + }
  182 + if (basicConfig.getTenantNameStrategy() == TenantNameStrategyType.CUSTOM
  183 + && StringUtils.isEmpty(basicConfig.getTenantNamePattern())) {
  184 + throw new DataValidationException("Tenant name pattern should be specified!");
  185 + }
  186 + }
  187 + if (mapperConfig.getType() == MapperType.GITHUB) {
  188 + OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic();
  189 + if (basicConfig == null) {
  190 + throw new DataValidationException("Basic config should be specified!");
  191 + }
  192 + if (!StringUtils.isEmpty(basicConfig.getEmailAttributeKey())) {
  193 + throw new DataValidationException("Email attribute key cannot be configured for GITHUB mapper type!");
  194 + }
  195 + if (basicConfig.getTenantNameStrategy() == null) {
  196 + throw new DataValidationException("Tenant name strategy should be specified!");
  197 + }
  198 + if (basicConfig.getTenantNameStrategy() == TenantNameStrategyType.CUSTOM
  199 + && StringUtils.isEmpty(basicConfig.getTenantNamePattern())) {
  200 + throw new DataValidationException("Tenant name pattern should be specified!");
  201 + }
  202 + }
  203 + if (mapperConfig.getType() == MapperType.CUSTOM) {
  204 + OAuth2CustomMapperConfig customConfig = mapperConfig.getCustom();
  205 + if (customConfig == null) {
  206 + throw new DataValidationException("Custom config should be specified!");
  207 + }
  208 + if (StringUtils.isEmpty(customConfig.getUrl())) {
  209 + throw new DataValidationException("Custom mapper URL should be specified!");
  210 + }
  211 + }
  212 + }
  213 + }
  214 + };
50 215 }
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.oauth2;
  17 +
  18 +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId;
  19 +import org.thingsboard.server.common.data.oauth2.*;
  20 +
  21 +import java.util.*;
  22 +
  23 +public class OAuth2Utils {
  24 + public static final String OAUTH2_AUTHORIZATION_PATH_TEMPLATE = "/oauth2/authorization/%s";
  25 +
  26 + public static OAuth2ClientInfo toClientInfo(OAuth2ClientRegistrationInfo clientRegistrationInfo) {
  27 + OAuth2ClientInfo client = new OAuth2ClientInfo();
  28 + client.setName(clientRegistrationInfo.getLoginButtonLabel());
  29 + client.setUrl(String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, clientRegistrationInfo.getUuidId().toString()));
  30 + client.setIcon(clientRegistrationInfo.getLoginButtonIcon());
  31 + return client;
  32 + }
  33 +
  34 + public static OAuth2ClientsParams toOAuth2Params(List<ExtendedOAuth2ClientRegistrationInfo> extendedOAuth2ClientRegistrationInfos) {
  35 + Map<OAuth2ClientRegistrationInfoId, Set<DomainInfo>> domainsByInfoId = new HashMap<>();
  36 + Map<OAuth2ClientRegistrationInfoId, OAuth2ClientRegistrationInfo> infoById = new HashMap<>();
  37 + for (ExtendedOAuth2ClientRegistrationInfo extendedClientRegistrationInfo : extendedOAuth2ClientRegistrationInfos) {
  38 + String domainName = extendedClientRegistrationInfo.getDomainName();
  39 + SchemeType domainScheme = extendedClientRegistrationInfo.getDomainScheme();
  40 + domainsByInfoId.computeIfAbsent(extendedClientRegistrationInfo.getId(), key -> new HashSet<>())
  41 + .add(new DomainInfo(domainScheme, domainName));
  42 + infoById.put(extendedClientRegistrationInfo.getId(), extendedClientRegistrationInfo);
  43 + }
  44 + Map<Set<DomainInfo>, OAuth2ClientsDomainParams> domainParamsMap = new HashMap<>();
  45 + domainsByInfoId.forEach((clientRegistrationInfoId, domainInfos) -> {
  46 + domainParamsMap.computeIfAbsent(domainInfos,
  47 + key -> new OAuth2ClientsDomainParams(key, new HashSet<>())
  48 + )
  49 + .getClientRegistrations()
  50 + .add(toClientRegistrationDto(infoById.get(clientRegistrationInfoId)));
  51 + });
  52 + boolean enabled = extendedOAuth2ClientRegistrationInfos.stream()
  53 + .map(OAuth2ClientRegistrationInfo::isEnabled)
  54 + .findFirst().orElse(false);
  55 + return new OAuth2ClientsParams(enabled, new HashSet<>(domainParamsMap.values()));
  56 + }
  57 +
  58 + public static ClientRegistrationDto toClientRegistrationDto(OAuth2ClientRegistrationInfo oAuth2ClientRegistrationInfo) {
  59 + return ClientRegistrationDto.builder()
  60 + .mapperConfig(oAuth2ClientRegistrationInfo.getMapperConfig())
  61 + .clientId(oAuth2ClientRegistrationInfo.getClientId())
  62 + .clientSecret(oAuth2ClientRegistrationInfo.getClientSecret())
  63 + .authorizationUri(oAuth2ClientRegistrationInfo.getAuthorizationUri())
  64 + .accessTokenUri(oAuth2ClientRegistrationInfo.getAccessTokenUri())
  65 + .scope(oAuth2ClientRegistrationInfo.getScope())
  66 + .userInfoUri(oAuth2ClientRegistrationInfo.getUserInfoUri())
  67 + .userNameAttributeName(oAuth2ClientRegistrationInfo.getUserNameAttributeName())
  68 + .jwkSetUri(oAuth2ClientRegistrationInfo.getJwkSetUri())
  69 + .clientAuthenticationMethod(oAuth2ClientRegistrationInfo.getClientAuthenticationMethod())
  70 + .loginButtonLabel(oAuth2ClientRegistrationInfo.getLoginButtonLabel())
  71 + .loginButtonIcon(oAuth2ClientRegistrationInfo.getLoginButtonIcon())
  72 + .additionalInfo(oAuth2ClientRegistrationInfo.getAdditionalInfo())
  73 + .build();
  74 + }
  75 +
  76 + public static OAuth2ClientRegistrationInfo toClientRegistrationInfo(boolean enabled, ClientRegistrationDto clientRegistrationDto) {
  77 + OAuth2ClientRegistrationInfo clientRegistrationInfo = new OAuth2ClientRegistrationInfo();
  78 + clientRegistrationInfo.setEnabled(enabled);
  79 + clientRegistrationInfo.setMapperConfig(clientRegistrationDto.getMapperConfig());
  80 + clientRegistrationInfo.setClientId(clientRegistrationDto.getClientId());
  81 + clientRegistrationInfo.setClientSecret(clientRegistrationDto.getClientSecret());
  82 + clientRegistrationInfo.setAuthorizationUri(clientRegistrationDto.getAuthorizationUri());
  83 + clientRegistrationInfo.setAccessTokenUri(clientRegistrationDto.getAccessTokenUri());
  84 + clientRegistrationInfo.setScope(clientRegistrationDto.getScope());
  85 + clientRegistrationInfo.setUserInfoUri(clientRegistrationDto.getUserInfoUri());
  86 + clientRegistrationInfo.setUserNameAttributeName(clientRegistrationDto.getUserNameAttributeName());
  87 + clientRegistrationInfo.setJwkSetUri(clientRegistrationDto.getJwkSetUri());
  88 + clientRegistrationInfo.setClientAuthenticationMethod(clientRegistrationDto.getClientAuthenticationMethod());
  89 + clientRegistrationInfo.setLoginButtonLabel(clientRegistrationDto.getLoginButtonLabel());
  90 + clientRegistrationInfo.setLoginButtonIcon(clientRegistrationDto.getLoginButtonIcon());
  91 + clientRegistrationInfo.setAdditionalInfo(clientRegistrationDto.getAdditionalInfo());
  92 + return clientRegistrationInfo;
  93 + }
  94 +
  95 + public static OAuth2ClientRegistration toClientRegistration(OAuth2ClientRegistrationInfoId clientRegistrationInfoId, SchemeType domainScheme, String domainName) {
  96 + OAuth2ClientRegistration clientRegistration = new OAuth2ClientRegistration();
  97 + clientRegistration.setClientRegistrationId(clientRegistrationInfoId);
  98 + clientRegistration.setDomainName(domainName);
  99 + clientRegistration.setDomainScheme(domainScheme);
  100 + return clientRegistration;
  101 + }
  102 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.sql.oauth2;
  17 +
  18 +import lombok.RequiredArgsConstructor;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.springframework.data.repository.CrudRepository;
  21 +import org.springframework.stereotype.Component;
  22 +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration;
  23 +import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationEntity;
  24 +import org.thingsboard.server.dao.oauth2.OAuth2ClientRegistrationDao;
  25 +import org.thingsboard.server.dao.sql.JpaAbstractDao;
  26 +
  27 +import java.util.UUID;
  28 +
  29 +@Component
  30 +@RequiredArgsConstructor
  31 +public class JpaOAuth2ClientRegistrationDao extends JpaAbstractDao<OAuth2ClientRegistrationEntity, OAuth2ClientRegistration> implements OAuth2ClientRegistrationDao {
  32 + private final OAuth2ClientRegistrationRepository repository;
  33 +
  34 + @Override
  35 + protected Class<OAuth2ClientRegistrationEntity> getEntityClass() {
  36 + return OAuth2ClientRegistrationEntity.class;
  37 + }
  38 +
  39 + @Override
  40 + protected CrudRepository<OAuth2ClientRegistrationEntity, UUID> getCrudRepository() {
  41 + return repository;
  42 + }
  43 +
  44 + @Override
  45 + public void deleteAll() {
  46 + repository.deleteAll();
  47 + }
  48 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.sql.oauth2;
  17 +
  18 +import lombok.RequiredArgsConstructor;
  19 +import org.springframework.data.repository.CrudRepository;
  20 +import org.springframework.stereotype.Component;
  21 +import org.thingsboard.server.common.data.oauth2.ExtendedOAuth2ClientRegistrationInfo;
  22 +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
  23 +import org.thingsboard.server.common.data.oauth2.SchemeType;
  24 +import org.thingsboard.server.dao.DaoUtil;
  25 +import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationInfoEntity;
  26 +import org.thingsboard.server.dao.oauth2.OAuth2ClientRegistrationInfoDao;
  27 +import org.thingsboard.server.dao.sql.JpaAbstractDao;
  28 +
  29 +import java.util.ArrayList;
  30 +import java.util.List;
  31 +import java.util.UUID;
  32 +import java.util.stream.Collectors;
  33 +
  34 +@Component
  35 +@RequiredArgsConstructor
  36 +public class JpaOAuth2ClientRegistrationInfoDao extends JpaAbstractDao<OAuth2ClientRegistrationInfoEntity, OAuth2ClientRegistrationInfo> implements OAuth2ClientRegistrationInfoDao {
  37 + private final OAuth2ClientRegistrationInfoRepository repository;
  38 +
  39 + @Override
  40 + protected Class<OAuth2ClientRegistrationInfoEntity> getEntityClass() {
  41 + return OAuth2ClientRegistrationInfoEntity.class;
  42 + }
  43 +
  44 + @Override
  45 + protected CrudRepository<OAuth2ClientRegistrationInfoEntity, UUID> getCrudRepository() {
  46 + return repository;
  47 + }
  48 +
  49 + @Override
  50 + public List<OAuth2ClientRegistrationInfo> findAll() {
  51 + Iterable<OAuth2ClientRegistrationInfoEntity> entities = repository.findAll();
  52 + List<OAuth2ClientRegistrationInfo> result = new ArrayList<>();
  53 + entities.forEach(entity -> {
  54 + result.add(DaoUtil.getData(entity));
  55 + });
  56 + return result;
  57 + }
  58 +
  59 + @Override
  60 + public List<ExtendedOAuth2ClientRegistrationInfo> findAllExtended() {
  61 + return repository.findAllExtended().stream()
  62 + .map(DaoUtil::getData)
  63 + .collect(Collectors.toList());
  64 + }
  65 +
  66 + @Override
  67 + public List<OAuth2ClientRegistrationInfo> findByDomainSchemesAndDomainName(List<SchemeType> domainSchemes, String domainName) {
  68 + List<OAuth2ClientRegistrationInfoEntity> entities = repository.findAllByDomainSchemesAndName(domainSchemes, domainName);
  69 + return entities.stream().map(DaoUtil::getData).collect(Collectors.toList());
  70 + }
  71 +
  72 + @Override
  73 + public void deleteAll() {
  74 + repository.deleteAll();
  75 + }
  76 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.sql.oauth2;
  17 +
  18 +import lombok.RequiredArgsConstructor;
  19 +import org.springframework.data.repository.CrudRepository;
  20 +import org.springframework.stereotype.Component;
  21 +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate;
  22 +import org.thingsboard.server.dao.DaoUtil;
  23 +import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationTemplateEntity;
  24 +import org.thingsboard.server.dao.oauth2.OAuth2ClientRegistrationTemplateDao;
  25 +import org.thingsboard.server.dao.sql.JpaAbstractDao;
  26 +
  27 +import java.util.ArrayList;
  28 +import java.util.List;
  29 +import java.util.UUID;
  30 +
  31 +@Component
  32 +@RequiredArgsConstructor
  33 +public class JpaOAuth2ClientRegistrationTemplateDao extends JpaAbstractDao<OAuth2ClientRegistrationTemplateEntity, OAuth2ClientRegistrationTemplate> implements OAuth2ClientRegistrationTemplateDao {
  34 + private final OAuth2ClientRegistrationTemplateRepository repository;
  35 +
  36 + @Override
  37 + protected Class<OAuth2ClientRegistrationTemplateEntity> getEntityClass() {
  38 + return OAuth2ClientRegistrationTemplateEntity.class;
  39 + }
  40 +
  41 + @Override
  42 + protected CrudRepository<OAuth2ClientRegistrationTemplateEntity, UUID> getCrudRepository() {
  43 + return repository;
  44 + }
  45 +
  46 + @Override
  47 + public List<OAuth2ClientRegistrationTemplate> findAll() {
  48 + Iterable<OAuth2ClientRegistrationTemplateEntity> entities = repository.findAll();
  49 + List<OAuth2ClientRegistrationTemplate> result = new ArrayList<>();
  50 + entities.forEach(entity -> result.add(DaoUtil.getData(entity)));
  51 + return result;
  52 + }
  53 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.sql.oauth2;
  17 +
  18 +import org.springframework.data.jpa.repository.Query;
  19 +import org.springframework.data.repository.CrudRepository;
  20 +import org.springframework.data.repository.query.Param;
  21 +import org.thingsboard.server.common.data.oauth2.SchemeType;
  22 +import org.thingsboard.server.dao.model.sql.ExtendedOAuth2ClientRegistrationInfoEntity;
  23 +import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationInfoEntity;
  24 +
  25 +import java.util.List;
  26 +import java.util.UUID;
  27 +
  28 +public interface OAuth2ClientRegistrationInfoRepository extends CrudRepository<OAuth2ClientRegistrationInfoEntity, UUID> {
  29 + @Query("SELECT new OAuth2ClientRegistrationInfoEntity(cr_info) " +
  30 + "FROM OAuth2ClientRegistrationInfoEntity cr_info " +
  31 + "LEFT JOIN OAuth2ClientRegistrationEntity cr on cr_info.id = cr.clientRegistrationInfoId " +
  32 + "WHERE cr.domainName = :domainName " +
  33 + "AND cr.domainScheme IN (:domainSchemes)")
  34 + List<OAuth2ClientRegistrationInfoEntity> findAllByDomainSchemesAndName(@Param("domainSchemes") List<SchemeType> domainSchemes,
  35 + @Param("domainName") String domainName);
  36 +
  37 + @Query("SELECT new org.thingsboard.server.dao.model.sql.ExtendedOAuth2ClientRegistrationInfoEntity(cr_info, cr.domainName, cr.domainScheme) " +
  38 + "FROM OAuth2ClientRegistrationInfoEntity cr_info " +
  39 + "LEFT JOIN OAuth2ClientRegistrationEntity cr on cr_info.id = cr.clientRegistrationInfoId ")
  40 + List<ExtendedOAuth2ClientRegistrationInfoEntity> findAllExtended();
  41 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.sql.oauth2;
  17 +
  18 +import org.springframework.data.repository.CrudRepository;
  19 +import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationEntity;
  20 +
  21 +import java.util.UUID;
  22 +
  23 +public interface OAuth2ClientRegistrationRepository extends CrudRepository<OAuth2ClientRegistrationEntity, UUID> {
  24 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.sql.oauth2;
  17 +
  18 +import org.springframework.data.repository.CrudRepository;
  19 +import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationTemplateEntity;
  20 +
  21 +import java.util.UUID;
  22 +
  23 +public interface OAuth2ClientRegistrationTemplateRepository extends CrudRepository<OAuth2ClientRegistrationTemplateEntity, UUID> {
  24 +}
... ...
... ... @@ -331,3 +331,73 @@ CREATE TABLE IF NOT EXISTS ts_kv_dictionary (
331 331 key_id int GENERATED BY DEFAULT AS IDENTITY(start with 0 increment by 1) UNIQUE,
332 332 CONSTRAINT ts_key_id_pkey PRIMARY KEY (key)
333 333 );
  334 +
  335 +
  336 +CREATE TABLE IF NOT EXISTS oauth2_client_registration_info (
  337 + id uuid NOT NULL CONSTRAINT oauth2_client_registration_info_pkey PRIMARY KEY,
  338 + enabled boolean,
  339 + created_time bigint NOT NULL,
  340 + additional_info varchar,
  341 + client_id varchar(255),
  342 + client_secret varchar(255),
  343 + authorization_uri varchar(255),
  344 + token_uri varchar(255),
  345 + scope varchar(255),
  346 + user_info_uri varchar(255),
  347 + user_name_attribute_name varchar(255),
  348 + jwk_set_uri varchar(255),
  349 + client_authentication_method varchar(255),
  350 + login_button_label varchar(255),
  351 + login_button_icon varchar(255),
  352 + allow_user_creation boolean,
  353 + activate_user boolean,
  354 + type varchar(31),
  355 + basic_email_attribute_key varchar(31),
  356 + basic_first_name_attribute_key varchar(31),
  357 + basic_last_name_attribute_key varchar(31),
  358 + basic_tenant_name_strategy varchar(31),
  359 + basic_tenant_name_pattern varchar(255),
  360 + basic_customer_name_pattern varchar(255),
  361 + basic_default_dashboard_name varchar(255),
  362 + basic_always_full_screen boolean,
  363 + custom_url varchar(255),
  364 + custom_username varchar(255),
  365 + custom_password varchar(255),
  366 + custom_send_token boolean
  367 +);
  368 +
  369 +CREATE TABLE IF NOT EXISTS oauth2_client_registration (
  370 + id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY,
  371 + created_time bigint NOT NULL,
  372 + domain_name varchar(255),
  373 + domain_scheme varchar(31),
  374 + client_registration_info_id uuid
  375 +);
  376 +
  377 +CREATE TABLE IF NOT EXISTS oauth2_client_registration_template (
  378 + id uuid NOT NULL CONSTRAINT oauth2_client_registration_template_pkey PRIMARY KEY,
  379 + created_time bigint NOT NULL,
  380 + additional_info varchar,
  381 + provider_id varchar(255),
  382 + authorization_uri varchar(255),
  383 + token_uri varchar(255),
  384 + scope varchar(255),
  385 + user_info_uri varchar(255),
  386 + user_name_attribute_name varchar(255),
  387 + jwk_set_uri varchar(255),
  388 + client_authentication_method varchar(255),
  389 + type varchar(31),
  390 + basic_email_attribute_key varchar(31),
  391 + basic_first_name_attribute_key varchar(31),
  392 + basic_last_name_attribute_key varchar(31),
  393 + basic_tenant_name_strategy varchar(31),
  394 + basic_tenant_name_pattern varchar(255),
  395 + basic_customer_name_pattern varchar(255),
  396 + basic_default_dashboard_name varchar(255),
  397 + basic_always_full_screen boolean,
  398 + comment varchar,
  399 + login_button_icon varchar(255),
  400 + login_button_label varchar(255),
  401 + help_link varchar(255),
  402 + CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id)
  403 +);
... ...
... ... @@ -359,6 +359,75 @@ CREATE TABLE IF NOT EXISTS ts_kv_dictionary
359 359 CONSTRAINT ts_key_id_pkey PRIMARY KEY (key)
360 360 );
361 361
  362 +CREATE TABLE IF NOT EXISTS oauth2_client_registration_info (
  363 + id uuid NOT NULL CONSTRAINT oauth2_client_registration_info_pkey PRIMARY KEY,
  364 + enabled boolean,
  365 + created_time bigint NOT NULL,
  366 + additional_info varchar,
  367 + client_id varchar(255),
  368 + client_secret varchar(255),
  369 + authorization_uri varchar(255),
  370 + token_uri varchar(255),
  371 + scope varchar(255),
  372 + user_info_uri varchar(255),
  373 + user_name_attribute_name varchar(255),
  374 + jwk_set_uri varchar(255),
  375 + client_authentication_method varchar(255),
  376 + login_button_label varchar(255),
  377 + login_button_icon varchar(255),
  378 + allow_user_creation boolean,
  379 + activate_user boolean,
  380 + type varchar(31),
  381 + basic_email_attribute_key varchar(31),
  382 + basic_first_name_attribute_key varchar(31),
  383 + basic_last_name_attribute_key varchar(31),
  384 + basic_tenant_name_strategy varchar(31),
  385 + basic_tenant_name_pattern varchar(255),
  386 + basic_customer_name_pattern varchar(255),
  387 + basic_default_dashboard_name varchar(255),
  388 + basic_always_full_screen boolean,
  389 + custom_url varchar(255),
  390 + custom_username varchar(255),
  391 + custom_password varchar(255),
  392 + custom_send_token boolean
  393 +);
  394 +
  395 +CREATE TABLE IF NOT EXISTS oauth2_client_registration (
  396 + id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY,
  397 + created_time bigint NOT NULL,
  398 + domain_name varchar(255),
  399 + domain_scheme varchar(31),
  400 + client_registration_info_id uuid
  401 +);
  402 +
  403 +CREATE TABLE IF NOT EXISTS oauth2_client_registration_template (
  404 + id uuid NOT NULL CONSTRAINT oauth2_client_registration_template_pkey PRIMARY KEY,
  405 + created_time bigint NOT NULL,
  406 + additional_info varchar,
  407 + provider_id varchar(255),
  408 + authorization_uri varchar(255),
  409 + token_uri varchar(255),
  410 + scope varchar(255),
  411 + user_info_uri varchar(255),
  412 + user_name_attribute_name varchar(255),
  413 + jwk_set_uri varchar(255),
  414 + client_authentication_method varchar(255),
  415 + type varchar(31),
  416 + basic_email_attribute_key varchar(31),
  417 + basic_first_name_attribute_key varchar(31),
  418 + basic_last_name_attribute_key varchar(31),
  419 + basic_tenant_name_strategy varchar(31),
  420 + basic_tenant_name_pattern varchar(255),
  421 + basic_customer_name_pattern varchar(255),
  422 + basic_default_dashboard_name varchar(255),
  423 + basic_always_full_screen boolean,
  424 + comment varchar,
  425 + login_button_icon varchar(255),
  426 + login_button_label varchar(255),
  427 + help_link varchar(255),
  428 + CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id)
  429 +);
  430 +
362 431 CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl(IN ttl bigint, IN debug_ttl bigint, INOUT deleted bigint)
363 432 LANGUAGE plpgsql AS
364 433 $$
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.service;
  17 +
  18 +import org.junit.After;
  19 +import org.junit.Assert;
  20 +import org.junit.Before;
  21 +import org.junit.Test;
  22 +import org.springframework.beans.factory.annotation.Autowired;
  23 +import org.thingsboard.server.common.data.id.TenantId;
  24 +import org.thingsboard.server.common.data.oauth2.MapperType;
  25 +import org.thingsboard.server.common.data.oauth2.OAuth2BasicMapperConfig;
  26 +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate;
  27 +import org.thingsboard.server.dao.exception.DataValidationException;
  28 +import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
  29 +
  30 +import java.util.Arrays;
  31 +import java.util.UUID;
  32 +
  33 +public class BaseOAuth2ConfigTemplateServiceTest extends AbstractServiceTest {
  34 +
  35 + @Autowired
  36 + protected OAuth2ConfigTemplateService oAuth2ConfigTemplateService;
  37 +
  38 + @Before
  39 + public void beforeRun() throws Exception {
  40 + Assert.assertTrue(oAuth2ConfigTemplateService.findAllClientRegistrationTemplates().isEmpty());
  41 + }
  42 +
  43 + @After
  44 + public void after() throws Exception {
  45 + oAuth2ConfigTemplateService.findAllClientRegistrationTemplates().forEach(clientRegistrationTemplate -> {
  46 + oAuth2ConfigTemplateService.deleteClientRegistrationTemplateById(clientRegistrationTemplate.getId());
  47 + });
  48 +
  49 + Assert.assertTrue(oAuth2ConfigTemplateService.findAllClientRegistrationTemplates().isEmpty());
  50 + }
  51 +
  52 +
  53 + @Test(expected = DataValidationException.class)
  54 + public void testSaveDuplicateProviderId() {
  55 + OAuth2ClientRegistrationTemplate first = validClientRegistrationTemplate("providerId");
  56 + OAuth2ClientRegistrationTemplate second = validClientRegistrationTemplate("providerId");
  57 + oAuth2ConfigTemplateService.saveClientRegistrationTemplate(first);
  58 + oAuth2ConfigTemplateService.saveClientRegistrationTemplate(second);
  59 + }
  60 +
  61 + @Test
  62 + public void testCreateNewTemplate() {
  63 + OAuth2ClientRegistrationTemplate clientRegistrationTemplate = validClientRegistrationTemplate(UUID.randomUUID().toString());
  64 + OAuth2ClientRegistrationTemplate savedClientRegistrationTemplate = oAuth2ConfigTemplateService.saveClientRegistrationTemplate(clientRegistrationTemplate);
  65 +
  66 + Assert.assertNotNull(savedClientRegistrationTemplate);
  67 + Assert.assertNotNull(savedClientRegistrationTemplate.getId());
  68 + clientRegistrationTemplate.setId(savedClientRegistrationTemplate.getId());
  69 + clientRegistrationTemplate.setCreatedTime(savedClientRegistrationTemplate.getCreatedTime());
  70 + Assert.assertEquals(clientRegistrationTemplate, savedClientRegistrationTemplate);
  71 + }
  72 +
  73 + @Test
  74 + public void testFindTemplate() {
  75 + OAuth2ClientRegistrationTemplate clientRegistrationTemplate = validClientRegistrationTemplate(UUID.randomUUID().toString());
  76 + OAuth2ClientRegistrationTemplate savedClientRegistrationTemplate = oAuth2ConfigTemplateService.saveClientRegistrationTemplate(clientRegistrationTemplate);
  77 +
  78 + OAuth2ClientRegistrationTemplate foundClientRegistrationTemplate = oAuth2ConfigTemplateService.findClientRegistrationTemplateById(savedClientRegistrationTemplate.getId());
  79 + Assert.assertEquals(savedClientRegistrationTemplate, foundClientRegistrationTemplate);
  80 + }
  81 +
  82 + @Test
  83 + public void testFindAll() {
  84 + oAuth2ConfigTemplateService.saveClientRegistrationTemplate(validClientRegistrationTemplate(UUID.randomUUID().toString()));
  85 + oAuth2ConfigTemplateService.saveClientRegistrationTemplate(validClientRegistrationTemplate(UUID.randomUUID().toString()));
  86 +
  87 + Assert.assertEquals(2, oAuth2ConfigTemplateService.findAllClientRegistrationTemplates().size());
  88 + }
  89 +
  90 + @Test
  91 + public void testDeleteTemplate() {
  92 + oAuth2ConfigTemplateService.saveClientRegistrationTemplate(validClientRegistrationTemplate(UUID.randomUUID().toString()));
  93 + oAuth2ConfigTemplateService.saveClientRegistrationTemplate(validClientRegistrationTemplate(UUID.randomUUID().toString()));
  94 + OAuth2ClientRegistrationTemplate saved = oAuth2ConfigTemplateService.saveClientRegistrationTemplate(validClientRegistrationTemplate(UUID.randomUUID().toString()));
  95 +
  96 + Assert.assertEquals(3, oAuth2ConfigTemplateService.findAllClientRegistrationTemplates().size());
  97 + Assert.assertNotNull(oAuth2ConfigTemplateService.findClientRegistrationTemplateById(saved.getId()));
  98 +
  99 + oAuth2ConfigTemplateService.deleteClientRegistrationTemplateById(saved.getId());
  100 +
  101 + Assert.assertEquals(2, oAuth2ConfigTemplateService.findAllClientRegistrationTemplates().size());
  102 + Assert.assertNull(oAuth2ConfigTemplateService.findClientRegistrationTemplateById(saved.getId()));
  103 + }
  104 +
  105 + private OAuth2ClientRegistrationTemplate validClientRegistrationTemplate(String providerId) {
  106 + OAuth2ClientRegistrationTemplate clientRegistrationTemplate = new OAuth2ClientRegistrationTemplate();
  107 + clientRegistrationTemplate.setProviderId(providerId);
  108 + clientRegistrationTemplate.setAdditionalInfo(mapper.createObjectNode().put(UUID.randomUUID().toString(), UUID.randomUUID().toString()));
  109 + clientRegistrationTemplate.setMapperType(MapperType.BASIC);
  110 + clientRegistrationTemplate.setBasic(
  111 + OAuth2BasicMapperConfig.builder()
  112 + .firstNameAttributeKey("firstName")
  113 + .lastNameAttributeKey("lastName")
  114 + .emailAttributeKey("email")
  115 + .tenantNamePattern("tenant")
  116 + .defaultDashboardName("Test")
  117 + .alwaysFullScreen(true)
  118 + .build()
  119 + );
  120 + clientRegistrationTemplate.setAuthorizationUri("authorizationUri");
  121 + clientRegistrationTemplate.setAccessTokenUri("tokenUri");
  122 + clientRegistrationTemplate.setScope(Arrays.asList("scope1", "scope2"));
  123 + clientRegistrationTemplate.setUserInfoUri("userInfoUri");
  124 + clientRegistrationTemplate.setUserNameAttributeName("userNameAttributeName");
  125 + clientRegistrationTemplate.setJwkSetUri("jwkSetUri");
  126 + clientRegistrationTemplate.setClientAuthenticationMethod("clientAuthenticationMethod");
  127 + clientRegistrationTemplate.setComment("comment");
  128 + clientRegistrationTemplate.setLoginButtonIcon("icon");
  129 + clientRegistrationTemplate.setLoginButtonLabel("label");
  130 + clientRegistrationTemplate.setHelpLink("helpLink");
  131 + return clientRegistrationTemplate;
  132 + }
  133 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.service;
  17 +
  18 +import com.google.common.collect.Sets;
  19 +import org.junit.After;
  20 +import org.junit.Assert;
  21 +import org.junit.Before;
  22 +import org.junit.Test;
  23 +import org.springframework.beans.factory.annotation.Autowired;
  24 +import org.thingsboard.server.common.data.oauth2.*;
  25 +import org.thingsboard.server.dao.exception.DataValidationException;
  26 +import org.thingsboard.server.dao.oauth2.OAuth2Service;
  27 +
  28 +import java.util.*;
  29 +import java.util.stream.Collectors;
  30 +
  31 +public class BaseOAuth2ServiceTest extends AbstractServiceTest {
  32 + private static final OAuth2ClientsParams EMPTY_PARAMS = new OAuth2ClientsParams(false, new HashSet<>());
  33 +
  34 + @Autowired
  35 + protected OAuth2Service oAuth2Service;
  36 +
  37 + @Before
  38 + public void beforeRun() {
  39 + Assert.assertTrue(oAuth2Service.findAllClientRegistrationInfos().isEmpty());
  40 + }
  41 +
  42 + @After
  43 + public void after() {
  44 + oAuth2Service.saveOAuth2Params(EMPTY_PARAMS);
  45 + Assert.assertTrue(oAuth2Service.findAllClientRegistrationInfos().isEmpty());
  46 + Assert.assertTrue(oAuth2Service.findOAuth2Params().getDomainsParams().isEmpty());
  47 + }
  48 +
  49 + @Test(expected = DataValidationException.class)
  50 + public void testSaveHttpAndMixedDomainsTogether() {
  51 + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet(
  52 + OAuth2ClientsDomainParams.builder()
  53 + .domainInfos(Sets.newHashSet(
  54 + DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
  55 + DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(),
  56 + DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
  57 + ))
  58 + .clientRegistrations(Sets.newHashSet(
  59 + validClientRegistrationDto(),
  60 + validClientRegistrationDto(),
  61 + validClientRegistrationDto()
  62 + ))
  63 + .build()
  64 + ));
  65 + oAuth2Service.saveOAuth2Params(clientsParams);
  66 + }
  67 +
  68 + @Test(expected = DataValidationException.class)
  69 + public void testSaveHttpsAndMixedDomainsTogether() {
  70 + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet(
  71 + OAuth2ClientsDomainParams.builder()
  72 + .domainInfos(Sets.newHashSet(
  73 + DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build(),
  74 + DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(),
  75 + DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
  76 + ))
  77 + .clientRegistrations(Sets.newHashSet(
  78 + validClientRegistrationDto(),
  79 + validClientRegistrationDto(),
  80 + validClientRegistrationDto()
  81 + ))
  82 + .build()
  83 + ));
  84 + oAuth2Service.saveOAuth2Params(clientsParams);
  85 + }
  86 +
  87 + @Test
  88 + public void testCreateAndFindParams() {
  89 + OAuth2ClientsParams clientsParams = createDefaultClientsParams();
  90 + oAuth2Service.saveOAuth2Params(clientsParams);
  91 + OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params();
  92 + Assert.assertNotNull(foundClientsParams);
  93 + // TODO ask if it's safe to check equality on AdditionalProperties
  94 + Assert.assertEquals(clientsParams, foundClientsParams);
  95 + }
  96 +
  97 + @Test
  98 + public void testDisableParams() {
  99 + OAuth2ClientsParams clientsParams = createDefaultClientsParams();
  100 + clientsParams.setEnabled(true);
  101 + oAuth2Service.saveOAuth2Params(clientsParams);
  102 + OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params();
  103 + Assert.assertNotNull(foundClientsParams);
  104 + Assert.assertEquals(clientsParams, foundClientsParams);
  105 +
  106 + clientsParams.setEnabled(false);
  107 + oAuth2Service.saveOAuth2Params(clientsParams);
  108 + OAuth2ClientsParams foundDisabledClientsParams = oAuth2Service.findOAuth2Params();
  109 + Assert.assertEquals(clientsParams, foundDisabledClientsParams);
  110 + }
  111 +
  112 + @Test
  113 + public void testClearDomainParams() {
  114 + OAuth2ClientsParams clientsParams = createDefaultClientsParams();
  115 + oAuth2Service.saveOAuth2Params(clientsParams);
  116 + OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params();
  117 + Assert.assertNotNull(foundClientsParams);
  118 + Assert.assertEquals(clientsParams, foundClientsParams);
  119 +
  120 + oAuth2Service.saveOAuth2Params(EMPTY_PARAMS);
  121 + OAuth2ClientsParams foundAfterClearClientsParams = oAuth2Service.findOAuth2Params();
  122 + Assert.assertNotNull(foundAfterClearClientsParams);
  123 + Assert.assertEquals(EMPTY_PARAMS, foundAfterClearClientsParams);
  124 + }
  125 +
  126 + @Test
  127 + public void testUpdateClientsParams() {
  128 + OAuth2ClientsParams clientsParams = createDefaultClientsParams();
  129 + oAuth2Service.saveOAuth2Params(clientsParams);
  130 + OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params();
  131 + Assert.assertNotNull(foundClientsParams);
  132 + Assert.assertEquals(clientsParams, foundClientsParams);
  133 +
  134 + OAuth2ClientsParams newClientsParams = new OAuth2ClientsParams(true, Sets.newHashSet(
  135 + OAuth2ClientsDomainParams.builder()
  136 + .domainInfos(Sets.newHashSet(
  137 + DomainInfo.builder().name("another-domain").scheme(SchemeType.HTTPS).build()
  138 + ))
  139 + .clientRegistrations(Sets.newHashSet(
  140 + validClientRegistrationDto()
  141 + ))
  142 + .build(),
  143 + OAuth2ClientsDomainParams.builder()
  144 + .domainInfos(Sets.newHashSet(
  145 + DomainInfo.builder().name("test-domain").scheme(SchemeType.MIXED).build()
  146 + ))
  147 + .clientRegistrations(Sets.newHashSet(
  148 + validClientRegistrationDto()
  149 + ))
  150 + .build()
  151 + ));
  152 + oAuth2Service.saveOAuth2Params(newClientsParams);
  153 + OAuth2ClientsParams foundAfterUpdateClientsParams = oAuth2Service.findOAuth2Params();
  154 + Assert.assertNotNull(foundAfterUpdateClientsParams);
  155 + Assert.assertEquals(newClientsParams, foundAfterUpdateClientsParams);
  156 + }
  157 +
  158 + @Test
  159 + public void testGetOAuth2Clients() {
  160 + Set<ClientRegistrationDto> firstGroup = Sets.newHashSet(
  161 + validClientRegistrationDto(),
  162 + validClientRegistrationDto(),
  163 + validClientRegistrationDto(),
  164 + validClientRegistrationDto()
  165 + );
  166 + Set<ClientRegistrationDto> secondGroup = Sets.newHashSet(
  167 + validClientRegistrationDto(),
  168 + validClientRegistrationDto()
  169 + );
  170 + Set<ClientRegistrationDto> thirdGroup = Sets.newHashSet(
  171 + validClientRegistrationDto()
  172 + );
  173 + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet(
  174 + OAuth2ClientsDomainParams.builder()
  175 + .domainInfos(Sets.newHashSet(
  176 + DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
  177 + DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
  178 + DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
  179 + ))
  180 + .clientRegistrations(firstGroup)
  181 + .build(),
  182 + OAuth2ClientsDomainParams.builder()
  183 + .domainInfos(Sets.newHashSet(
  184 + DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(),
  185 + DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build()
  186 + ))
  187 + .clientRegistrations(secondGroup)
  188 + .build(),
  189 + OAuth2ClientsDomainParams.builder()
  190 + .domainInfos(Sets.newHashSet(
  191 + DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(),
  192 + DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build()
  193 + ))
  194 + .clientRegistrations(thirdGroup)
  195 + .build()
  196 + ));
  197 +
  198 + oAuth2Service.saveOAuth2Params(clientsParams);
  199 + OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params();
  200 + Assert.assertNotNull(foundClientsParams);
  201 + Assert.assertEquals(clientsParams, foundClientsParams);
  202 +
  203 + List<OAuth2ClientInfo> firstGroupClientInfos = firstGroup.stream()
  204 + .map(clientRegistrationDto -> new OAuth2ClientInfo(
  205 + clientRegistrationDto.getLoginButtonLabel(), clientRegistrationDto.getLoginButtonIcon(), null))
  206 + .collect(Collectors.toList());
  207 + List<OAuth2ClientInfo> secondGroupClientInfos = secondGroup.stream()
  208 + .map(clientRegistrationDto -> new OAuth2ClientInfo(
  209 + clientRegistrationDto.getLoginButtonLabel(), clientRegistrationDto.getLoginButtonIcon(), null))
  210 + .collect(Collectors.toList());
  211 + List<OAuth2ClientInfo> thirdGroupClientInfos = thirdGroup.stream()
  212 + .map(clientRegistrationDto -> new OAuth2ClientInfo(
  213 + clientRegistrationDto.getLoginButtonLabel(), clientRegistrationDto.getLoginButtonIcon(), null))
  214 + .collect(Collectors.toList());
  215 +
  216 + List<OAuth2ClientInfo> nonExistentDomainClients = oAuth2Service.getOAuth2Clients("http", "non-existent-domain");
  217 + Assert.assertTrue(nonExistentDomainClients.isEmpty());
  218 +
  219 + List<OAuth2ClientInfo> firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain");
  220 + Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpClients.size());
  221 + firstGroupClientInfos.forEach(firstGroupClientInfo -> {
  222 + Assert.assertTrue(
  223 + firstDomainHttpClients.stream().anyMatch(clientInfo ->
  224 + clientInfo.getIcon().equals(firstGroupClientInfo.getIcon())
  225 + && clientInfo.getName().equals(firstGroupClientInfo.getName()))
  226 + );
  227 + });
  228 +
  229 + List<OAuth2ClientInfo> firstDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "first-domain");
  230 + Assert.assertTrue(firstDomainHttpsClients.isEmpty());
  231 +
  232 + List<OAuth2ClientInfo> fourthDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "fourth-domain");
  233 + Assert.assertEquals(secondGroupClientInfos.size(), fourthDomainHttpClients.size());
  234 + secondGroupClientInfos.forEach(secondGroupClientInfo -> {
  235 + Assert.assertTrue(
  236 + fourthDomainHttpClients.stream().anyMatch(clientInfo ->
  237 + clientInfo.getIcon().equals(secondGroupClientInfo.getIcon())
  238 + && clientInfo.getName().equals(secondGroupClientInfo.getName()))
  239 + );
  240 + });
  241 + List<OAuth2ClientInfo> fourthDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "fourth-domain");
  242 + Assert.assertEquals(secondGroupClientInfos.size(), fourthDomainHttpsClients.size());
  243 + secondGroupClientInfos.forEach(secondGroupClientInfo -> {
  244 + Assert.assertTrue(
  245 + fourthDomainHttpsClients.stream().anyMatch(clientInfo ->
  246 + clientInfo.getIcon().equals(secondGroupClientInfo.getIcon())
  247 + && clientInfo.getName().equals(secondGroupClientInfo.getName()))
  248 + );
  249 + });
  250 +
  251 + List<OAuth2ClientInfo> secondDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "second-domain");
  252 + Assert.assertEquals(firstGroupClientInfos.size() + secondGroupClientInfos.size(), secondDomainHttpClients.size());
  253 + firstGroupClientInfos.forEach(firstGroupClientInfo -> {
  254 + Assert.assertTrue(
  255 + secondDomainHttpClients.stream().anyMatch(clientInfo ->
  256 + clientInfo.getIcon().equals(firstGroupClientInfo.getIcon())
  257 + && clientInfo.getName().equals(firstGroupClientInfo.getName()))
  258 + );
  259 + });
  260 + secondGroupClientInfos.forEach(secondGroupClientInfo -> {
  261 + Assert.assertTrue(
  262 + secondDomainHttpClients.stream().anyMatch(clientInfo ->
  263 + clientInfo.getIcon().equals(secondGroupClientInfo.getIcon())
  264 + && clientInfo.getName().equals(secondGroupClientInfo.getName()))
  265 + );
  266 + });
  267 +
  268 + List<OAuth2ClientInfo> secondDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "second-domain");
  269 + Assert.assertEquals(firstGroupClientInfos.size() + thirdGroupClientInfos.size(), secondDomainHttpsClients.size());
  270 + firstGroupClientInfos.forEach(firstGroupClientInfo -> {
  271 + Assert.assertTrue(
  272 + secondDomainHttpsClients.stream().anyMatch(clientInfo ->
  273 + clientInfo.getIcon().equals(firstGroupClientInfo.getIcon())
  274 + && clientInfo.getName().equals(firstGroupClientInfo.getName()))
  275 + );
  276 + });
  277 + thirdGroupClientInfos.forEach(thirdGroupClientInfo -> {
  278 + Assert.assertTrue(
  279 + secondDomainHttpsClients.stream().anyMatch(clientInfo ->
  280 + clientInfo.getIcon().equals(thirdGroupClientInfo.getIcon())
  281 + && clientInfo.getName().equals(thirdGroupClientInfo.getName()))
  282 + );
  283 + });
  284 + }
  285 +
  286 + @Test
  287 + public void testGetOAuth2ClientsForHttpAndHttps() {
  288 + Set<ClientRegistrationDto> firstGroup = Sets.newHashSet(
  289 + validClientRegistrationDto(),
  290 + validClientRegistrationDto(),
  291 + validClientRegistrationDto(),
  292 + validClientRegistrationDto()
  293 + );
  294 + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet(
  295 + OAuth2ClientsDomainParams.builder()
  296 + .domainInfos(Sets.newHashSet(
  297 + DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
  298 + DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
  299 + DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build()
  300 + ))
  301 + .clientRegistrations(firstGroup)
  302 + .build()
  303 + ));
  304 +
  305 + oAuth2Service.saveOAuth2Params(clientsParams);
  306 + OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params();
  307 + Assert.assertNotNull(foundClientsParams);
  308 + Assert.assertEquals(clientsParams, foundClientsParams);
  309 +
  310 + List<OAuth2ClientInfo> firstGroupClientInfos = firstGroup.stream()
  311 + .map(clientRegistrationDto -> new OAuth2ClientInfo(
  312 + clientRegistrationDto.getLoginButtonLabel(), clientRegistrationDto.getLoginButtonIcon(), null))
  313 + .collect(Collectors.toList());
  314 +
  315 + List<OAuth2ClientInfo> firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain");
  316 + Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpClients.size());
  317 + firstGroupClientInfos.forEach(firstGroupClientInfo -> {
  318 + Assert.assertTrue(
  319 + firstDomainHttpClients.stream().anyMatch(clientInfo ->
  320 + clientInfo.getIcon().equals(firstGroupClientInfo.getIcon())
  321 + && clientInfo.getName().equals(firstGroupClientInfo.getName()))
  322 + );
  323 + });
  324 +
  325 + List<OAuth2ClientInfo> firstDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "first-domain");
  326 + Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpsClients.size());
  327 + firstGroupClientInfos.forEach(firstGroupClientInfo -> {
  328 + Assert.assertTrue(
  329 + firstDomainHttpsClients.stream().anyMatch(clientInfo ->
  330 + clientInfo.getIcon().equals(firstGroupClientInfo.getIcon())
  331 + && clientInfo.getName().equals(firstGroupClientInfo.getName()))
  332 + );
  333 + });
  334 + }
  335 +
  336 + @Test
  337 + public void testGetDisabledOAuth2Clients() {
  338 + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet(
  339 + OAuth2ClientsDomainParams.builder()
  340 + .domainInfos(Sets.newHashSet(
  341 + DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
  342 + DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
  343 + DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
  344 + ))
  345 + .clientRegistrations(Sets.newHashSet(
  346 + validClientRegistrationDto(),
  347 + validClientRegistrationDto(),
  348 + validClientRegistrationDto()
  349 + ))
  350 + .build(),
  351 + OAuth2ClientsDomainParams.builder()
  352 + .domainInfos(Sets.newHashSet(
  353 + DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(),
  354 + DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build()
  355 + ))
  356 + .clientRegistrations(Sets.newHashSet(
  357 + validClientRegistrationDto(),
  358 + validClientRegistrationDto()
  359 + ))
  360 + .build()
  361 + ));
  362 +
  363 + oAuth2Service.saveOAuth2Params(clientsParams);
  364 +
  365 + List<OAuth2ClientInfo> secondDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "second-domain");
  366 + Assert.assertEquals(5, secondDomainHttpClients.size());
  367 +
  368 + clientsParams.setEnabled(false);
  369 + oAuth2Service.saveOAuth2Params(clientsParams);
  370 +
  371 + List<OAuth2ClientInfo> secondDomainHttpDisabledClients = oAuth2Service.getOAuth2Clients("http", "second-domain");
  372 + Assert.assertEquals(0, secondDomainHttpDisabledClients.size());
  373 + }
  374 +
  375 + @Test
  376 + public void testFindAllClientRegistrationInfos() {
  377 + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet(
  378 + OAuth2ClientsDomainParams.builder()
  379 + .domainInfos(Sets.newHashSet(
  380 + DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
  381 + DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
  382 + DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
  383 + ))
  384 + .clientRegistrations(Sets.newHashSet(
  385 + validClientRegistrationDto(),
  386 + validClientRegistrationDto(),
  387 + validClientRegistrationDto()
  388 + ))
  389 + .build(),
  390 + OAuth2ClientsDomainParams.builder()
  391 + .domainInfos(Sets.newHashSet(
  392 + DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(),
  393 + DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build()
  394 + ))
  395 + .clientRegistrations(Sets.newHashSet(
  396 + validClientRegistrationDto(),
  397 + validClientRegistrationDto()
  398 + ))
  399 + .build(),
  400 + OAuth2ClientsDomainParams.builder()
  401 + .domainInfos(Sets.newHashSet(
  402 + DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(),
  403 + DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build()
  404 + ))
  405 + .clientRegistrations(Sets.newHashSet(
  406 + validClientRegistrationDto()
  407 + ))
  408 + .build()
  409 + ));
  410 +
  411 + oAuth2Service.saveOAuth2Params(clientsParams);
  412 + List<OAuth2ClientRegistrationInfo> foundClientRegistrationInfos = oAuth2Service.findAllClientRegistrationInfos();
  413 + Assert.assertEquals(6, foundClientRegistrationInfos.size());
  414 + clientsParams.getDomainsParams().stream()
  415 + .flatMap(domainParams -> domainParams.getClientRegistrations().stream())
  416 + .forEach(clientRegistrationDto ->
  417 + Assert.assertTrue(
  418 + foundClientRegistrationInfos.stream()
  419 + .anyMatch(clientRegistrationInfo -> clientRegistrationInfo.getClientId().equals(clientRegistrationDto.getClientId()))
  420 + )
  421 + );
  422 + }
  423 +
  424 + @Test
  425 + public void testFindClientRegistrationById() {
  426 + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet(
  427 + OAuth2ClientsDomainParams.builder()
  428 + .domainInfos(Sets.newHashSet(
  429 + DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
  430 + DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
  431 + DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
  432 + ))
  433 + .clientRegistrations(Sets.newHashSet(
  434 + validClientRegistrationDto(),
  435 + validClientRegistrationDto(),
  436 + validClientRegistrationDto()
  437 + ))
  438 + .build(),
  439 + OAuth2ClientsDomainParams.builder()
  440 + .domainInfos(Sets.newHashSet(
  441 + DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(),
  442 + DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build()
  443 + ))
  444 + .clientRegistrations(Sets.newHashSet(
  445 + validClientRegistrationDto(),
  446 + validClientRegistrationDto()
  447 + ))
  448 + .build(),
  449 + OAuth2ClientsDomainParams.builder()
  450 + .domainInfos(Sets.newHashSet(
  451 + DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(),
  452 + DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build()
  453 + ))
  454 + .clientRegistrations(Sets.newHashSet(
  455 + validClientRegistrationDto()
  456 + ))
  457 + .build()
  458 + ));
  459 +
  460 + oAuth2Service.saveOAuth2Params(clientsParams);
  461 + List<OAuth2ClientRegistrationInfo> clientRegistrationInfos = oAuth2Service.findAllClientRegistrationInfos();
  462 + clientRegistrationInfos.forEach(clientRegistrationInfo -> {
  463 + OAuth2ClientRegistrationInfo foundClientRegistrationInfo = oAuth2Service.findClientRegistrationInfo(clientRegistrationInfo.getUuidId());
  464 + Assert.assertEquals(clientRegistrationInfo, foundClientRegistrationInfo);
  465 + });
  466 + }
  467 +
  468 + private OAuth2ClientsParams createDefaultClientsParams() {
  469 + return new OAuth2ClientsParams(true, Sets.newHashSet(
  470 + OAuth2ClientsDomainParams.builder()
  471 + .domainInfos(Sets.newHashSet(
  472 + DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
  473 + DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
  474 + DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
  475 + ))
  476 + .clientRegistrations(Sets.newHashSet(
  477 + validClientRegistrationDto(),
  478 + validClientRegistrationDto(),
  479 + validClientRegistrationDto(),
  480 + validClientRegistrationDto()
  481 + ))
  482 + .build(),
  483 + OAuth2ClientsDomainParams.builder()
  484 + .domainInfos(Sets.newHashSet(
  485 + DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
  486 + DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build()
  487 + ))
  488 + .clientRegistrations(Sets.newHashSet(
  489 + validClientRegistrationDto(),
  490 + validClientRegistrationDto()
  491 + ))
  492 + .build()
  493 + ));
  494 + }
  495 +
  496 + private ClientRegistrationDto validClientRegistrationDto() {
  497 + return ClientRegistrationDto.builder()
  498 + .clientId(UUID.randomUUID().toString())
  499 + .clientSecret(UUID.randomUUID().toString())
  500 + .authorizationUri(UUID.randomUUID().toString())
  501 + .accessTokenUri(UUID.randomUUID().toString())
  502 + .scope(Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString()))
  503 + .userInfoUri(UUID.randomUUID().toString())
  504 + .userNameAttributeName(UUID.randomUUID().toString())
  505 + .jwkSetUri(UUID.randomUUID().toString())
  506 + .clientAuthenticationMethod(UUID.randomUUID().toString())
  507 + .loginButtonLabel(UUID.randomUUID().toString())
  508 + .loginButtonIcon(UUID.randomUUID().toString())
  509 + .additionalInfo(mapper.createObjectNode().put(UUID.randomUUID().toString(), UUID.randomUUID().toString()))
  510 + .mapperConfig(
  511 + OAuth2MapperConfig.builder()
  512 + .allowUserCreation(true)
  513 + .activateUser(true)
  514 + .type(MapperType.CUSTOM)
  515 + .custom(
  516 + OAuth2CustomMapperConfig.builder()
  517 + .url(UUID.randomUUID().toString())
  518 + .build()
  519 + )
  520 + .build()
  521 + )
  522 + .build();
  523 + }
  524 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.service.sql;
  17 +
  18 +import org.thingsboard.server.dao.service.BaseOAuth2ConfigTemplateServiceTest;
  19 +import org.thingsboard.server.dao.service.DaoSqlTest;
  20 +
  21 +@DaoSqlTest
  22 +public class OAuth2ConfigTemplateServiceSqlTest extends BaseOAuth2ConfigTemplateServiceTest {
  23 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.service.sql;
  17 +
  18 +import org.thingsboard.server.dao.service.BaseOAuth2ServiceTest;
  19 +import org.thingsboard.server.dao.service.DaoSqlTest;
  20 +
  21 +@DaoSqlTest
  22 +public class OAuth2ServiceSqlTest extends BaseOAuth2ServiceTest {
  23 +}
... ...
... ... @@ -24,4 +24,7 @@ DROP TABLE IF EXISTS tenant_profile;
24 24 DROP TABLE IF EXISTS rule_node_state;
25 25 DROP TABLE IF EXISTS rule_node;
26 26 DROP TABLE IF EXISTS rule_chain;
  27 +DROP TABLE IF EXISTS oauth2_client_registration;
  28 +DROP TABLE IF EXISTS oauth2_client_registration_info;
  29 +DROP TABLE IF EXISTS oauth2_client_registration_template;
27 30 DROP FUNCTION IF EXISTS to_uuid;
... ...
... ... @@ -25,3 +25,6 @@ DROP TABLE IF EXISTS rule_node_state;
25 25 DROP TABLE IF EXISTS rule_node;
26 26 DROP TABLE IF EXISTS rule_chain;
27 27 DROP TABLE IF EXISTS tb_schema_settings;
  28 +DROP TABLE IF EXISTS oauth2_client_registration;
  29 +DROP TABLE IF EXISTS oauth2_client_registration_info;
  30 +DROP TABLE IF EXISTS oauth2_client_registration_template;
... ...
... ... @@ -25,3 +25,6 @@ DROP TABLE IF EXISTS entity_view;
25 25 DROP TABLE IF EXISTS device_profile;
26 26 DROP TABLE IF EXISTS tenant_profile;
27 27 DROP TABLE IF EXISTS tb_schema_settings;
  28 +DROP TABLE IF EXISTS oauth2_client_registration;
  29 +DROP TABLE IF EXISTS oauth2_client_registration_info;
  30 +DROP TABLE IF EXISTS oauth2_client_registration_template;
... ...
... ... @@ -18,7 +18,7 @@ import { Injectable, NgZone } from '@angular/core';
18 18 import { JwtHelperService } from '@auth0/angular-jwt';
19 19 import { HttpClient } from '@angular/common/http';
20 20
21   -import { forkJoin, Observable, of, throwError, ReplaySubject } from 'rxjs';
  21 +import { forkJoin, Observable, of, ReplaySubject, throwError } from 'rxjs';
22 22 import { catchError, map, mergeMap, tap } from 'rxjs/operators';
23 23
24 24 import { LoginRequest, LoginResponse, OAuth2Client, PublicLoginRequest } from '@shared/models/login.models';
... ...
... ... @@ -18,7 +18,14 @@ import { Injectable } from '@angular/core';
18 18 import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 19 import { Observable } from 'rxjs';
20 20 import { HttpClient } from '@angular/common/http';
21   -import { AdminSettings, MailServerSettings, SecuritySettings, UpdateMessage } from '@shared/models/settings.models';
  21 +import {
  22 + AdminSettings,
  23 + ClientProviderTemplated,
  24 + MailServerSettings,
  25 + OAuth2Settings,
  26 + SecuritySettings,
  27 + UpdateMessage
  28 +} from '@shared/models/settings.models';
22 29
23 30 @Injectable({
24 31 providedIn: 'root'
... ... @@ -53,6 +60,19 @@ export class AdminService {
53 60 defaultHttpOptionsFromConfig(config));
54 61 }
55 62
  63 + public getOAuth2Settings(config?: RequestConfig): Observable<OAuth2Settings> {
  64 + return this.http.get<OAuth2Settings>(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config));
  65 + }
  66 +
  67 + public getOAuth2Template(config?: RequestConfig): Observable<Array<ClientProviderTemplated>> {
  68 + return this.http.get<Array<ClientProviderTemplated>>(`/api/oauth2/config/template`, defaultHttpOptionsFromConfig(config));
  69 + }
  70 +
  71 + public saveOAuth2Settings(OAuth2Setting: OAuth2Settings, config?: RequestConfig): Observable<OAuth2Settings> {
  72 + return this.http.post<OAuth2Settings>('/api/oauth2/config', OAuth2Setting,
  73 + defaultHttpOptionsFromConfig(config));
  74 + }
  75 +
56 76 public checkUpdates(config?: RequestConfig): Observable<UpdateMessage> {
57 77 return this.http.get<UpdateMessage>(`/api/admin/updates`, defaultHttpOptionsFromConfig(config));
58 78 }
... ...
... ... @@ -108,7 +108,7 @@ export class MenuService {
108 108 name: 'admin.system-settings',
109 109 type: 'toggle',
110 110 path: '/settings',
111   - height: '120px',
  111 + height: '160px',
112 112 icon: 'settings',
113 113 pages: [
114 114 {
... ... @@ -131,6 +131,13 @@ export class MenuService {
131 131 type: 'link',
132 132 path: '/settings/security-settings',
133 133 icon: 'security'
  134 + },
  135 + {
  136 + id: guid(),
  137 + name: 'admin.oauth2.oauth2',
  138 + type: 'link',
  139 + path: '/settings/oauth2',
  140 + icon: 'security'
134 141 }
135 142 ]
136 143 }
... ...
... ... @@ -22,6 +22,7 @@ import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
22 22 import { Authority } from '@shared/models/authority.enum';
23 23 import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-settings.component';
24 24 import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component';
  25 +import { OAuth2SettingsComponent } from '@home/pages/admin/oauth2-settings.component';
25 26
26 27 const routes: Routes = [
27 28 {
... ... @@ -77,6 +78,19 @@ const routes: Routes = [
77 78 icon: 'security'
78 79 }
79 80 }
  81 + },
  82 + {
  83 + path: 'oauth2',
  84 + component: OAuth2SettingsComponent,
  85 + canDeactivate: [ConfirmOnExitGuard],
  86 + data: {
  87 + auth: [Authority.SYS_ADMIN],
  88 + title: 'admin.oauth2.oauth2',
  89 + breadcrumb: {
  90 + label: 'admin.oauth2.oauth2',
  91 + icon: 'security'
  92 + }
  93 + }
80 94 }
81 95 ]
82 96 }
... ...
... ... @@ -23,13 +23,15 @@ import { MailServerComponent } from '@modules/home/pages/admin/mail-server.compo
23 23 import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-settings.component';
24 24 import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component';
25 25 import { HomeComponentsModule } from '@modules/home/components/home-components.module';
  26 +import { OAuth2SettingsComponent } from '@modules/home/pages/admin/oauth2-settings.component';
26 27
27 28 @NgModule({
28 29 declarations:
29 30 [
30 31 GeneralSettingsComponent,
31 32 MailServerComponent,
32   - SecuritySettingsComponent
  33 + SecuritySettingsComponent,
  34 + OAuth2SettingsComponent
33 35 ],
34 36 imports: [
35 37 CommonModule,
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2020 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<div>
  19 + <mat-card class="settings-card">
  20 + <mat-card-title>
  21 + <div fxLayout="row">
  22 + <span class="mat-headline" translate>admin.oauth2.oauth2</span>
  23 + <span fxFlex></span>
  24 + <div tb-help="oauth2Settings"></div>
  25 + </div>
  26 + </mat-card-title>
  27 + <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
  28 + </mat-progress-bar>
  29 + <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
  30 + <mat-card-content style="padding-top: 16px;">
  31 + <form [formGroup]="oauth2SettingsForm" (ngSubmit)="save()">
  32 + <fieldset [disabled]="isLoading$ | async">
  33 + <mat-checkbox formControlName="enabled">
  34 + {{ 'admin.oauth2.enable' | translate }}
  35 + </mat-checkbox>
  36 + <section *ngIf="oauth2SettingsForm.get('enabled').value && !(isLoading$ | async)" style="margin-top: 1em;">
  37 + <ng-container formArrayName="domainsParams">
  38 + <div class="container">
  39 + <mat-accordion multi>
  40 + <ng-container *ngFor="let domain of domainsParams.controls; let i = index; trackBy: trackByParams">
  41 + <mat-expansion-panel [formGroupName]="i">
  42 + <mat-expansion-panel-header>
  43 + <mat-panel-title fxLayoutAlign="start center">
  44 + {{ domainListTittle(domain) }}
  45 + </mat-panel-title>
  46 + <mat-panel-description fxLayoutAlign="end center">
  47 + <button mat-icon-button
  48 + type="button"
  49 + (click)="deleteDomain($event, i)"
  50 + matTooltip="{{ 'action.delete' | translate }}"
  51 + matTooltipPosition="above">
  52 + <mat-icon>delete</mat-icon>
  53 + </button>
  54 + </mat-panel-description>
  55 + </mat-expansion-panel-header>
  56 +
  57 + <ng-template matExpansionPanelContent>
  58 + <ng-container formArrayName="domainInfos">
  59 + <section *ngFor="let domainInfo of clientDomainInfos(domain).controls; let n = index; trackBy: trackByParams"
  60 + class="domains-list">
  61 + <div [formGroupName]="n" fxLayout="row" fxLayoutGap="8px">
  62 + <div fxLayout="row" fxLayout.xs="column" fxLayoutGap="8px">
  63 + <div fxLayout="column" fxFlex.sm="60" fxFlex.gt-sm="50">
  64 + <div fxLayout="row" fxLayout.xs="column" fxLayoutGap="8px">
  65 + <mat-form-field fxFlex="30" fxFlex.xs class="mat-block">
  66 + <mat-label translate>admin.oauth2.protocol</mat-label>
  67 + <mat-select formControlName="scheme">
  68 + <mat-option *ngFor="let protocol of protocols" [value]="protocol">
  69 + {{ domainSchemaTranslations.get(protocol) | translate | uppercase }}
  70 + </mat-option>
  71 + </mat-select>
  72 + </mat-form-field>
  73 + <mat-form-field fxFlex class="mat-block">
  74 + <mat-label translate>admin.domain-name</mat-label>
  75 + <input matInput formControlName="name" required>
  76 + <mat-error *ngIf="domainInfo.get('name').hasError('pattern')">
  77 + {{ 'admin.error-verification-url' | translate }}
  78 + </mat-error>
  79 + </mat-form-field>
  80 + </div>
  81 + <mat-error *ngIf="domainInfo.hasError('unique')">
  82 + {{ 'admin.domain-name-unique' | translate }}
  83 + </mat-error>
  84 + </div>
  85 +
  86 + <div fxFlex fxLayout="column">
  87 + <mat-form-field fxFlex class="mat-block">
  88 + <mat-label translate>admin.oauth2.redirect-uri-template</mat-label>
  89 + <input matInput [value]="redirectURI(domainInfo)" readonly>
  90 + <button mat-icon-button color="primary" matSuffix type="button"
  91 + ngxClipboard cbContent="{{ redirectURI(domainInfo) }}"
  92 + matTooltip="{{ 'admin.oauth2.copy-redirect-uri' | translate }}"
  93 + matTooltipPosition="above">
  94 + <mat-icon class="material-icons" svgIcon="mdi:clipboard-arrow-left"></mat-icon>
  95 + </button>
  96 + </mat-form-field>
  97 +
  98 + <mat-form-field fxFlex *ngIf="domainInfo.get('scheme').value === 'MIXED'" class="mat-block">
  99 + <mat-label></mat-label>
  100 + <input matInput [value]="redirectURIMixed(domainInfo)" readonly>
  101 + <button mat-icon-button color="primary" matSuffix type="button"
  102 + ngxClipboard cbContent="{{ redirectURIMixed(domainInfo) }}"
  103 + matTooltip="{{ 'admin.oauth2.copy-redirect-uri' | translate }}"
  104 + matTooltipPosition="above">
  105 + <mat-icon class="material-icons" svgIcon="mdi:clipboard-arrow-left"></mat-icon>
  106 + </button>
  107 + </mat-form-field>
  108 + </div>
  109 + </div>
  110 +
  111 + <div fxLayout="column" fxLayoutAlign="center start">
  112 + <button type="button" mat-icon-button color="primary"
  113 + (click)="removeDomain($event, domain, n)"
  114 + [disabled]="clientDomainInfos(domain).controls.length < 2"
  115 + matTooltip="{{ 'admin.oauth2.delete-domain' | translate }}"
  116 + matTooltipPosition="above">
  117 + <mat-icon>close</mat-icon>
  118 + </button>
  119 + </div>
  120 + </div>
  121 + </section>
  122 + <div fxLayout="row" fxLayoutAlign="end center" style="margin-bottom: 1.25em">
  123 + <button mat-button mat-raised-button color="primary"
  124 + [disabled]="(isLoading$ | async)"
  125 + (click)="addDomainInfo(domain)"
  126 + type="button">
  127 + {{'admin.oauth2.add-domain' | translate}}
  128 + </button>
  129 + </div>
  130 + </ng-container>
  131 +
  132 + <ng-container formArrayName="clientRegistrations">
  133 + <div class="container">
  134 + <mat-expansion-panel *ngFor="let registration of clientDomainProviders(domain).controls; let j = index; trackBy: trackByParams"
  135 + class="registration-card mat-elevation-z0">
  136 + <mat-expansion-panel-header>
  137 + <mat-panel-title fxLayoutAlign="start center">
  138 + {{ getProviderName(registration) }}
  139 + </mat-panel-title>
  140 + <mat-panel-description fxLayoutAlign="end center">
  141 + <button mat-icon-button
  142 + type="button"
  143 + [disabled]="clientDomainProviders(domain).controls.length < 2"
  144 + (click)="deleteProvider($event, domain, j)"
  145 + matTooltip="{{ 'admin.oauth2.delete-provider' | translate }}"
  146 + matTooltipPosition="above">
  147 + <mat-icon>delete</mat-icon>
  148 + </button>
  149 + </mat-panel-description>
  150 + </mat-expansion-panel-header>
  151 +
  152 + <ng-template matExpansionPanelContent>
  153 + <section [formGroupName]="j">
  154 + <section formGroupName="additionalInfo" fxLayout="row">
  155 + <mat-form-field fxFlex class="mat-block">
  156 + <mat-label translate>admin.oauth2.login-provider</mat-label>
  157 + <mat-select formControlName="providerName">
  158 + <mat-option *ngFor="let provider of templateProvider" [value]="provider">
  159 + {{ provider }}
  160 + </mat-option>
  161 + </mat-select>
  162 + </mat-form-field>
  163 + <div [tb-help]="getHelpLink(registration)" *ngIf="getProviderName(registration) !== 'Custom'"></div>
  164 + </section>
  165 +
  166 + <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
  167 + <mat-form-field fxFlex class="mat-block">
  168 + <mat-label translate>admin.oauth2.client-id</mat-label>
  169 + <input matInput formControlName="clientId" required>
  170 + <mat-error *ngIf="registration.get('clientId').hasError('required')">
  171 + {{ 'admin.oauth2.client-id-required' | translate }}
  172 + </mat-error>
  173 + </mat-form-field>
  174 +
  175 + <mat-form-field fxFlex class="mat-block">
  176 + <mat-label translate>admin.oauth2.client-secret</mat-label>
  177 + <input matInput formControlName="clientSecret" required>
  178 + <mat-error *ngIf="registration.get('clientSecret').hasError('required')">
  179 + {{ 'admin.oauth2.client-secret-required' | translate }}
  180 + </mat-error>
  181 + </mat-form-field>
  182 + </div>
  183 +
  184 + <mat-expansion-panel class="mat-elevation-z0 custom-settings"
  185 + [disabled]="getProviderName(registration) === 'Custom'"
  186 + [expanded]="getProviderName(registration) === 'Custom'">
  187 + <mat-expansion-panel-header [fxHide]="getProviderName(registration) === 'Custom'">
  188 + <mat-panel-description fxLayoutAlign="end center">
  189 + {{ 'admin.oauth2.custom-setting' | translate }}
  190 + </mat-panel-description>
  191 + </mat-expansion-panel-header>
  192 + <ng-template matExpansionPanelContent>
  193 + <mat-tab-group dynamicHeight>
  194 + <mat-tab label="{{ 'admin.oauth2.general' | translate }}">
  195 + <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" style="margin-top: 16px;">
  196 + <mat-form-field fxFlex class="mat-block">
  197 + <mat-label translate>admin.oauth2.access-token-uri</mat-label>
  198 + <input matInput formControlName="accessTokenUri" required>
  199 + <button mat-icon-button matSuffix
  200 + type="button"
  201 + (click)="toggleEditMode(registration, 'accessTokenUri')"
  202 + *ngIf="getProviderName(registration) !== 'Custom'">
  203 + <mat-icon class="material-icons">create</mat-icon>
  204 + </button>
  205 + <mat-error *ngIf="registration.get('accessTokenUri').hasError('required')">
  206 + {{ 'admin.oauth2.access-token-uri-required' | translate }}
  207 + </mat-error>
  208 + <mat-error *ngIf="registration.get('accessTokenUri').hasError('pattern')">
  209 + {{ 'admin.oauth2.uri-pattern-error' | translate }}
  210 + </mat-error>
  211 + </mat-form-field>
  212 +
  213 + <mat-form-field fxFlex class="mat-block">
  214 + <mat-label translate>admin.oauth2.authorization-uri</mat-label>
  215 + <input matInput formControlName="authorizationUri" required>
  216 + <button mat-icon-button matSuffix
  217 + type="button"
  218 + (click)="toggleEditMode(registration, 'authorizationUri')"
  219 + *ngIf="getProviderName(registration) !== 'Custom'">
  220 + <mat-icon class="material-icons">create</mat-icon>
  221 + </button>
  222 + <mat-error *ngIf="registration.get('authorizationUri').hasError('required')">
  223 + {{ 'admin.oauth2.authorization-uri-required' | translate }}
  224 + </mat-error>
  225 + <mat-error *ngIf="registration.get('authorizationUri').hasError('pattern')">
  226 + {{ 'admin.oauth2.uri-pattern-error' | translate }}
  227 + </mat-error>
  228 + </mat-form-field>
  229 + </div>
  230 +
  231 + <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
  232 + <mat-form-field fxFlex class="mat-block" appearance="legacy">
  233 + <mat-label translate>admin.oauth2.jwk-set-uri</mat-label>
  234 + <input matInput formControlName="jwkSetUri">
  235 + <button mat-icon-button matSuffix
  236 + type="button" aria-label="Clear"
  237 + (click)="toggleEditMode(registration, 'jwkSetUri')"
  238 + *ngIf="getProviderName(registration) !== 'Custom'">
  239 + <mat-icon class="material-icons">create</mat-icon>
  240 + </button>
  241 + <mat-error *ngIf="registration.get('jwkSetUri').hasError('pattern')">
  242 + {{ 'admin.oauth2.uri-pattern-error' | translate }}
  243 + </mat-error>
  244 + </mat-form-field>
  245 +
  246 + <mat-form-field fxFlex class="mat-block">
  247 + <mat-label translate>admin.oauth2.user-info-uri</mat-label>
  248 + <input matInput formControlName="userInfoUri" required>
  249 + <button mat-icon-button matSuffix
  250 + type="button"
  251 + (click)="toggleEditMode(registration, 'userInfoUri')"
  252 + *ngIf="getProviderName(registration) !== 'Custom'">
  253 + <mat-icon class="material-icons">create</mat-icon>
  254 + </button>
  255 + <mat-error *ngIf="registration.get('userInfoUri').hasError('required')">
  256 + {{ 'admin.oauth2.user-info-uri-required' | translate }}
  257 + </mat-error>
  258 + <mat-error *ngIf="registration.get('userInfoUri').hasError('pattern')">
  259 + {{ 'admin.oauth2.uri-pattern-error' | translate }}
  260 + </mat-error>
  261 + </mat-form-field>
  262 + </div>
  263 +
  264 + <mat-form-field fxFlex class="mat-block">
  265 + <mat-label translate>admin.oauth2.client-authentication-method</mat-label>
  266 + <mat-select formControlName="clientAuthenticationMethod">
  267 + <mat-option *ngFor="let clientAuthenticationMethod of clientAuthenticationMethods"
  268 + [value]="clientAuthenticationMethod">
  269 + {{ clientAuthenticationMethod | uppercase }}
  270 + </mat-option>
  271 + </mat-select>
  272 + </mat-form-field>
  273 +
  274 + <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" *ngIf="getProviderName(registration) === 'Custom'">
  275 + <mat-form-field fxFlex class="mat-block" floatLabel="always">
  276 + <mat-label translate>admin.oauth2.login-button-label</mat-label>
  277 + <input matInput formControlName="loginButtonLabel"
  278 + placeholder="{{ 'admin.oauth2.login-button-label-1' | translate }}"
  279 + required>
  280 + <mat-error *ngIf="registration.get('loginButtonLabel').hasError('required')">
  281 + {{ 'admin.oauth2.login-button-label-required' | translate }}
  282 + </mat-error>
  283 + </mat-form-field>
  284 +
  285 + <mat-form-field fxFlex class="mat-block">
  286 + <mat-label translate>admin.oauth2.login-button-icon</mat-label>
  287 + <input matInput formControlName="loginButtonIcon">
  288 + </mat-form-field>
  289 + </div>
  290 +
  291 + <section formGroupName="mapperConfig">
  292 + <div fxLayout="column" fxLayoutGap="8px" style="margin-bottom: 8px;">
  293 + <mat-checkbox formControlName="allowUserCreation">
  294 + {{ 'admin.oauth2.allow-user-creation' | translate }}
  295 + </mat-checkbox>
  296 + <mat-checkbox formControlName="activateUser">
  297 + {{ 'admin.oauth2.activate-user' | translate }}
  298 + </mat-checkbox>
  299 + </div>
  300 + </section>
  301 +
  302 + <mat-form-field fxFlex class="mat-block">
  303 + <mat-label translate>admin.oauth2.scope</mat-label>
  304 + <mat-chip-list #scopeList>
  305 + <mat-chip *ngFor="let scope of registration.get('scope').value; let k = index; trackBy: trackByParams"
  306 + removable (removed)="removeScope(k, registration)">
  307 + {{scope}}
  308 + <mat-icon matChipRemove>cancel</mat-icon>
  309 + </mat-chip>
  310 + <input [matChipInputFor]="scopeList"
  311 + [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
  312 + matChipInputAddOnBlur
  313 + (matChipInputTokenEnd)="addScope($event, registration)">
  314 + </mat-chip-list>
  315 + <mat-error *ngIf="registration.get('scope').hasError('required')">
  316 + {{ 'admin.oauth2.scope-required' | translate }}
  317 + </mat-error>
  318 + </mat-form-field>
  319 +
  320 + </mat-tab>
  321 + <mat-tab label="{{ 'admin.oauth2.mapper' | translate }}">
  322 + <mat-form-field class="mat-block" style="margin-top: 16px;">
  323 + <mat-label translate>admin.oauth2.user-name-attribute-name</mat-label>
  324 + <input matInput formControlName="userNameAttributeName" required>
  325 + <mat-error
  326 + *ngIf="registration.get('userNameAttributeName').hasError('required')">
  327 + {{ 'admin.oauth2.user-name-attribute-name-required' | translate }}
  328 + </mat-error>
  329 + </mat-form-field>
  330 +
  331 + <section formGroupName="mapperConfig">
  332 + <mat-form-field fxFlex class="mat-block">
  333 + <mat-label translate>admin.oauth2.type</mat-label>
  334 + <mat-select formControlName="type">
  335 + <mat-option *ngFor="let converterTypeExternalUser of converterTypesExternalUser"
  336 + [value]="converterTypeExternalUser">
  337 + {{ converterTypeExternalUser }}
  338 + </mat-option>
  339 + </mat-select>
  340 + </mat-form-field>
  341 +
  342 + <section formGroupName="basic"
  343 + *ngIf="registration.get('mapperConfig.type').value === 'BASIC'">
  344 + <mat-form-field class="mat-block">
  345 + <mat-label translate>admin.oauth2.email-attribute-key</mat-label>
  346 + <input matInput formControlName="emailAttributeKey" required>
  347 + <mat-error
  348 + *ngIf="registration.get('mapperConfig.basic.emailAttributeKey').hasError('required')">
  349 + {{ 'admin.oauth2.email-attribute-key-required' | translate }}
  350 + </mat-error>
  351 + </mat-form-field>
  352 +
  353 + <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
  354 + <mat-form-field fxFlex class="mat-block">
  355 + <mat-label translate>admin.oauth2.first-name-attribute-key</mat-label>
  356 + <input matInput formControlName="firstNameAttributeKey">
  357 + </mat-form-field>
  358 +
  359 + <mat-form-field fxFlex class="mat-block">
  360 + <mat-label translate>admin.oauth2.last-name-attribute-key</mat-label>
  361 + <input matInput formControlName="lastNameAttributeKey">
  362 + </mat-form-field>
  363 + </div>
  364 +
  365 + <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
  366 + <mat-form-field fxFlex class="mat-block">
  367 + <mat-label translate>admin.oauth2.tenant-name-strategy</mat-label>
  368 + <mat-select formControlName="tenantNameStrategy">
  369 + <mat-option *ngFor="let tenantNameStrategy of tenantNameStrategies"
  370 + [value]="tenantNameStrategy">
  371 + {{ tenantNameStrategy }}
  372 + </mat-option>
  373 + </mat-select>
  374 + </mat-form-field>
  375 +
  376 + <mat-form-field fxFlex class="mat-block" [fxShow]="registration.get('mapperConfig.basic.tenantNameStrategy').value === 'CUSTOM'">
  377 + <mat-label translate>admin.oauth2.tenant-name-pattern</mat-label>
  378 + <input matInput
  379 + formControlName="tenantNamePattern"
  380 + [required]="registration.get('mapperConfig.basic.tenantNameStrategy').value === 'CUSTOM'">
  381 + <mat-error
  382 + *ngIf="registration.get('mapperConfig.basic.tenantNamePattern').hasError('required')">
  383 + {{ 'admin.oauth2.tenant-name-pattern-required' | translate }}
  384 + </mat-error>
  385 + </mat-form-field>
  386 + </div>
  387 +
  388 + <mat-form-field fxFlex class="mat-block">
  389 + <mat-label translate>admin.oauth2.customer-name-pattern</mat-label>
  390 + <input matInput formControlName="customerNamePattern">
  391 + </mat-form-field>
  392 +
  393 + <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
  394 + <mat-form-field fxFlex class="mat-block">
  395 + <mat-label translate>admin.oauth2.default-dashboard-name</mat-label>
  396 + <input matInput formControlName="defaultDashboardName">
  397 + </mat-form-field>
  398 +
  399 + <mat-checkbox fxFlex formControlName="alwaysFullScreen" class="checkbox-row">
  400 + {{ 'admin.oauth2.always-fullscreen' | translate}}
  401 + </mat-checkbox>
  402 + </div>
  403 + </section>
  404 +
  405 + <section formGroupName="custom"
  406 + *ngIf="registration.get('mapperConfig.type').value === 'CUSTOM'">
  407 + <mat-form-field class="mat-block">
  408 + <mat-label translate>admin.oauth2.url</mat-label>
  409 + <input matInput formControlName="url" required>
  410 + <mat-error
  411 + *ngIf="registration.get('mapperConfig.custom.url').hasError('required')">
  412 + {{ 'admin.oauth2.url-required' | translate }}
  413 + </mat-error>
  414 + <mat-error
  415 + *ngIf="registration.get('mapperConfig.custom.url').hasError('pattern')">
  416 + {{ 'admin.oauth2.url-pattern' | translate }}
  417 + </mat-error>
  418 + </mat-form-field>
  419 +
  420 + <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
  421 + <mat-form-field fxFlex class="mat-block">
  422 + <mat-label translate>common.username</mat-label>
  423 + <input matInput formControlName="username" autocomplete="new-username">
  424 + </mat-form-field>
  425 +
  426 + <mat-form-field fxFlex class="mat-block">
  427 + <mat-label translate>common.password</mat-label>
  428 + <input matInput type="password" formControlName="password" autocomplete="new-password">
  429 + </mat-form-field>
  430 + </div>
  431 + </section>
  432 + </section>
  433 + </mat-tab>
  434 + </mat-tab-group>
  435 + </ng-template>
  436 + </mat-expansion-panel>
  437 +
  438 + </section>
  439 + </ng-template>
  440 + </mat-expansion-panel>
  441 + </div>
  442 + </ng-container>
  443 +
  444 + <div fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px">
  445 + <button mat-button mat-raised-button color="primary"
  446 + [disabled]="(isLoading$ | async)"
  447 + (click)="addProvider(domain)"
  448 + type="button">
  449 + {{'admin.oauth2.add-provider' | translate}}
  450 + </button>
  451 + </div>
  452 + </ng-template>
  453 +
  454 + </mat-expansion-panel>
  455 + </ng-container>
  456 + </mat-accordion>
  457 + </div>
  458 + </ng-container>
  459 + </section>
  460 + </fieldset>
  461 + <div fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px">
  462 + <button type="button" mat-raised-button color="primary"
  463 + [disabled]="isLoading$ | async"
  464 + *ngIf="oauth2SettingsForm.get('enabled').value"
  465 + (click)="addDomain()">
  466 + <mat-icon>add</mat-icon>
  467 + <span translate>action.add</span>
  468 + </button>
  469 + <button mat-button mat-raised-button color="primary"
  470 + [disabled]="(isLoading$ | async) || oauth2SettingsForm.invalid || !oauth2SettingsForm.dirty"
  471 + type="submit">
  472 + {{'action.save' | translate}}
  473 + </button>
  474 + </div>
  475 + </form>
  476 + </mat-card-content>
  477 + </mat-card>
  478 +</div>
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +:host{
  17 + .checkbox-row {
  18 + margin-top: 1em;
  19 + }
  20 +
  21 + .registration-card{
  22 + margin-bottom: 0.5em;
  23 + border: 1px solid rgba(0, 0, 0, 0.2);
  24 +
  25 + .custom-settings{
  26 + font-size: 16px;
  27 + .mat-expansion-panel-header{
  28 + padding: 0 2px;
  29 + }
  30 + }
  31 + }
  32 +
  33 + .container{
  34 + margin-bottom: 1em;
  35 +
  36 + .tb-highlight{
  37 + margin: 0;
  38 + }
  39 +
  40 + .tb-hint{
  41 + padding-bottom: 0;
  42 + }
  43 + }
  44 +}
  45 +
  46 +:host ::ng-deep{
  47 + .registration-card{
  48 + .custom-settings{
  49 + .mat-expansion-panel-body{
  50 + padding: 0 2px 1em;
  51 + }
  52 + .mat-tab-label{
  53 + text-transform: none;
  54 + }
  55 + .mat-form-field-suffix .mat-icon-button .mat-icon{
  56 + font-size: 18px;
  57 + }
  58 + }
  59 + }
  60 + .domains-list{
  61 + margin-bottom: 1.5em;
  62 + .mat-form-field-suffix .mat-icon-button .mat-icon{
  63 + font-size: 24px;
  64 + }
  65 + }
  66 +}
... ...
  1 +///
  2 +/// Copyright © 2016-2020 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
  18 +import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
  19 +import {
  20 + ClientAuthenticationMethod,
  21 + ClientProviderTemplated,
  22 + ClientRegistration,
  23 + DomainInfo,
  24 + DomainSchema,
  25 + domainSchemaTranslations,
  26 + DomainsParam,
  27 + MapperConfig,
  28 + MapperConfigBasic,
  29 + MapperConfigCustom,
  30 + MapperConfigType,
  31 + OAuth2Settings,
  32 + TenantNameStrategy
  33 +} from '@shared/models/settings.models';
  34 +import { Store } from '@ngrx/store';
  35 +import { AppState } from '@core/core.state';
  36 +import { AdminService } from '@core/http/admin.service';
  37 +import { PageComponent } from '@shared/components/page.component';
  38 +import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard';
  39 +import { COMMA, ENTER } from '@angular/cdk/keycodes';
  40 +import { MatChipInputEvent } from '@angular/material/chips';
  41 +import { WINDOW } from '@core/services/window.service';
  42 +import { forkJoin, Subscription } from 'rxjs';
  43 +import { DialogService } from '@core/services/dialog.service';
  44 +import { TranslateService } from '@ngx-translate/core';
  45 +import { isDefined } from '@core/utils';
  46 +
  47 +@Component({
  48 + selector: 'tb-oauth2-settings',
  49 + templateUrl: './oauth2-settings.component.html',
  50 + styleUrls: ['./oauth2-settings.component.scss', './settings-card.scss']
  51 +})
  52 +export class OAuth2SettingsComponent extends PageComponent implements OnInit, HasConfirmForm, OnDestroy {
  53 +
  54 + private URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.,?+=&%@\-/]*)?$/;
  55 + private subscriptions: Subscription[] = [];
  56 + private templates = new Map<string, ClientProviderTemplated>();
  57 + private defaultProvider = {
  58 + additionalInfo: {
  59 + providerName: 'Custom'
  60 + },
  61 + clientAuthenticationMethod: ClientAuthenticationMethod.POST,
  62 + userNameAttributeName: 'email',
  63 + mapperConfig: {
  64 + allowUserCreation: true,
  65 + activateUser: false,
  66 + type: MapperConfigType.BASIC,
  67 + basic: {
  68 + emailAttributeKey: 'email',
  69 + tenantNameStrategy: TenantNameStrategy.DOMAIN,
  70 + alwaysFullScreen: false
  71 + }
  72 + }
  73 + };
  74 +
  75 + readonly separatorKeysCodes: number[] = [ENTER, COMMA];
  76 +
  77 + oauth2SettingsForm: FormGroup;
  78 + oauth2Settings: OAuth2Settings;
  79 +
  80 + clientAuthenticationMethods = Object.keys(ClientAuthenticationMethod);
  81 + converterTypesExternalUser = Object.keys(MapperConfigType);
  82 + tenantNameStrategies = Object.keys(TenantNameStrategy);
  83 + protocols = Object.keys(DomainSchema);
  84 + domainSchemaTranslations = domainSchemaTranslations;
  85 +
  86 + templateProvider = ['Custom'];
  87 +
  88 + constructor(protected store: Store<AppState>,
  89 + private adminService: AdminService,
  90 + private fb: FormBuilder,
  91 + private dialogService: DialogService,
  92 + private translate: TranslateService,
  93 + @Inject(WINDOW) private window: Window) {
  94 + super(store);
  95 + }
  96 +
  97 + ngOnInit(): void {
  98 + this.buildOAuth2SettingsForm();
  99 + forkJoin([
  100 + this.adminService.getOAuth2Template(),
  101 + this.adminService.getOAuth2Settings()
  102 + ]).subscribe(
  103 + ([templates, oauth2Settings]) => {
  104 + this.initTemplates(templates);
  105 + this.oauth2Settings = oauth2Settings;
  106 + this.initOAuth2Settings(this.oauth2Settings);
  107 + }
  108 + );
  109 + }
  110 +
  111 + ngOnDestroy() {
  112 + super.ngOnDestroy();
  113 + this.subscriptions.forEach((subscription) => {
  114 + subscription.unsubscribe();
  115 + });
  116 + }
  117 +
  118 + private initTemplates(templates: ClientProviderTemplated[]): void {
  119 + templates.map(provider => this.templates.set(provider.name, provider));
  120 + this.templateProvider.push(...Array.from(this.templates.keys()));
  121 + this.templateProvider.sort();
  122 + }
  123 +
  124 + get domainsParams(): FormArray {
  125 + return this.oauth2SettingsForm.get('domainsParams') as FormArray;
  126 + }
  127 +
  128 + private formBasicGroup(mapperConfigBasic?: MapperConfigBasic): FormGroup {
  129 + let tenantNamePattern;
  130 + if (mapperConfigBasic?.tenantNamePattern) {
  131 + tenantNamePattern = mapperConfigBasic.tenantNamePattern;
  132 + } else {
  133 + tenantNamePattern = {value: null, disabled: true};
  134 + }
  135 + const basicGroup = this.fb.group({
  136 + emailAttributeKey: [mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email', Validators.required],
  137 + firstNameAttributeKey: [mapperConfigBasic?.firstNameAttributeKey ? mapperConfigBasic.firstNameAttributeKey : ''],
  138 + lastNameAttributeKey: [mapperConfigBasic?.lastNameAttributeKey ? mapperConfigBasic.lastNameAttributeKey : ''],
  139 + tenantNameStrategy: [mapperConfigBasic?.tenantNameStrategy ? mapperConfigBasic.tenantNameStrategy : TenantNameStrategy.DOMAIN],
  140 + tenantNamePattern: [tenantNamePattern, Validators.required],
  141 + customerNamePattern: [mapperConfigBasic?.customerNamePattern ? mapperConfigBasic.customerNamePattern : null],
  142 + defaultDashboardName: [mapperConfigBasic?.defaultDashboardName ? mapperConfigBasic.defaultDashboardName : null],
  143 + alwaysFullScreen: [mapperConfigBasic?.alwaysFullScreen ? mapperConfigBasic.alwaysFullScreen : false]
  144 + });
  145 +
  146 + this.subscriptions.push(basicGroup.get('tenantNameStrategy').valueChanges.subscribe((domain) => {
  147 + if (domain === 'CUSTOM') {
  148 + basicGroup.get('tenantNamePattern').enable();
  149 + } else {
  150 + basicGroup.get('tenantNamePattern').disable();
  151 + }
  152 + }));
  153 +
  154 + return basicGroup;
  155 + }
  156 +
  157 + private formCustomGroup(mapperConfigCustom?: MapperConfigCustom): FormGroup {
  158 + return this.fb.group({
  159 + url: [mapperConfigCustom?.url ? mapperConfigCustom.url : null, [Validators.required, Validators.pattern(this.URL_REGEXP)]],
  160 + username: [mapperConfigCustom?.username ? mapperConfigCustom.username : null],
  161 + password: [mapperConfigCustom?.password ? mapperConfigCustom.password : null]
  162 + });
  163 + }
  164 +
  165 + private buildOAuth2SettingsForm(): void {
  166 + this.oauth2SettingsForm = this.fb.group({
  167 + domainsParams: this.fb.array([]),
  168 + enabled: [false]
  169 + });
  170 + }
  171 +
  172 + private initOAuth2Settings(oauth2Settings: OAuth2Settings): void {
  173 + if (oauth2Settings) {
  174 + this.oauth2SettingsForm.patchValue({enabled: oauth2Settings.enabled}, {emitEvent: false});
  175 + oauth2Settings.domainsParams.forEach((domain) => {
  176 + this.domainsParams.push(this.buildDomainsForm(domain));
  177 + });
  178 + }
  179 + }
  180 +
  181 + private uniqueDomainValidator(control: FormGroup): { [key: string]: boolean } | null {
  182 + if (control.parent?.value) {
  183 + const domain = control.value.name;
  184 + const listProtocols = control.parent.getRawValue()
  185 + .filter((domainInfo) => domainInfo.name === domain)
  186 + .map((domainInfo) => domainInfo.scheme);
  187 + if (listProtocols.length > 1 && listProtocols.indexOf(DomainSchema.MIXED) > -1 ||
  188 + new Set(listProtocols).size !== listProtocols.length) {
  189 + return {unique: true};
  190 + }
  191 + }
  192 + return null;
  193 + }
  194 +
  195 + public domainListTittle(control: AbstractControl): string {
  196 + const domainInfos = control.get('domainInfos').value as DomainInfo[];
  197 + if (domainInfos.length) {
  198 + const domainList = new Set<string>();
  199 + domainInfos.forEach((domain) => {
  200 + domainList.add(domain.name);
  201 + });
  202 + return Array.from(domainList).join(', ');
  203 + }
  204 + return this.translate.instant('admin.oauth2.new-domain');
  205 + }
  206 +
  207 + private buildDomainsForm(domainParams?: DomainsParam): FormGroup {
  208 + const formDomain = this.fb.group({
  209 + domainInfos: this.fb.array([], Validators.required),
  210 + clientRegistrations: this.fb.array([], Validators.required)
  211 + });
  212 +
  213 + if (domainParams) {
  214 + domainParams.domainInfos.forEach((domain) => {
  215 + this.clientDomainInfos(formDomain).push(this.buildDomainForm(domain));
  216 + });
  217 + domainParams.clientRegistrations.forEach((registration) => {
  218 + this.clientDomainProviders(formDomain).push(this.buildProviderForm(registration));
  219 + });
  220 + } else {
  221 + this.clientDomainProviders(formDomain).push(this.buildProviderForm());
  222 + this.clientDomainInfos(formDomain).push(this.buildDomainForm());
  223 + }
  224 +
  225 + return formDomain;
  226 + }
  227 +
  228 + private buildDomainForm(domainInfo?: DomainInfo): FormGroup {
  229 + const domain = this.fb.group({
  230 + name: [domainInfo ? domainInfo.name : this.window.location.hostname, [
  231 + Validators.required,
  232 + Validators.pattern('((?![:/]).)*$')]],
  233 + scheme: [domainInfo?.scheme ? domainInfo.scheme : DomainSchema.HTTPS, Validators.required]
  234 + }, {validators: this.uniqueDomainValidator});
  235 + return domain;
  236 + }
  237 +
  238 + private buildProviderForm(registrationData?: ClientRegistration): FormGroup {
  239 + let additionalInfo = null;
  240 + if (registrationData?.additionalInfo) {
  241 + additionalInfo = JSON.parse(registrationData.additionalInfo);
  242 + if (this.templateProvider.indexOf(additionalInfo.providerName) === -1) {
  243 + additionalInfo.providerName = 'Custom';
  244 + }
  245 + }
  246 + let defaultProviderName = 'Custom';
  247 + if (this.templateProvider.indexOf('Google')) {
  248 + defaultProviderName = 'Google';
  249 + }
  250 +
  251 + const clientRegistration = this.fb.group({
  252 + id: this.fb.group({
  253 + id: [registrationData?.id?.id ? registrationData.id.id : null],
  254 + entityType: [registrationData?.id?.entityType ? registrationData.id.entityType : null]
  255 + }),
  256 + additionalInfo: this.fb.group({
  257 + providerName: [additionalInfo?.providerName ? additionalInfo?.providerName : defaultProviderName, Validators.required]
  258 + }),
  259 + loginButtonLabel: [registrationData?.loginButtonLabel ? registrationData.loginButtonLabel : null, Validators.required],
  260 + loginButtonIcon: [registrationData?.loginButtonIcon ? registrationData.loginButtonIcon : null],
  261 + clientId: [registrationData?.clientId ? registrationData.clientId : '', Validators.required],
  262 + clientSecret: [registrationData?.clientSecret ? registrationData.clientSecret : '', Validators.required],
  263 + accessTokenUri: [registrationData?.accessTokenUri ? registrationData.accessTokenUri : '',
  264 + [Validators.required,
  265 + Validators.pattern(this.URL_REGEXP)]],
  266 + authorizationUri: [registrationData?.authorizationUri ? registrationData.authorizationUri : '',
  267 + [Validators.required,
  268 + Validators.pattern(this.URL_REGEXP)]],
  269 + scope: this.fb.array(registrationData?.scope ? registrationData.scope : [], Validators.required),
  270 + jwkSetUri: [registrationData?.jwkSetUri ? registrationData.jwkSetUri : '', Validators.pattern(this.URL_REGEXP)],
  271 + userInfoUri: [registrationData?.userInfoUri ? registrationData.userInfoUri : '',
  272 + [Validators.required,
  273 + Validators.pattern(this.URL_REGEXP)]],
  274 + clientAuthenticationMethod: [
  275 + registrationData?.clientAuthenticationMethod ? registrationData.clientAuthenticationMethod : ClientAuthenticationMethod.POST,
  276 + Validators.required],
  277 + userNameAttributeName: [
  278 + registrationData?.userNameAttributeName ? registrationData.userNameAttributeName : 'email', Validators.required],
  279 + mapperConfig: this.fb.group({
  280 + allowUserCreation: [registrationData?.mapperConfig?.allowUserCreation ? registrationData.mapperConfig.allowUserCreation : true],
  281 + activateUser: [registrationData?.mapperConfig?.activateUser ? registrationData.mapperConfig.activateUser : false],
  282 + type: [registrationData?.mapperConfig?.type ? registrationData.mapperConfig.type : MapperConfigType.BASIC, Validators.required]
  283 + }
  284 + )
  285 + });
  286 +
  287 + if (registrationData) {
  288 + this.changeMapperConfigType(clientRegistration, registrationData.mapperConfig.type, registrationData.mapperConfig);
  289 + } else {
  290 + this.changeMapperConfigType(clientRegistration, MapperConfigType.BASIC);
  291 + this.setProviderDefaultValue(defaultProviderName, clientRegistration);
  292 + }
  293 +
  294 + this.subscriptions.push(clientRegistration.get('mapperConfig.type').valueChanges.subscribe((value) => {
  295 + this.changeMapperConfigType(clientRegistration, value);
  296 + }));
  297 +
  298 + this.subscriptions.push(clientRegistration.get('additionalInfo.providerName').valueChanges.subscribe((provider) => {
  299 + (clientRegistration.get('scope') as FormArray).clear();
  300 + this.setProviderDefaultValue(provider, clientRegistration);
  301 + }));
  302 +
  303 + return clientRegistration;
  304 + }
  305 +
  306 + private setProviderDefaultValue(provider: string, clientRegistration: FormGroup) {
  307 + if (provider === 'Custom') {
  308 + const defaultSettings = {...this.defaultProvider, ...{id: clientRegistration.get('id').value}};
  309 + clientRegistration.reset(defaultSettings, {emitEvent: false});
  310 + clientRegistration.get('accessTokenUri').enable();
  311 + clientRegistration.get('authorizationUri').enable();
  312 + clientRegistration.get('jwkSetUri').enable();
  313 + clientRegistration.get('userInfoUri').enable();
  314 + } else {
  315 + const template = this.templates.get(provider);
  316 + delete template.id;
  317 + delete template.additionalInfo;
  318 + template.clientId = '';
  319 + template.clientSecret = '';
  320 + template.scope.forEach(() => {
  321 + (clientRegistration.get('scope') as FormArray).push(this.fb.control(''));
  322 + });
  323 + clientRegistration.get('accessTokenUri').disable();
  324 + clientRegistration.get('authorizationUri').disable();
  325 + clientRegistration.get('jwkSetUri').disable();
  326 + clientRegistration.get('userInfoUri').disable();
  327 + clientRegistration.patchValue(template, {emitEvent: false});
  328 + }
  329 + }
  330 +
  331 + private changeMapperConfigType(control: AbstractControl, type: MapperConfigType, predefinedValue?: MapperConfig) {
  332 + const mapperConfig = control.get('mapperConfig') as FormGroup;
  333 + if (type === MapperConfigType.BASIC) {
  334 + mapperConfig.removeControl('custom');
  335 + mapperConfig.addControl('basic', this.formBasicGroup(predefinedValue?.basic));
  336 + } else {
  337 + mapperConfig.removeControl('basic');
  338 + mapperConfig.addControl('custom', this.formCustomGroup(predefinedValue?.custom));
  339 + }
  340 + }
  341 +
  342 + save(): void {
  343 + const setting = this.prepareFormValue(this.oauth2SettingsForm.getRawValue());
  344 + this.adminService.saveOAuth2Settings(setting).subscribe(
  345 + (oauth2Settings) => {
  346 + this.oauth2Settings = oauth2Settings;
  347 + this.oauth2SettingsForm.markAsPristine();
  348 + this.oauth2SettingsForm.markAsUntouched();
  349 + }
  350 + );
  351 + }
  352 +
  353 + private prepareFormValue(formValue: OAuth2Settings): OAuth2Settings{
  354 + formValue.domainsParams.forEach((setting, index) => {
  355 + setting.clientRegistrations.forEach((registration) => {
  356 + registration.additionalInfo = JSON.stringify(registration.additionalInfo);
  357 + if (registration.id.id === null) {
  358 + delete registration.id;
  359 + }
  360 + });
  361 + });
  362 + return formValue;
  363 + }
  364 +
  365 + confirmForm(): FormGroup {
  366 + return this.oauth2SettingsForm;
  367 + }
  368 +
  369 + addScope(event: MatChipInputEvent, control: AbstractControl): void {
  370 + const input = event.input;
  371 + const value = event.value;
  372 + const controller = control.get('scope') as FormArray;
  373 + if ((value.trim() !== '')) {
  374 + controller.push(this.fb.control(value.trim()));
  375 + controller.markAsDirty();
  376 + }
  377 +
  378 + if (input) {
  379 + input.value = '';
  380 + }
  381 + }
  382 +
  383 + removeScope(i: number, control: AbstractControl): void {
  384 + const controller = control.get('scope') as FormArray;
  385 + controller.removeAt(i);
  386 + controller.markAsTouched();
  387 + controller.markAsDirty();
  388 + }
  389 +
  390 + addDomain(): void {
  391 + this.domainsParams.push(this.buildDomainsForm());
  392 + }
  393 +
  394 + deleteDomain($event: Event, index: number): void {
  395 + if ($event) {
  396 + $event.stopPropagation();
  397 + $event.preventDefault();
  398 + }
  399 +
  400 + const domainName = this.domainListTittle(this.domainsParams.at(index));
  401 + this.dialogService.confirm(
  402 + this.translate.instant('admin.oauth2.delete-domain-title', {domainName: domainName || ''}),
  403 + this.translate.instant('admin.oauth2.delete-domain-text'), null,
  404 + this.translate.instant('action.delete')
  405 + ).subscribe((data) => {
  406 + if (data) {
  407 + this.domainsParams.removeAt(index);
  408 + this.domainsParams.markAsTouched();
  409 + this.domainsParams.markAsDirty();
  410 + }
  411 + });
  412 + }
  413 +
  414 + clientDomainProviders(control: AbstractControl): FormArray {
  415 + return control.get('clientRegistrations') as FormArray;
  416 + }
  417 +
  418 + clientDomainInfos(control: AbstractControl): FormArray {
  419 + return control.get('domainInfos') as FormArray;
  420 + }
  421 +
  422 + addProvider(control: AbstractControl): void {
  423 + this.clientDomainProviders(control).push(this.buildProviderForm());
  424 + }
  425 +
  426 + deleteProvider($event: Event, control: AbstractControl, index: number): void {
  427 + if ($event) {
  428 + $event.stopPropagation();
  429 + $event.preventDefault();
  430 + }
  431 +
  432 + const providerName = this.clientDomainProviders(control).at(index).get('additionalInfo.providerName').value;
  433 + this.dialogService.confirm(
  434 + this.translate.instant('admin.oauth2.delete-registration-title', {name: providerName || ''}),
  435 + this.translate.instant('admin.oauth2.delete-registration-text'), null,
  436 + this.translate.instant('action.delete')
  437 + ).subscribe((data) => {
  438 + if (data) {
  439 + this.clientDomainProviders(control).removeAt(index);
  440 + this.clientDomainProviders(control).markAsTouched();
  441 + this.clientDomainProviders(control).markAsDirty();
  442 + }
  443 + });
  444 + }
  445 +
  446 + toggleEditMode(control: AbstractControl, path: string) {
  447 + control.get(path).disabled ? control.get(path).enable() : control.get(path).disable();
  448 + }
  449 +
  450 + getProviderName(controller: AbstractControl): string {
  451 + return controller.get('additionalInfo.providerName').value;
  452 + }
  453 +
  454 + getHelpLink(controller: AbstractControl): string {
  455 + const provider = controller.get('additionalInfo.providerName').value;
  456 + if (provider === null || provider === 'Custom') {
  457 + return '';
  458 + }
  459 + return this.templates.get(provider).helpLink;
  460 + }
  461 +
  462 + addDomainInfo(control: AbstractControl): void {
  463 + this.clientDomainInfos(control).push(this.buildDomainForm({
  464 + name: '',
  465 + scheme: DomainSchema.HTTPS
  466 + }));
  467 + }
  468 +
  469 + removeDomain($event: Event, control: AbstractControl, index: number): void {
  470 + if ($event) {
  471 + $event.stopPropagation();
  472 + $event.preventDefault();
  473 + }
  474 + this.clientDomainInfos(control).removeAt(index);
  475 + this.clientDomainInfos(control).markAsTouched();
  476 + this.clientDomainInfos(control).markAsDirty();
  477 + }
  478 +
  479 + redirectURI(control: AbstractControl, schema?: DomainSchema): string {
  480 + const domainInfo = control.value as DomainInfo;
  481 + if (domainInfo.name !== '') {
  482 + let protocol;
  483 + if (isDefined(schema)) {
  484 + protocol = schema.toLowerCase();
  485 + } else {
  486 + protocol = domainInfo.scheme === DomainSchema.MIXED ? DomainSchema.HTTPS.toLowerCase() : domainInfo.scheme.toLowerCase();
  487 + }
  488 + return `${protocol}://${domainInfo.name}/login/oauth2/code/`;
  489 + }
  490 + return '';
  491 + }
  492 +
  493 + redirectURIMixed(control: AbstractControl): string {
  494 + return this.redirectURI(control, DomainSchema.HTTP);
  495 + }
  496 +
  497 + trackByParams(index: number): number {
  498 + return index;
  499 + }
  500 +}
... ...
... ... @@ -60,6 +60,7 @@ export const HelpLinks = {
60 60 linksMap: {
61 61 outgoingMailSettings: helpBaseUrl + '/docs/user-guide/ui/mail-settings',
62 62 securitySettings: helpBaseUrl + '/docs/user-guide/ui/security-settings',
  63 + oauth2Settings: helpBaseUrl + '/docs/user-guide/oauth-2-support/',
63 64 ruleEngine: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/overview/',
64 65 ruleNodeCheckRelation: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#check-relation-filter-node',
65 66 ruleNodeCheckExistenceFields: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#check-existence-fields-node',
... ... @@ -123,7 +124,7 @@ export const HelpLinks = {
123 124 widgetsConfigLatest: helpBaseUrl + '/docs/user-guide/ui/dashboards#latest',
124 125 widgetsConfigRpc: helpBaseUrl + '/docs/user-guide/ui/dashboards#rpc',
125 126 widgetsConfigAlarm: helpBaseUrl + '/docs/user-guide/ui/dashboards#alarm',
126   - widgetsConfigStatic: helpBaseUrl + '/docs/user-guide/ui/dashboards#static'
  127 + widgetsConfigStatic: helpBaseUrl + '/docs/user-guide/ui/dashboards#static',
127 128 }
128 129 };
129 130
... ...
... ... @@ -14,6 +14,9 @@
14 14 /// limitations under the License.
15 15 ///
16 16
  17 +import { EntityId } from '@shared/models/id/entity-id';
  18 +import { TenantId } from '@shared/models/id/tenant-id';
  19 +
17 20 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])$/;
18 21
19 22 export interface AdminSettings<T> {
... ... @@ -60,3 +63,99 @@ export interface UpdateMessage {
60 63 message: string;
61 64 updateAvailable: boolean;
62 65 }
  66 +
  67 +export interface OAuth2Settings {
  68 + enabled: boolean;
  69 + domainsParams: DomainsParam[];
  70 +}
  71 +
  72 +export interface DomainsParam {
  73 + clientRegistrations: ClientRegistration[];
  74 + domainInfos: DomainInfo[];
  75 +}
  76 +
  77 +export interface DomainInfo {
  78 + name: string;
  79 + scheme: DomainSchema;
  80 +}
  81 +
  82 +export enum DomainSchema{
  83 + HTTP = 'HTTP',
  84 + HTTPS = 'HTTPS',
  85 + MIXED = 'MIXED'
  86 +}
  87 +
  88 +export const domainSchemaTranslations = new Map<DomainSchema, string>(
  89 + [
  90 + [DomainSchema.HTTP, 'admin.oauth2.domain-schema-http'],
  91 + [DomainSchema.HTTPS, 'admin.oauth2.domain-schema-https'],
  92 + [DomainSchema.MIXED, 'admin.oauth2.domain-schema-mixed']
  93 + ]
  94 +);
  95 +
  96 +export enum MapperConfigType{
  97 + BASIC = 'BASIC',
  98 + CUSTOM = 'CUSTOM'
  99 +}
  100 +
  101 +export enum TenantNameStrategy{
  102 + DOMAIN = 'DOMAIN',
  103 + EMAIL = 'EMAIL',
  104 + CUSTOM = 'CUSTOM'
  105 +}
  106 +
  107 +export interface ClientProviderTemplated extends ClientRegistration{
  108 + comment: string;
  109 + createdTime: number;
  110 + helpLink: string;
  111 + name: string;
  112 + providerId: string;
  113 + tenantId: TenantId;
  114 +}
  115 +
  116 +export interface ClientRegistration {
  117 + loginButtonLabel: string;
  118 + loginButtonIcon: string;
  119 + clientId: string;
  120 + clientSecret: string;
  121 + accessTokenUri: string;
  122 + authorizationUri: string;
  123 + scope: string[];
  124 + jwkSetUri?: string;
  125 + userInfoUri: string;
  126 + clientAuthenticationMethod: ClientAuthenticationMethod;
  127 + userNameAttributeName: string;
  128 + mapperConfig: MapperConfig;
  129 + id?: EntityId;
  130 + additionalInfo: string;
  131 +}
  132 +
  133 +export enum ClientAuthenticationMethod {
  134 + BASIC = 'BASIC',
  135 + POST = 'POST'
  136 +}
  137 +
  138 +export interface MapperConfig {
  139 + allowUserCreation: boolean;
  140 + activateUser: boolean;
  141 + type: MapperConfigType;
  142 + basic?: MapperConfigBasic;
  143 + custom?: MapperConfigCustom;
  144 +}
  145 +
  146 +export interface MapperConfigBasic {
  147 + emailAttributeKey: string;
  148 + firstNameAttributeKey?: string;
  149 + lastNameAttributeKey?: string;
  150 + tenantNameStrategy: TenantNameStrategy;
  151 + tenantNamePattern?: string;
  152 + customerNamePattern?: string;
  153 + defaultDashboardName?: string;
  154 + alwaysFullScreen?: boolean;
  155 +}
  156 +
  157 +export interface MapperConfigCustom {
  158 + url: string;
  159 + username?: string;
  160 + password?: string;
  161 +}
... ...
... ... @@ -120,8 +120,74 @@
120 120 "general-policy": "General policy",
121 121 "max-failed-login-attempts": "Maximum number of failed login attempts, before account is locked",
122 122 "minimum-max-failed-login-attempts-range": "Maximum number of failed login attempts can't be negative",
123   - "user-lockout-notification-email": "In case user account lockout, send notification to email"
124   - },
  123 + "user-lockout-notification-email": "In case user account lockout, send notification to email",
  124 + "domain-name": "Domain name",
  125 + "domain-name-unique": "Domain name and protocol need to unique.",
  126 + "error-verification-url": "A domain name shouldn't contain symbols '/' and ':'. Example: thingsboard.io",
  127 + "oauth2": {
  128 + "access-token-uri": "Access token URI",
  129 + "access-token-uri-required": "Access token URI is required.",
  130 + "activate-user": "Activate user",
  131 + "add-domain": "Add domain",
  132 + "delete-domain": "Delete domain",
  133 + "add-provider": "Add provider",
  134 + "delete-provider": "Delete provider",
  135 + "allow-user-creation": "Allow user creation",
  136 + "always-fullscreen": "Always fullscreen",
  137 + "authorization-uri": "Authorization URI",
  138 + "authorization-uri-required": "Authorization URI is required.",
  139 + "client-authentication-method": "Client authentication method",
  140 + "client-id": "Client ID",
  141 + "client-id-required": "Client ID is required.",
  142 + "client-secret": "Client secret",
  143 + "client-secret-required": "Client secret is required.",
  144 + "custom-setting": "Custom settings",
  145 + "customer-name-pattern": "Customer name pattern",
  146 + "default-dashboard-name": "Default dashboard name",
  147 + "delete-domain-text": "Be careful, after the confirmation a domain and all provider data will be unavailable.",
  148 + "delete-domain-title": "Are you sure you want to delete settings the domain '{{domainName}}'?",
  149 + "delete-registration-text": "Be careful, after the confirmation a provider data will be unavailable.",
  150 + "delete-registration-title": "Are you sure you want to delete the provider '{{name}}'?",
  151 + "email-attribute-key": "Email attribute key",
  152 + "email-attribute-key-required": "Email attribute key is required.",
  153 + "first-name-attribute-key": "First name attribute key",
  154 + "general": "General",
  155 + "jwk-set-uri": "JSON Web Key URI",
  156 + "last-name-attribute-key": "Last name attribute key",
  157 + "login-button-icon": "Login button icon",
  158 + "login-button-label": "Provider label",
  159 + "login-button-label-1": "Login with $(Provider label)",
  160 + "login-button-label-required": "Label is required.",
  161 + "login-provider": "Login provider",
  162 + "mapper": "Mapper",
  163 + "new-domain": "New domain",
  164 + "oauth2": "OAuth2",
  165 + "redirect-uri-template": "Redirect URI template",
  166 + "copy-redirect-uri": "Copy redirect URI",
  167 + "registration-id": "Registration ID",
  168 + "registration-id-required": "Registration ID is required.",
  169 + "registration-id-unique": "Registration ID need to unique for the system.",
  170 + "scope": "Scope",
  171 + "scope-required": "Scope is required.",
  172 + "tenant-name-pattern": "Tenant name pattern",
  173 + "tenant-name-pattern-required": "Tenant name pattern is required.",
  174 + "tenant-name-strategy": "Tenant name strategy",
  175 + "type": "Mapper type",
  176 + "uri-pattern-error": "Invalid URI format.",
  177 + "url": "URL",
  178 + "url-pattern": "Invalid URL format.",
  179 + "url-required": "URL is required.",
  180 + "user-info-uri": "User info URI",
  181 + "user-info-uri-required": "User info URI is required.",
  182 + "user-name-attribute-name": "User name attribute key",
  183 + "user-name-attribute-name-required": "User name attribute key is required",
  184 + "protocol": "Protocol",
  185 + "domain-schema-http": "HTTP",
  186 + "domain-schema-https": "HTTPS",
  187 + "domain-schema-mixed": "HTTP+HTTPS",
  188 + "enable": "Enable OAuth2 settings"
  189 + }
  190 + },
125 191 "alarm": {
126 192 "alarm": "Alarm",
127 193 "alarms": "Alarms",
... ...