From c7e236499ecbec5eeb4bac1d2b21f3adaadb334c Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 1 Oct 2020 17:22:17 +0300 Subject: [PATCH] Added support for multiple domains in OAuth2 config --- .../main/data/upgrade/3.1.1/schema_update.sql | 18 +- ...tomOAuth2AuthorizationRequestResolver.java | 257 ++++++++++ .../ThingsboardSecurityConfiguration.java | 6 + .../server/controller/BaseController.java | 2 +- .../server/controller/OAuth2Controller.java | 61 +-- .../Oauth2AuthenticationSuccessHandler.java | 4 +- .../service/security/permission/Resource.java | 2 +- .../permission/SysAdminPermissions.java | 2 +- .../thingsboard/server/utils/WebUtils.java | 70 +++ .../server/dao/oauth2/OAuth2Service.java | 15 +- .../server/common/data/EntityType.java | 2 +- .../common/data/id/EntityIdFactory.java | 4 +- .../id/OAuth2ClientRegistrationInfoId.java | 39 ++ .../data/oauth2/ClientRegistrationDto.java | 6 +- ...lientRegistration.java => DomainInfo.java} | 9 +- .../ExtendedOAuth2ClientRegistrationInfo.java | 39 ++ .../data/oauth2/OAuth2ClientRegistration.java | 56 +- .../oauth2/OAuth2ClientRegistrationInfo.java | 76 +++ .../oauth2/OAuth2ClientsDomainParams.java | 6 +- .../data/oauth2/OAuth2ClientsParams.java | 8 +- .../server/common/data/oauth2/SchemeType.java | 20 + .../server/dao/model/ModelConstants.java | 6 +- ...actOAuth2ClientRegistrationInfoEntity.java | 231 +++++++++ ...dedOAuth2ClientRegistrationInfoEntity.java | 48 ++ .../sql/OAuth2ClientRegistrationEntity.java | 162 +----- .../OAuth2ClientRegistrationInfoEntity.java | 51 ++ .../HybridClientRegistrationRepository.java | 12 +- .../oauth2/OAuth2ClientRegistrationDao.java | 9 +- .../OAuth2ClientRegistrationInfoDao.java | 34 ++ .../server/dao/oauth2/OAuth2ServiceImpl.java | 89 ++-- .../server/dao/oauth2/OAuth2Utils.java | 131 +++-- .../JpaOAuth2ClientRegistrationDao.java | 25 +- .../JpaOAuth2ClientRegistrationInfoDao.java | 76 +++ ...Auth2ClientRegistrationInfoRepository.java | 41 ++ .../OAuth2ClientRegistrationRepository.java | 4 - .../resources/sql/schema-entities-hsql.sql | 14 +- .../main/resources/sql/schema-entities.sql | 14 +- .../dao/service/BaseOAuth2ServiceTest.java | 484 +++++++++++++----- 38 files changed, 1562 insertions(+), 571 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java create mode 100644 application/src/main/java/org/thingsboard/server/utils/WebUtils.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationInfoId.java rename common/data/src/main/java/org/thingsboard/server/common/data/oauth2/{ExtendedOAuth2ClientRegistration.java => DomainInfo.java} (81%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ExtendedOAuth2ClientRegistrationInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/oauth2/SchemeType.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractOAuth2ClientRegistrationInfoEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/ExtendedOAuth2ClientRegistrationInfoEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationInfoEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationInfoDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationInfoDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationInfoRepository.java diff --git a/application/src/main/data/upgrade/3.1.1/schema_update.sql b/application/src/main/data/upgrade/3.1.1/schema_update.sql index aa6b59617b..0b99e797b0 100644 --- a/application/src/main/data/upgrade/3.1.1/schema_update.sql +++ b/application/src/main/data/upgrade/3.1.1/schema_update.sql @@ -14,19 +14,17 @@ -- limitations under the License. -- -DROP TABLE IF EXISTS oauth2_client_registration; +DROP TABLE IF EXISTS oauth2_client_registration_info; -CREATE TABLE IF NOT EXISTS oauth2_client_registration ( - id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, +CREATE TABLE IF NOT EXISTS oauth2_client_registration_info ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_info_pkey PRIMARY KEY, enabled boolean, created_time bigint NOT NULL, additional_info varchar, - domain_name varchar(255), client_id varchar(255), client_secret varchar(255), authorization_uri varchar(255), token_uri varchar(255), - redirect_uri_template varchar(255), scope varchar(255), user_info_uri varchar(255), user_name_attribute_name varchar(255), @@ -51,6 +49,16 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration ( custom_send_token boolean ); +DROP TABLE IF EXISTS oauth2_client_registration; + +CREATE TABLE IF NOT EXISTS oauth2_client_registration ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, + created_time bigint NOT NULL, + domain_name varchar(255), + domain_scheme varchar(31), + client_registration_info_id uuid +); + DROP TABLE IF EXISTS oauth2_client_registration_template; CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( diff --git a/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java b/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java new file mode 100644 index 0000000000..e8c59efde8 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java @@ -0,0 +1,257 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; +import org.springframework.security.crypto.keygen.StringKeyGenerator; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.ClientAuthenticationMethod; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; +import org.springframework.security.oauth2.core.oidc.OidcScopes; +import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; +import org.springframework.security.web.util.UrlUtils; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; +import org.thingsboard.server.dao.oauth2.OAuth2Configuration; +import org.thingsboard.server.utils.WebUtils; + +import javax.servlet.http.HttpServletRequest; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +@Service +@Slf4j +public class CustomOAuth2AuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver { + public static final String DEFAULT_AUTHORIZATION_REQUEST_BASE_URI = "/oauth2/authorization"; + public static final String DEFAULT_LOGIN_PROCESSING_URI = "/login/oauth2/code/"; + private static final String REGISTRATION_ID_URI_VARIABLE_NAME = "registrationId"; + private static final char PATH_DELIMITER = '/'; + + private final AntPathRequestMatcher authorizationRequestMatcher = new AntPathRequestMatcher( + DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/{" + REGISTRATION_ID_URI_VARIABLE_NAME + "}"); + private final StringKeyGenerator stateGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder()); + private final StringKeyGenerator secureKeyGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96); + + @Autowired + private ClientRegistrationRepository clientRegistrationRepository; + + @Autowired(required = false) + private OAuth2Configuration oauth2Configuration; + + + @Override + public OAuth2AuthorizationRequest resolve(HttpServletRequest request) { + String registrationId = this.resolveRegistrationId(request); + String redirectUriAction = getAction(request, "login"); + return resolve(request, registrationId, redirectUriAction); + } + + @Override + public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId) { + if (registrationId == null) { + return null; + } + String redirectUriAction = getAction(request, "authorize"); + return resolve(request, registrationId, redirectUriAction); + } + + private String getAction(HttpServletRequest request, String defaultAction) { + String action = request.getParameter("action"); + if (action == null) { + return defaultAction; + } + return action; + } + + private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId, String redirectUriAction) { + if (registrationId == null) { + return null; + } + + ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId); + if (clientRegistration == null) { + throw new IllegalArgumentException("Invalid Client Registration with Id: " + registrationId); + } + + Map attributes = new HashMap<>(); + attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()); + + OAuth2AuthorizationRequest.Builder builder; + if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) { + builder = OAuth2AuthorizationRequest.authorizationCode(); + Map additionalParameters = new HashMap<>(); + if (!CollectionUtils.isEmpty(clientRegistration.getScopes()) && + clientRegistration.getScopes().contains(OidcScopes.OPENID)) { + // Section 3.1.2.1 Authentication Request - https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest + // scope + // REQUIRED. OpenID Connect requests MUST contain the "openid" scope value. + addNonceParameters(attributes, additionalParameters); + } + if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())) { + addPkceParameters(attributes, additionalParameters); + } + builder.additionalParameters(additionalParameters); + } else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) { + builder = OAuth2AuthorizationRequest.implicit(); + } else { + throw new IllegalArgumentException("Invalid Authorization Grant Type (" + + clientRegistration.getAuthorizationGrantType().getValue() + + ") for Client Registration with Id: " + clientRegistration.getRegistrationId()); + } + + String redirectUriStr = expandRedirectUri(request, clientRegistration, redirectUriAction); + + return builder + .clientId(clientRegistration.getClientId()) + .authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri()) + .redirectUri(redirectUriStr) + .scopes(clientRegistration.getScopes()) + .state(this.stateGenerator.generateKey()) + .attributes(attributes) + .build(); + } + + private String resolveRegistrationId(HttpServletRequest request) { + if (this.authorizationRequestMatcher.matches(request)) { + return this.authorizationRequestMatcher + .matcher(request).getVariables().get(REGISTRATION_ID_URI_VARIABLE_NAME); + } + return null; + } + + /** + * Expands the {@link ClientRegistration#getRedirectUriTemplate()} with following provided variables:
+ * - baseUrl (e.g. https://localhost/app)
+ * - baseScheme (e.g. https)
+ * - baseHost (e.g. localhost)
+ * - basePort (e.g. :8080)
+ * - basePath (e.g. /app)
+ * - registrationId (e.g. google)
+ * - action (e.g. login)
+ *

+ * Null variables are provided as empty strings. + *

+ * Default redirectUriTemplate is: {@link org.springframework.security.config.oauth2.client}.CommonOAuth2Provider#DEFAULT_REDIRECT_URL + * + * @return expanded URI + */ + private String expandRedirectUri(HttpServletRequest request, ClientRegistration clientRegistration, String action) { + Map uriVariables = new HashMap<>(); + uriVariables.put("registrationId", clientRegistration.getRegistrationId()); + + UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request)) + .replacePath(request.getContextPath()) + .replaceQuery(null) + .fragment(null) + .build(); + String scheme = uriComponents.getScheme(); + uriVariables.put("baseScheme", scheme == null ? "" : scheme); + String host = uriComponents.getHost(); + uriVariables.put("baseHost", host == null ? "" : host); + // following logic is based on HierarchicalUriComponents#toUriString() + int port = uriComponents.getPort(); + uriVariables.put("basePort", port == -1 ? "" : ":" + port); + String path = uriComponents.getPath(); + if (StringUtils.hasLength(path)) { + if (path.charAt(0) != PATH_DELIMITER) { + path = PATH_DELIMITER + path; + } + } + uriVariables.put("basePath", path == null ? "" : path); + uriVariables.put("baseUrl", uriComponents.toUriString()); + + uriVariables.put("action", action == null ? "" : action); + + return UriComponentsBuilder.fromUriString(getRedirectUri(request)) + .buildAndExpand(uriVariables) + .toUriString(); + } + + private String getRedirectUri(HttpServletRequest request) { + String loginProcessingUri = oauth2Configuration != null ? oauth2Configuration.getLoginProcessingUrl() : DEFAULT_LOGIN_PROCESSING_URI; + + String scheme = WebUtils.getScheme(request); + String host = WebUtils.getHost(request); + String port = WebUtils.getPort(request); + log.trace("Scheme - {}, host - {}, port - {}.", scheme, host, port); + String requestHost = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort(); + return requestHost + loginProcessingUri; + } + + /** + * Creates nonce and its hash for use in OpenID Connect 1.0 Authentication Requests. + * + * @param attributes where the {@link OidcParameterNames#NONCE} is stored for the authentication request + * @param additionalParameters where the {@link OidcParameterNames#NONCE} hash is added for the authentication request + * + * @since 5.2 + * @see 3.1.2.1. Authentication Request + */ + private void addNonceParameters(Map attributes, Map additionalParameters) { + try { + String nonce = this.secureKeyGenerator.generateKey(); + String nonceHash = createHash(nonce); + attributes.put(OidcParameterNames.NONCE, nonce); + additionalParameters.put(OidcParameterNames.NONCE, nonceHash); + } catch (NoSuchAlgorithmException e) { } + } + + /** + * Creates and adds additional PKCE parameters for use in the OAuth 2.0 Authorization and Access Token Requests + * + * @param attributes where {@link PkceParameterNames#CODE_VERIFIER} is stored for the token request + * @param additionalParameters where {@link PkceParameterNames#CODE_CHALLENGE} and, usually, + * {@link PkceParameterNames#CODE_CHALLENGE_METHOD} are added to be used in the authorization request. + * + * @since 5.2 + * @see 1.1. Protocol Flow + * @see 4.1. Client Creates a Code Verifier + * @see 4.2. Client Creates the Code Challenge + */ + private void addPkceParameters(Map attributes, Map additionalParameters) { + String codeVerifier = this.secureKeyGenerator.generateKey(); + attributes.put(PkceParameterNames.CODE_VERIFIER, codeVerifier); + try { + String codeChallenge = createHash(codeVerifier); + additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, codeChallenge); + additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"); + } catch (NoSuchAlgorithmException e) { + additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, codeVerifier); + } + } + + private static String createHash(String value) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] digest = md.digest(value.getBytes(StandardCharsets.US_ASCII)); + return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); + } +} diff --git a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java index fb71bcb994..4894ecd6ae 100644 --- a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java @@ -32,6 +32,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @@ -175,6 +176,9 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt web.ignoring().antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**"); } + @Autowired + private OAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver; + @Override protected void configure(HttpSecurity http) throws Exception { http.headers().cacheControl().and().frameOptions().disable() @@ -209,6 +213,8 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt .addFilterAfter(rateLimitProcessingFilter, UsernamePasswordAuthenticationFilter.class); if (oauth2Configuration != null) { http.oauth2Login() + .authorizationEndpoint().authorizationRequestResolver(oAuth2AuthorizationRequestResolver) + .and() .loginPage("/oauth2Login") .loginProcessingUrl(oauth2Configuration.getLoginProcessingUrl()) .successHandler(oauth2AuthenticationSuccessHandler) diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 577e6c9e83..8363dd7205 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -373,7 +373,7 @@ public abstract class BaseController { case WIDGET_TYPE: checkWidgetTypeId(new WidgetTypeId(entityId.getId()), operation); return; - case OAUTH2_CLIENT_REGISTRATION: + case OAUTH2_CLIENT_REGISTRATION_INFO: case OAUTH2_CLIENT_REGISTRATION_TEMPLATE: return; default: diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index 6be426c245..d94a706440 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -19,13 +19,10 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; -import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientsDomainParams; import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; +import org.thingsboard.server.common.data.oauth2.SchemeType; import org.thingsboard.server.queue.util.TbCoreComponent; import javax.servlet.http.HttpServletRequest; @@ -36,14 +33,11 @@ import java.util.List; @RequestMapping("/api") @Slf4j public class OAuth2Controller extends BaseController { - private static final String CLIENT_REGISTRATION_ID = "clientRegistrationId"; - private static final String DOMAIN = "domain"; - @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST) @ResponseBody public List getOAuth2Clients(HttpServletRequest request) throws ThingsboardException { try { - return oAuth2Service.getOAuth2Clients(request.getServerName()); + return oAuth2Service.getOAuth2Clients(request.getScheme(), request.getServerName()); } catch (Exception e) { throw handleException(e); } @@ -65,56 +59,9 @@ public class OAuth2Controller extends BaseController { @ResponseStatus(value = HttpStatus.OK) public OAuth2ClientsParams saveOAuth2Params(@RequestBody OAuth2ClientsParams oauth2Params) throws ThingsboardException { try { - return oAuth2Service.saveOAuth2Params(oauth2Params); - } catch (Exception e) { - throw handleException(e); - } - } - - @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") - @RequestMapping(value = "/oauth2/config/{clientRegistrationId}", method = RequestMethod.DELETE) - @ResponseStatus(value = HttpStatus.OK) - public void deleteClientRegistration(@PathVariable(CLIENT_REGISTRATION_ID) String strClientRegistrationId) throws ThingsboardException { - checkParameter(CLIENT_REGISTRATION_ID, strClientRegistrationId); - try { - OAuth2ClientRegistrationId clientRegistrationId = new OAuth2ClientRegistrationId(toUUID(strClientRegistrationId)); - oAuth2Service.deleteClientRegistrationById(clientRegistrationId); - - logEntityAction(clientRegistrationId, - null, - null, - ActionType.DELETED, null, strClientRegistrationId); - - } catch (Exception e) { - - logEntityAction(emptyId(EntityType.OAUTH2_CLIENT_REGISTRATION), - null, - null, - ActionType.DELETED, e, strClientRegistrationId); - - throw handleException(e); - } - } - - - @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") - @RequestMapping(value = "/oauth2/config/domain/{domain}", method = RequestMethod.DELETE) - @ResponseStatus(value = HttpStatus.OK) - public void deleteClientRegistrationForDomain(@PathVariable(DOMAIN) String domain) throws ThingsboardException { - checkParameter(DOMAIN, domain); - try { - oAuth2Service.deleteClientRegistrationsByDomain(domain); - - logEntityAction(emptyId(EntityType.OAUTH2_CLIENT_REGISTRATION), null, - null, - ActionType.DELETED, null, domain); - + oAuth2Service.saveOAuth2Params(oauth2Params); + return oAuth2Service.findOAuth2Params(); } catch (Exception e) { - logEntityAction(emptyId(EntityType.OAUTH2_CLIENT_REGISTRATION), - null, - null, - ActionType.DELETED, e, domain); - throw handleException(e); } } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index ddd5de087d..5bb02b9a63 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java @@ -22,7 +22,7 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; import org.thingsboard.server.service.security.model.SecurityUser; @@ -68,7 +68,7 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS try { OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication; - OAuth2ClientRegistration clientRegistration = oAuth2Service.findClientRegistration(UUID.fromString(token.getAuthorizedClientRegistrationId())); + OAuth2ClientRegistrationInfo clientRegistration = oAuth2Service.findClientRegistrationInfo(UUID.fromString(token.getAuthorizedClientRegistrationId())); OAuth2AuthorizedClient oAuth2AuthorizedClient = oAuth2AuthorizedClientService.loadAuthorizedClient( token.getAuthorizedClientRegistrationId(), token.getPrincipal().getName()); diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java index 7bae743c22..3b5a115a61 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java @@ -32,7 +32,7 @@ public enum Resource { USER(EntityType.USER), WIDGETS_BUNDLE(EntityType.WIDGETS_BUNDLE), WIDGET_TYPE(EntityType.WIDGET_TYPE), - OAUTH2_CONFIGURATION(EntityType.OAUTH2_CLIENT_REGISTRATION), + OAUTH2_CONFIGURATION_INFO(EntityType.OAUTH2_CLIENT_REGISTRATION_INFO), OAUTH2_CONFIGURATION_TEMPLATE(EntityType.OAUTH2_CLIENT_REGISTRATION_TEMPLATE), ; diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java index 871de30e6e..49febbcd84 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java @@ -35,7 +35,7 @@ public class SysAdminPermissions extends AbstractPermissions { put(Resource.USER, userPermissionChecker); put(Resource.WIDGETS_BUNDLE, systemEntityPermissionChecker); put(Resource.WIDGET_TYPE, systemEntityPermissionChecker); - put(Resource.OAUTH2_CONFIGURATION, PermissionChecker.allowAllPermissionChecker); + put(Resource.OAUTH2_CONFIGURATION_INFO, PermissionChecker.allowAllPermissionChecker); put(Resource.OAUTH2_CONFIGURATION_TEMPLATE, PermissionChecker.allowAllPermissionChecker); } diff --git a/application/src/main/java/org/thingsboard/server/utils/WebUtils.java b/application/src/main/java/org/thingsboard/server/utils/WebUtils.java new file mode 100644 index 0000000000..31d61634af --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/utils/WebUtils.java @@ -0,0 +1,70 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.utils; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.StringUtils; + +import javax.servlet.http.HttpServletRequest; + +@Slf4j +public class WebUtils { + private static final String X_FORWARDED_HOST_HEADER_KEY = "x-forwarded-host"; + private static final String X_FORWARDED_PORT_HEADER_KEY = "x-forwarded-port"; + private static final String X_FORWARDED_PROTO_HEADER_KEY = "x-forwarded-proto"; + + public static String getHost(HttpServletRequest request) { + String forwardedHost = request.getHeader(X_FORWARDED_HOST_HEADER_KEY); + log.trace("Forwarded host - {}.", forwardedHost); + if (!StringUtils.isEmpty(forwardedHost)) { + if (forwardedHost.contains(":")) { + return forwardedHost.substring(0, forwardedHost.indexOf(":")); + } else { + return forwardedHost; + } + } else { + return request.getServerName(); + } + } + + public static String getScheme(HttpServletRequest request) { + String forwardedProto = request.getHeader(X_FORWARDED_PROTO_HEADER_KEY); + log.trace("Forwarded proto - {}.", forwardedProto); + if (!StringUtils.isEmpty(forwardedProto)) { + return forwardedProto; + } else { + return request.getServerName(); + } + } + + public static String getPort(HttpServletRequest request) { + String forwardedPort = request.getHeader(X_FORWARDED_PORT_HEADER_KEY); + log.trace("Forwarded port - {}.", forwardedPort); + if (!StringUtils.isEmpty(forwardedPort)) { + return forwardedPort; + } + String forwardedHost = request.getHeader(X_FORWARDED_HOST_HEADER_KEY); + if (!StringUtils.isEmpty(forwardedHost)) { + if (forwardedHost.contains(":")) { + return forwardedHost.substring(forwardedHost.indexOf(":")); + } else { + return "HTTP".equals(getScheme(request).toUpperCase()) ? + "80" : "443"; + } + } + return Integer.toString(request.getServerPort()); + } +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java index 76920561d6..073bab823b 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java @@ -15,26 +15,21 @@ */ package org.thingsboard.server.dao.oauth2; -import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; import java.util.List; import java.util.UUID; public interface OAuth2Service { - List getOAuth2Clients(String domainName); + List getOAuth2Clients(String domainScheme, String domainName); - OAuth2ClientsParams saveOAuth2Params(OAuth2ClientsParams oauth2Params); + void saveOAuth2Params(OAuth2ClientsParams oauth2Params); OAuth2ClientsParams findOAuth2Params(); - OAuth2ClientRegistration findClientRegistration(UUID id); + OAuth2ClientRegistrationInfo findClientRegistrationInfo(UUID id); - List findAllClientRegistrations(); - - void deleteClientRegistrationById(OAuth2ClientRegistrationId id); - - void deleteClientRegistrationsByDomain(String domain); + List findAllClientRegistrationInfos(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java index 7791597141..bb786ce196 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java @@ -19,5 +19,5 @@ package org.thingsboard.server.common.data; * @author Andrew Shvayka */ public enum EntityType { - TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, OAUTH2_CLIENT_REGISTRATION, OAUTH2_CLIENT_REGISTRATION_TEMPLATE + TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, OAUTH2_CLIENT_REGISTRATION, OAUTH2_CLIENT_REGISTRATION_INFO, OAUTH2_CLIENT_REGISTRATION_TEMPLATE } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java index 41d9a929f2..7e5bb22019 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java @@ -62,8 +62,8 @@ public class EntityIdFactory { return new WidgetsBundleId(uuid); case WIDGET_TYPE: return new WidgetTypeId(uuid); - case OAUTH2_CLIENT_REGISTRATION: - return new OAuth2ClientRegistrationId(uuid); + case OAUTH2_CLIENT_REGISTRATION_INFO: + return new OAuth2ClientRegistrationInfoId(uuid); case OAUTH2_CLIENT_REGISTRATION_TEMPLATE: return new OAuth2ClientRegistrationTemplateId(uuid); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationInfoId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationInfoId.java new file mode 100644 index 0000000000..999f0d5816 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationInfoId.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.EntityType; + +import java.util.UUID; + +public class OAuth2ClientRegistrationInfoId extends UUIDBased implements EntityId { + + @JsonCreator + public OAuth2ClientRegistrationInfoId(@JsonProperty("id") UUID id) { + super(id); + } + + public static OAuth2ClientRegistrationInfoId fromString(String clientRegistrationInfoId) { + return new OAuth2ClientRegistrationInfoId(UUID.fromString(clientRegistrationInfoId)); + } + + @Override + public EntityType getEntityType() { + return EntityType.OAUTH2_CLIENT_REGISTRATION_INFO; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java index eef79bda6c..b3b1911523 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java @@ -15,11 +15,9 @@ */ package org.thingsboard.server.common.data.oauth2; -import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; import lombok.*; -import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; -import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId; import java.util.List; @@ -30,8 +28,6 @@ import java.util.List; @AllArgsConstructor @Builder public class ClientRegistrationDto { - private OAuth2ClientRegistrationId id; - private long createdTime; private OAuth2MapperConfig mapperConfig; private String clientId; private String clientSecret; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ExtendedOAuth2ClientRegistration.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/DomainInfo.java similarity index 81% rename from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ExtendedOAuth2ClientRegistration.java rename to common/data/src/main/java/org/thingsboard/server/common/data/oauth2/DomainInfo.java index 18d5337132..7eb5b34661 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ExtendedOAuth2ClientRegistration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/DomainInfo.java @@ -17,12 +17,13 @@ package org.thingsboard.server.common.data.oauth2; import lombok.*; +@EqualsAndHashCode @Data @ToString -@Builder(toBuilder = true) @NoArgsConstructor @AllArgsConstructor -public class ExtendedOAuth2ClientRegistration { - private String redirectUriTemplate; - private OAuth2ClientRegistration clientRegistration; +@Builder +public class DomainInfo { + private SchemeType scheme; + private String name; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ExtendedOAuth2ClientRegistrationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ExtendedOAuth2ClientRegistrationInfo.java new file mode 100644 index 0000000000..9998b2bd35 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ExtendedOAuth2ClientRegistrationInfo.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.oauth2; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class ExtendedOAuth2ClientRegistrationInfo extends OAuth2ClientRegistrationInfo { + + private String domainName; + private SchemeType domainScheme; + + public ExtendedOAuth2ClientRegistrationInfo() { + super(); + } + + public ExtendedOAuth2ClientRegistrationInfo(OAuth2ClientRegistrationInfo oAuth2ClientRegistrationInfo, + SchemeType domainScheme, + String domainName) { + super(oAuth2ClientRegistrationInfo); + this.domainScheme = domainScheme; + this.domainName = domainName; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java index 1932a77bef..0ced05cfdb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java @@ -15,68 +15,28 @@ */ package org.thingsboard.server.common.data.oauth2; -import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; -import org.thingsboard.server.common.data.HasName; -import org.thingsboard.server.common.data.HasTenantId; -import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; +import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; -import org.thingsboard.server.common.data.id.TenantId; - -import java.util.List; +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId; @EqualsAndHashCode(callSuper = true) @Data -@ToString(exclude = {"clientSecret"}) +@ToString @NoArgsConstructor -public class OAuth2ClientRegistration extends SearchTextBasedWithAdditionalInfo implements HasName { +public class OAuth2ClientRegistration extends BaseData { - private boolean enabled; + private OAuth2ClientRegistrationInfoId clientRegistrationId; private String domainName; - private String redirectUriTemplate; - private OAuth2MapperConfig mapperConfig; - private String clientId; - private String clientSecret; - private String authorizationUri; - private String accessTokenUri; - private List scope; - private String userInfoUri; - private String userNameAttributeName; - private String jwkSetUri; - private String clientAuthenticationMethod; - private String loginButtonLabel; - private String loginButtonIcon; + private SchemeType domainScheme; public OAuth2ClientRegistration(OAuth2ClientRegistration clientRegistration) { super(clientRegistration); - this.enabled = clientRegistration.enabled; + this.clientRegistrationId = clientRegistration.clientRegistrationId; this.domainName = clientRegistration.domainName; - this.redirectUriTemplate = clientRegistration.redirectUriTemplate; - this.mapperConfig = clientRegistration.mapperConfig; - this.clientId = clientRegistration.clientId; - this.clientSecret = clientRegistration.clientSecret; - this.authorizationUri = clientRegistration.authorizationUri; - this.accessTokenUri = clientRegistration.accessTokenUri; - this.scope = clientRegistration.scope; - this.userInfoUri = clientRegistration.userInfoUri; - this.userNameAttributeName = clientRegistration.userNameAttributeName; - this.jwkSetUri = clientRegistration.jwkSetUri; - this.clientAuthenticationMethod = clientRegistration.clientAuthenticationMethod; - this.loginButtonLabel = clientRegistration.loginButtonLabel; - this.loginButtonIcon = clientRegistration.loginButtonIcon; - } - - @Override - @JsonProperty(access = JsonProperty.Access.READ_ONLY) - public String getName() { - return loginButtonLabel; - } - - @Override - public String getSearchText() { - return getName(); + this.domainScheme = clientRegistration.domainScheme; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationInfo.java new file mode 100644 index 0000000000..177d677abf --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationInfo.java @@ -0,0 +1,76 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.oauth2; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.thingsboard.server.common.data.HasName; +import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId; + +import java.util.List; + +@EqualsAndHashCode(callSuper = true) +@Data +@ToString(exclude = {"clientSecret"}) +@NoArgsConstructor +public class OAuth2ClientRegistrationInfo extends SearchTextBasedWithAdditionalInfo implements HasName { + + private boolean enabled; + private OAuth2MapperConfig mapperConfig; + private String clientId; + private String clientSecret; + private String authorizationUri; + private String accessTokenUri; + private List scope; + private String userInfoUri; + private String userNameAttributeName; + private String jwkSetUri; + private String clientAuthenticationMethod; + private String loginButtonLabel; + private String loginButtonIcon; + + public OAuth2ClientRegistrationInfo(OAuth2ClientRegistrationInfo clientRegistration) { + super(clientRegistration); + this.enabled = clientRegistration.enabled; + this.mapperConfig = clientRegistration.mapperConfig; + this.clientId = clientRegistration.clientId; + this.clientSecret = clientRegistration.clientSecret; + this.authorizationUri = clientRegistration.authorizationUri; + this.accessTokenUri = clientRegistration.accessTokenUri; + this.scope = clientRegistration.scope; + this.userInfoUri = clientRegistration.userInfoUri; + this.userNameAttributeName = clientRegistration.userNameAttributeName; + this.jwkSetUri = clientRegistration.jwkSetUri; + this.clientAuthenticationMethod = clientRegistration.clientAuthenticationMethod; + this.loginButtonLabel = clientRegistration.loginButtonLabel; + this.loginButtonIcon = clientRegistration.loginButtonIcon; + } + + @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public String getName() { + return loginButtonLabel; + } + + @Override + public String getSearchText() { + return getName(); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java index 33670c7b2b..d93401f5cc 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.oauth2; import lombok.*; import java.util.List; +import java.util.Set; @EqualsAndHashCode @Data @@ -26,7 +27,6 @@ import java.util.List; @NoArgsConstructor @AllArgsConstructor public class OAuth2ClientsDomainParams { - private String domainName; - private String redirectUriTemplate; - private List clientRegistrations; + private Set domainInfos; + private Set clientRegistrations; } \ No newline at end of file diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java index 7eb29f9068..3cb3b863fd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java @@ -16,11 +16,7 @@ package org.thingsboard.server.common.data.oauth2; import lombok.*; -import org.thingsboard.server.common.data.id.TenantId; - -import java.util.Collection; -import java.util.List; -import java.util.Objects; +import java.util.Set; @EqualsAndHashCode @Data @@ -30,5 +26,5 @@ import java.util.Objects; @AllArgsConstructor public class OAuth2ClientsParams { private boolean enabled; - private List oAuth2DomainDtos; + private Set oAuth2DomainDtos; } \ No newline at end of file diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/SchemeType.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/SchemeType.java new file mode 100644 index 0000000000..80d0362b7d --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/SchemeType.java @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.oauth2; + +public enum SchemeType { + HTTP, HTTPS, MIXED; +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index f6d599cefc..b3f0f5b954 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -358,11 +358,15 @@ public class ModelConstants { * OAuth2 client registration constants. */ public static final String OAUTH2_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; - public static final String OAUTH2_ENABLED_PROPERTY = "enabled"; + public static final String OAUTH2_CLIENT_REGISTRATION_INFO_COLUMN_FAMILY_NAME = "oauth2_client_registration_info"; public static final String OAUTH2_CLIENT_REGISTRATION_COLUMN_FAMILY_NAME = "oauth2_client_registration"; + public static final String OAUTH2_CLIENT_REGISTRATION_TO_DOMAIN_COLUMN_FAMILY_NAME = "oauth2_client_registration_to_domain"; public static final String OAUTH2_CLIENT_REGISTRATION_TEMPLATE_COLUMN_FAMILY_NAME = "oauth2_client_registration_template"; + public static final String OAUTH2_ENABLED_PROPERTY = "enabled"; public static final String OAUTH2_TEMPLATE_PROVIDER_ID_PROPERTY = "provider_id"; + public static final String OAUTH2_CLIENT_REGISTRATION_INFO_ID_PROPERTY = "client_registration_info_id"; public static final String OAUTH2_DOMAIN_NAME_PROPERTY = "domain_name"; + public static final String OAUTH2_DOMAIN_SCHEME_PROPERTY = "domain_scheme"; public static final String OAUTH2_CLIENT_ID_PROPERTY = "client_id"; public static final String OAUTH2_CLIENT_SECRET_PROPERTY = "client_secret"; public static final String OAUTH2_AUTHORIZATION_URI_PROPERTY = "authorization_uri"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractOAuth2ClientRegistrationInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractOAuth2ClientRegistrationInfoEntity.java new file mode 100644 index 0000000000..efa9a563ec --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractOAuth2ClientRegistrationInfoEntity.java @@ -0,0 +1,231 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.model.sql; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId; +import org.thingsboard.server.common.data.oauth2.*; +import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.util.mapping.JsonStringType; + +import javax.persistence.*; +import java.util.Arrays; + +@Data +@EqualsAndHashCode(callSuper = true) +@TypeDef(name = "json", typeClass = JsonStringType.class) +@MappedSuperclass +public abstract class AbstractOAuth2ClientRegistrationInfoEntity extends BaseSqlEntity { + + @Column(name = ModelConstants.OAUTH2_ENABLED_PROPERTY) + private Boolean enabled; + @Column(name = ModelConstants.OAUTH2_CLIENT_ID_PROPERTY) + private String clientId; + @Column(name = ModelConstants.OAUTH2_CLIENT_SECRET_PROPERTY) + private String clientSecret; + @Column(name = ModelConstants.OAUTH2_AUTHORIZATION_URI_PROPERTY) + private String authorizationUri; + @Column(name = ModelConstants.OAUTH2_TOKEN_URI_PROPERTY) + private String tokenUri; + @Column(name = ModelConstants.OAUTH2_SCOPE_PROPERTY) + private String scope; + @Column(name = ModelConstants.OAUTH2_USER_INFO_URI_PROPERTY) + private String userInfoUri; + @Column(name = ModelConstants.OAUTH2_USER_NAME_ATTRIBUTE_NAME_PROPERTY) + private String userNameAttributeName; + @Column(name = ModelConstants.OAUTH2_JWK_SET_URI_PROPERTY) + private String jwkSetUri; + @Column(name = ModelConstants.OAUTH2_CLIENT_AUTHENTICATION_METHOD_PROPERTY) + private String clientAuthenticationMethod; + @Column(name = ModelConstants.OAUTH2_LOGIN_BUTTON_LABEL_PROPERTY) + private String loginButtonLabel; + @Column(name = ModelConstants.OAUTH2_LOGIN_BUTTON_ICON_PROPERTY) + private String loginButtonIcon; + @Column(name = ModelConstants.OAUTH2_ALLOW_USER_CREATION_PROPERTY) + private Boolean allowUserCreation; + @Column(name = ModelConstants.OAUTH2_ACTIVATE_USER_PROPERTY) + private Boolean activateUser; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.OAUTH2_MAPPER_TYPE_PROPERTY) + private MapperType type; + @Column(name = ModelConstants.OAUTH2_EMAIL_ATTRIBUTE_KEY_PROPERTY) + private String emailAttributeKey; + @Column(name = ModelConstants.OAUTH2_FIRST_NAME_ATTRIBUTE_KEY_PROPERTY) + private String firstNameAttributeKey; + @Column(name = ModelConstants.OAUTH2_LAST_NAME_ATTRIBUTE_KEY_PROPERTY) + private String lastNameAttributeKey; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.OAUTH2_TENANT_NAME_STRATEGY_PROPERTY) + private TenantNameStrategyType tenantNameStrategy; + @Column(name = ModelConstants.OAUTH2_TENANT_NAME_PATTERN_PROPERTY) + private String tenantNamePattern; + @Column(name = ModelConstants.OAUTH2_CUSTOMER_NAME_PATTERN_PROPERTY) + private String customerNamePattern; + @Column(name = ModelConstants.OAUTH2_DEFAULT_DASHBOARD_NAME_PROPERTY) + private String defaultDashboardName; + @Column(name = ModelConstants.OAUTH2_ALWAYS_FULL_SCREEN_PROPERTY) + private Boolean alwaysFullScreen; + @Column(name = ModelConstants.OAUTH2_MAPPER_URL_PROPERTY) + private String url; + @Column(name = ModelConstants.OAUTH2_MAPPER_USERNAME_PROPERTY) + private String username; + @Column(name = ModelConstants.OAUTH2_MAPPER_PASSWORD_PROPERTY) + private String password; + @Column(name = ModelConstants.OAUTH2_MAPPER_SEND_TOKEN_PROPERTY) + private Boolean sendToken; + + @Type(type = "json") + @Column(name = ModelConstants.OAUTH2_ADDITIONAL_INFO_PROPERTY) + private JsonNode additionalInfo; + + public AbstractOAuth2ClientRegistrationInfoEntity() { + super(); + } + + public AbstractOAuth2ClientRegistrationInfoEntity(OAuth2ClientRegistrationInfo clientRegistrationInfo) { + if (clientRegistrationInfo.getId() != null) { + this.setUuid(clientRegistrationInfo.getId().getId()); + } + this.createdTime = clientRegistrationInfo.getCreatedTime(); + this.enabled = clientRegistrationInfo.isEnabled(); + this.clientId = clientRegistrationInfo.getClientId(); + this.clientSecret = clientRegistrationInfo.getClientSecret(); + this.authorizationUri = clientRegistrationInfo.getAuthorizationUri(); + this.tokenUri = clientRegistrationInfo.getAccessTokenUri(); + this.scope = clientRegistrationInfo.getScope().stream().reduce((result, element) -> result + "," + element).orElse(""); + this.userInfoUri = clientRegistrationInfo.getUserInfoUri(); + this.userNameAttributeName = clientRegistrationInfo.getUserNameAttributeName(); + this.jwkSetUri = clientRegistrationInfo.getJwkSetUri(); + this.clientAuthenticationMethod = clientRegistrationInfo.getClientAuthenticationMethod(); + this.loginButtonLabel = clientRegistrationInfo.getLoginButtonLabel(); + this.loginButtonIcon = clientRegistrationInfo.getLoginButtonIcon(); + this.additionalInfo = clientRegistrationInfo.getAdditionalInfo(); + OAuth2MapperConfig mapperConfig = clientRegistrationInfo.getMapperConfig(); + if (mapperConfig != null) { + this.allowUserCreation = mapperConfig.isAllowUserCreation(); + this.activateUser = mapperConfig.isActivateUser(); + this.type = mapperConfig.getType(); + OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic(); + if (basicConfig != null) { + this.emailAttributeKey = basicConfig.getEmailAttributeKey(); + this.firstNameAttributeKey = basicConfig.getFirstNameAttributeKey(); + this.lastNameAttributeKey = basicConfig.getLastNameAttributeKey(); + this.tenantNameStrategy = basicConfig.getTenantNameStrategy(); + this.tenantNamePattern = basicConfig.getTenantNamePattern(); + this.customerNamePattern = basicConfig.getCustomerNamePattern(); + this.defaultDashboardName = basicConfig.getDefaultDashboardName(); + this.alwaysFullScreen = basicConfig.isAlwaysFullScreen(); + } + OAuth2CustomMapperConfig customConfig = mapperConfig.getCustom(); + if (customConfig != null) { + this.url = customConfig.getUrl(); + this.username = customConfig.getUsername(); + this.password = customConfig.getPassword(); + this.sendToken = customConfig.isSendToken(); + } + } + } + + public AbstractOAuth2ClientRegistrationInfoEntity(OAuth2ClientRegistrationInfoEntity oAuth2ClientRegistrationInfoEntity) { + this.setId(oAuth2ClientRegistrationInfoEntity.getId()); + this.setCreatedTime(oAuth2ClientRegistrationInfoEntity.getCreatedTime()); + this.enabled = oAuth2ClientRegistrationInfoEntity.getEnabled(); + this.clientId = oAuth2ClientRegistrationInfoEntity.getClientId(); + this.clientSecret = oAuth2ClientRegistrationInfoEntity.getClientSecret(); + this.authorizationUri = oAuth2ClientRegistrationInfoEntity.getAuthorizationUri(); + this.tokenUri = oAuth2ClientRegistrationInfoEntity.getTokenUri(); + this.scope = oAuth2ClientRegistrationInfoEntity.getScope(); + this.userInfoUri = oAuth2ClientRegistrationInfoEntity.getUserInfoUri(); + this.userNameAttributeName = oAuth2ClientRegistrationInfoEntity.getUserNameAttributeName(); + this.jwkSetUri = oAuth2ClientRegistrationInfoEntity.getJwkSetUri(); + this.clientAuthenticationMethod = oAuth2ClientRegistrationInfoEntity.getClientAuthenticationMethod(); + this.loginButtonLabel = oAuth2ClientRegistrationInfoEntity.getLoginButtonLabel(); + this.loginButtonIcon = oAuth2ClientRegistrationInfoEntity.getLoginButtonIcon(); + this.additionalInfo = oAuth2ClientRegistrationInfoEntity.getAdditionalInfo(); + this.allowUserCreation = oAuth2ClientRegistrationInfoEntity.getAllowUserCreation(); + this.activateUser = oAuth2ClientRegistrationInfoEntity.getActivateUser(); + this.type = oAuth2ClientRegistrationInfoEntity.getType(); + this.emailAttributeKey = oAuth2ClientRegistrationInfoEntity.getEmailAttributeKey(); + this.firstNameAttributeKey = oAuth2ClientRegistrationInfoEntity.getFirstNameAttributeKey(); + this.lastNameAttributeKey = oAuth2ClientRegistrationInfoEntity.getLastNameAttributeKey(); + this.tenantNameStrategy = oAuth2ClientRegistrationInfoEntity.getTenantNameStrategy(); + this.tenantNamePattern = oAuth2ClientRegistrationInfoEntity.getTenantNamePattern(); + this.customerNamePattern = oAuth2ClientRegistrationInfoEntity.getCustomerNamePattern(); + this.defaultDashboardName = oAuth2ClientRegistrationInfoEntity.getDefaultDashboardName(); + this.alwaysFullScreen = oAuth2ClientRegistrationInfoEntity.getAlwaysFullScreen(); + this.url = oAuth2ClientRegistrationInfoEntity.getUrl(); + this.username = oAuth2ClientRegistrationInfoEntity.getUsername(); + this.password = oAuth2ClientRegistrationInfoEntity.getPassword(); + this.sendToken = oAuth2ClientRegistrationInfoEntity.getSendToken(); + } + + + protected OAuth2ClientRegistrationInfo toOAuth2ClientRegistrationInfo() { + OAuth2ClientRegistrationInfo clientRegistrationInfo = new OAuth2ClientRegistrationInfo(); + clientRegistrationInfo.setId(new OAuth2ClientRegistrationInfoId(id)); + clientRegistrationInfo.setEnabled(enabled); + clientRegistrationInfo.setCreatedTime(createdTime); + clientRegistrationInfo.setAdditionalInfo(additionalInfo); + clientRegistrationInfo.setMapperConfig( + OAuth2MapperConfig.builder() + .allowUserCreation(allowUserCreation) + .activateUser(activateUser) + .type(type) + .basic( + type == MapperType.BASIC ? + OAuth2BasicMapperConfig.builder() + .emailAttributeKey(emailAttributeKey) + .firstNameAttributeKey(firstNameAttributeKey) + .lastNameAttributeKey(lastNameAttributeKey) + .tenantNameStrategy(tenantNameStrategy) + .tenantNamePattern(tenantNamePattern) + .customerNamePattern(customerNamePattern) + .defaultDashboardName(defaultDashboardName) + .alwaysFullScreen(alwaysFullScreen) + .build() + : null + ) + .custom( + type == MapperType.CUSTOM ? + OAuth2CustomMapperConfig.builder() + .url(url) + .username(username) + .password(password) + .sendToken(sendToken) + .build() + : null + ) + .build() + ); + clientRegistrationInfo.setClientId(clientId); + clientRegistrationInfo.setClientSecret(clientSecret); + clientRegistrationInfo.setAuthorizationUri(authorizationUri); + clientRegistrationInfo.setAccessTokenUri(tokenUri); + clientRegistrationInfo.setScope(Arrays.asList(scope.split(","))); + clientRegistrationInfo.setUserInfoUri(userInfoUri); + clientRegistrationInfo.setUserNameAttributeName(userNameAttributeName); + clientRegistrationInfo.setJwkSetUri(jwkSetUri); + clientRegistrationInfo.setClientAuthenticationMethod(clientAuthenticationMethod); + clientRegistrationInfo.setLoginButtonLabel(loginButtonLabel); + clientRegistrationInfo.setLoginButtonIcon(loginButtonIcon); + return clientRegistrationInfo; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ExtendedOAuth2ClientRegistrationInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ExtendedOAuth2ClientRegistrationInfoEntity.java new file mode 100644 index 0000000000..ddb6e264ac --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ExtendedOAuth2ClientRegistrationInfoEntity.java @@ -0,0 +1,48 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.model.sql; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.oauth2.ExtendedOAuth2ClientRegistrationInfo; +import org.thingsboard.server.common.data.oauth2.SchemeType; + +@Data +@EqualsAndHashCode(callSuper = true) +public class ExtendedOAuth2ClientRegistrationInfoEntity extends AbstractOAuth2ClientRegistrationInfoEntity { + + private String domainName; + private SchemeType domainScheme; + + public ExtendedOAuth2ClientRegistrationInfoEntity() { + super(); + } + + public ExtendedOAuth2ClientRegistrationInfoEntity(OAuth2ClientRegistrationInfoEntity oAuth2ClientRegistrationInfoEntity, + String domainName, + SchemeType domainScheme) { + super(oAuth2ClientRegistrationInfoEntity); + this.domainName = domainName; + this.domainScheme = domainScheme; + } + + @Override + public ExtendedOAuth2ClientRegistrationInfo toData() { + return new ExtendedOAuth2ClientRegistrationInfo(super.toOAuth2ClientRegistrationInfo(), + domainScheme, + domainName); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java index c34e2a9793..466034e31c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java @@ -15,13 +15,11 @@ */ package org.thingsboard.server.dao.model.sql; -import com.fasterxml.jackson.databind.JsonNode; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; -import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId; import org.thingsboard.server.common.data.oauth2.*; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; @@ -38,70 +36,15 @@ import java.util.UUID; @Table(name = ModelConstants.OAUTH2_CLIENT_REGISTRATION_COLUMN_FAMILY_NAME) public class OAuth2ClientRegistrationEntity extends BaseSqlEntity { - @Column(name = ModelConstants.OAUTH2_ENABLED_PROPERTY) - private Boolean enabled; + @Column(name = ModelConstants.OAUTH2_CLIENT_REGISTRATION_INFO_ID_PROPERTY, columnDefinition = "uuid") + private UUID clientRegistrationInfoId; + @Column(name = ModelConstants.OAUTH2_DOMAIN_NAME_PROPERTY) private String domainName; - @Column(name = ModelConstants.OAUTH2_CLIENT_ID_PROPERTY) - private String clientId; - @Column(name = ModelConstants.OAUTH2_CLIENT_SECRET_PROPERTY) - private String clientSecret; - @Column(name = ModelConstants.OAUTH2_AUTHORIZATION_URI_PROPERTY) - private String authorizationUri; - @Column(name = ModelConstants.OAUTH2_TOKEN_URI_PROPERTY) - private String tokenUri; - @Column(name = ModelConstants.OAUTH2_REDIRECT_URI_TEMPLATE_PROPERTY) - private String redirectUriTemplate; - @Column(name = ModelConstants.OAUTH2_SCOPE_PROPERTY) - private String scope; - @Column(name = ModelConstants.OAUTH2_USER_INFO_URI_PROPERTY) - private String userInfoUri; - @Column(name = ModelConstants.OAUTH2_USER_NAME_ATTRIBUTE_NAME_PROPERTY) - private String userNameAttributeName; - @Column(name = ModelConstants.OAUTH2_JWK_SET_URI_PROPERTY) - private String jwkSetUri; - @Column(name = ModelConstants.OAUTH2_CLIENT_AUTHENTICATION_METHOD_PROPERTY) - private String clientAuthenticationMethod; - @Column(name = ModelConstants.OAUTH2_LOGIN_BUTTON_LABEL_PROPERTY) - private String loginButtonLabel; - @Column(name = ModelConstants.OAUTH2_LOGIN_BUTTON_ICON_PROPERTY) - private String loginButtonIcon; - @Column(name = ModelConstants.OAUTH2_ALLOW_USER_CREATION_PROPERTY) - private Boolean allowUserCreation; - @Column(name = ModelConstants.OAUTH2_ACTIVATE_USER_PROPERTY) - private Boolean activateUser; - @Enumerated(EnumType.STRING) - @Column(name = ModelConstants.OAUTH2_MAPPER_TYPE_PROPERTY) - private MapperType type; - @Column(name = ModelConstants.OAUTH2_EMAIL_ATTRIBUTE_KEY_PROPERTY) - private String emailAttributeKey; - @Column(name = ModelConstants.OAUTH2_FIRST_NAME_ATTRIBUTE_KEY_PROPERTY) - private String firstNameAttributeKey; - @Column(name = ModelConstants.OAUTH2_LAST_NAME_ATTRIBUTE_KEY_PROPERTY) - private String lastNameAttributeKey; - @Enumerated(EnumType.STRING) - @Column(name = ModelConstants.OAUTH2_TENANT_NAME_STRATEGY_PROPERTY) - private TenantNameStrategyType tenantNameStrategy; - @Column(name = ModelConstants.OAUTH2_TENANT_NAME_PATTERN_PROPERTY) - private String tenantNamePattern; - @Column(name = ModelConstants.OAUTH2_CUSTOMER_NAME_PATTERN_PROPERTY) - private String customerNamePattern; - @Column(name = ModelConstants.OAUTH2_DEFAULT_DASHBOARD_NAME_PROPERTY) - private String defaultDashboardName; - @Column(name = ModelConstants.OAUTH2_ALWAYS_FULL_SCREEN_PROPERTY) - private Boolean alwaysFullScreen; - @Column(name = ModelConstants.OAUTH2_MAPPER_URL_PROPERTY) - private String url; - @Column(name = ModelConstants.OAUTH2_MAPPER_USERNAME_PROPERTY) - private String username; - @Column(name = ModelConstants.OAUTH2_MAPPER_PASSWORD_PROPERTY) - private String password; - @Column(name = ModelConstants.OAUTH2_MAPPER_SEND_TOKEN_PROPERTY) - private Boolean sendToken; - @Type(type = "json") - @Column(name = ModelConstants.OAUTH2_ADDITIONAL_INFO_PROPERTY) - private JsonNode additionalInfo; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.OAUTH2_DOMAIN_SCHEME_PROPERTY) + private SchemeType domainScheme; public OAuth2ClientRegistrationEntity() { super(); @@ -111,99 +54,22 @@ public class OAuth2ClientRegistrationEntity extends BaseSqlEntity result + "," + element).orElse(""); - this.userInfoUri = clientRegistration.getUserInfoUri(); - this.userNameAttributeName = clientRegistration.getUserNameAttributeName(); - this.jwkSetUri = clientRegistration.getJwkSetUri(); - this.clientAuthenticationMethod = clientRegistration.getClientAuthenticationMethod(); - this.loginButtonLabel = clientRegistration.getLoginButtonLabel(); - this.loginButtonIcon = clientRegistration.getLoginButtonIcon(); - this.additionalInfo = clientRegistration.getAdditionalInfo(); - OAuth2MapperConfig mapperConfig = clientRegistration.getMapperConfig(); - if (mapperConfig != null) { - this.allowUserCreation = mapperConfig.isAllowUserCreation(); - this.activateUser = mapperConfig.isActivateUser(); - this.type = mapperConfig.getType(); - OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic(); - if (basicConfig != null) { - this.emailAttributeKey = basicConfig.getEmailAttributeKey(); - this.firstNameAttributeKey = basicConfig.getFirstNameAttributeKey(); - this.lastNameAttributeKey = basicConfig.getLastNameAttributeKey(); - this.tenantNameStrategy = basicConfig.getTenantNameStrategy(); - this.tenantNamePattern = basicConfig.getTenantNamePattern(); - this.customerNamePattern = basicConfig.getCustomerNamePattern(); - this.defaultDashboardName = basicConfig.getDefaultDashboardName(); - this.alwaysFullScreen = basicConfig.isAlwaysFullScreen(); - } - OAuth2CustomMapperConfig customConfig = mapperConfig.getCustom(); - if (customConfig != null) { - this.url = customConfig.getUrl(); - this.username = customConfig.getUsername(); - this.password = customConfig.getPassword(); - this.sendToken = customConfig.isSendToken(); - } + if (clientRegistration.getClientRegistrationId() != null){ + this.clientRegistrationInfoId = clientRegistration.getClientRegistrationId().getId(); } + this.createdTime = clientRegistration.getCreatedTime(); + this.domainName = clientRegistration.getDomainName(); + this.domainScheme = clientRegistration.getDomainScheme(); } @Override public OAuth2ClientRegistration toData() { OAuth2ClientRegistration clientRegistration = new OAuth2ClientRegistration(); clientRegistration.setId(new OAuth2ClientRegistrationId(id)); - clientRegistration.setEnabled(enabled); + clientRegistration.setClientRegistrationId(new OAuth2ClientRegistrationInfoId(clientRegistrationInfoId)); clientRegistration.setCreatedTime(createdTime); clientRegistration.setDomainName(domainName); - clientRegistration.setAdditionalInfo(additionalInfo); - clientRegistration.setMapperConfig( - OAuth2MapperConfig.builder() - .allowUserCreation(allowUserCreation) - .activateUser(activateUser) - .type(type) - .basic( - type == MapperType.BASIC ? - OAuth2BasicMapperConfig.builder() - .emailAttributeKey(emailAttributeKey) - .firstNameAttributeKey(firstNameAttributeKey) - .lastNameAttributeKey(lastNameAttributeKey) - .tenantNameStrategy(tenantNameStrategy) - .tenantNamePattern(tenantNamePattern) - .customerNamePattern(customerNamePattern) - .defaultDashboardName(defaultDashboardName) - .alwaysFullScreen(alwaysFullScreen) - .build() - : null - ) - .custom( - type == MapperType.CUSTOM ? - OAuth2CustomMapperConfig.builder() - .url(url) - .username(username) - .password(password) - .sendToken(sendToken) - .build() - : null - ) - .build() - ); - clientRegistration.setClientId(clientId); - clientRegistration.setClientSecret(clientSecret); - clientRegistration.setAuthorizationUri(authorizationUri); - clientRegistration.setAccessTokenUri(tokenUri); - clientRegistration.setRedirectUriTemplate(redirectUriTemplate); - clientRegistration.setScope(Arrays.asList(scope.split(","))); - clientRegistration.setUserInfoUri(userInfoUri); - clientRegistration.setUserNameAttributeName(userNameAttributeName); - clientRegistration.setJwkSetUri(jwkSetUri); - clientRegistration.setClientAuthenticationMethod(clientAuthenticationMethod); - clientRegistration.setLoginButtonLabel(loginButtonLabel); - clientRegistration.setLoginButtonIcon(loginButtonIcon); + clientRegistration.setDomainScheme(domainScheme); return clientRegistration; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationInfoEntity.java new file mode 100644 index 0000000000..7fd68e2716 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationInfoEntity.java @@ -0,0 +1,51 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.model.sql; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.TypeDef; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.util.mapping.JsonStringType; + +import javax.persistence.Entity; +import javax.persistence.Table; + +@Data +@EqualsAndHashCode(callSuper = true) +@Entity +@TypeDef(name = "json", typeClass = JsonStringType.class) +@Table(name = ModelConstants.OAUTH2_CLIENT_REGISTRATION_INFO_COLUMN_FAMILY_NAME) +public class OAuth2ClientRegistrationInfoEntity extends AbstractOAuth2ClientRegistrationInfoEntity { + + public OAuth2ClientRegistrationInfoEntity() { + super(); + } + + public OAuth2ClientRegistrationInfoEntity(OAuth2ClientRegistrationInfo clientRegistration) { + super(clientRegistration); + } + + public OAuth2ClientRegistrationInfoEntity(OAuth2ClientRegistrationInfoEntity oAuth2ClientRegistrationInfoEntity) { + super(oAuth2ClientRegistrationInfoEntity); + } + + @Override + public OAuth2ClientRegistrationInfo toData() { + return super.toOAuth2ClientRegistrationInfo(); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java index c3b3572da3..93c4c47df0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java @@ -21,8 +21,7 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.oauth2.ExtendedOAuth2ClientRegistration; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; import java.util.UUID; @@ -34,12 +33,12 @@ public class HybridClientRegistrationRepository implements ClientRegistrationRep @Override public ClientRegistration findByRegistrationId(String registrationId) { - OAuth2ClientRegistration oAuth2ClientRegistration = oAuth2Service.findClientRegistration(UUID.fromString(registrationId)); - return oAuth2ClientRegistration == null ? - null : toSpringClientRegistration(oAuth2ClientRegistration); + OAuth2ClientRegistrationInfo oAuth2ClientRegistrationInfo = oAuth2Service.findClientRegistrationInfo(UUID.fromString(registrationId)); + return oAuth2ClientRegistrationInfo == null ? + null : toSpringClientRegistration(oAuth2ClientRegistrationInfo); } - private ClientRegistration toSpringClientRegistration(OAuth2ClientRegistration localClientRegistration){ + private ClientRegistration toSpringClientRegistration(OAuth2ClientRegistrationInfo localClientRegistration){ String registrationId = localClientRegistration.getUuidId().toString(); return ClientRegistration.withRegistrationId(registrationId) .clientName(localClientRegistration.getName()) @@ -47,7 +46,6 @@ public class HybridClientRegistrationRepository implements ClientRegistrationRep .authorizationUri(localClientRegistration.getAuthorizationUri()) .clientSecret(localClientRegistration.getClientSecret()) .tokenUri(localClientRegistration.getAccessTokenUri()) - .redirectUriTemplate(localClientRegistration.getRedirectUriTemplate()) .scope(localClientRegistration.getScope()) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .userInfoUri(localClientRegistration.getUserInfoUri()) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java index 6dc1c729cb..d56ff34354 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java @@ -18,13 +18,6 @@ package org.thingsboard.server.dao.oauth2; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; import org.thingsboard.server.dao.Dao; -import java.util.List; -import java.util.UUID; - public interface OAuth2ClientRegistrationDao extends Dao { - List findAll(); - - List findByDomainName(String domainName); - - int removeByDomainName(String domainName); + void deleteAll(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationInfoDao.java new file mode 100644 index 0000000000..a907aefe6b --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationInfoDao.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.oauth2; + +import org.thingsboard.server.common.data.oauth2.ExtendedOAuth2ClientRegistrationInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; +import org.thingsboard.server.common.data.oauth2.SchemeType; +import org.thingsboard.server.dao.Dao; + +import java.util.List; +import java.util.Set; + +public interface OAuth2ClientRegistrationInfoDao extends Dao { + List findAll(); + + List findAllExtended(); + + List findByDomainSchemesAndDomainName(List domainSchemes, String domainName); + + void deleteAll(); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index c9fcb9e51b..fab4c559d3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -19,11 +19,11 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; -import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.*; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.exception.IncorrectParameterException; import javax.transaction.Transactional; import java.util.*; @@ -39,78 +39,89 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; public static final String INCORRECT_CLIENT_REGISTRATION_ID = "Incorrect clientRegistrationId "; public static final String INCORRECT_DOMAIN_NAME = "Incorrect domainName "; + public static final String INCORRECT_DOMAIN_SCHEME = "Incorrect domainScheme "; + @Autowired + private OAuth2ClientRegistrationInfoDao clientRegistrationInfoDao; @Autowired private OAuth2ClientRegistrationDao clientRegistrationDao; @Override - public List getOAuth2Clients(String domainName) { - log.trace("Executing getOAuth2Clients [{}]", domainName); + public List getOAuth2Clients(String domainSchemeStr, String domainName) { + log.trace("Executing getOAuth2Clients [{}://{}]", domainSchemeStr, domainName); + if (domainSchemeStr == null) { + throw new IncorrectParameterException(INCORRECT_DOMAIN_SCHEME); + } + SchemeType domainScheme; + try { + domainScheme = SchemeType.valueOf(domainSchemeStr.toUpperCase()); + } catch (IllegalArgumentException e){ + throw new IncorrectParameterException(INCORRECT_DOMAIN_SCHEME); + } validateString(domainName, INCORRECT_DOMAIN_NAME + domainName); - return clientRegistrationDao.findByDomainName(domainName).stream() - .filter(OAuth2ClientRegistration::isEnabled) + return clientRegistrationInfoDao.findByDomainSchemesAndDomainName(Arrays.asList(domainScheme, SchemeType.MIXED), domainName).stream() + .filter(OAuth2ClientRegistrationInfo::isEnabled) .map(OAuth2Utils::toClientInfo) .collect(Collectors.toList()); } @Override @Transactional - public OAuth2ClientsParams saveOAuth2Params(OAuth2ClientsParams oauth2Params) { + public void saveOAuth2Params(OAuth2ClientsParams oauth2Params) { log.trace("Executing saveOAuth2Params [{}]", oauth2Params); clientParamsValidator.accept(oauth2Params); - List inputClientRegistrations = OAuth2Utils.toClientRegistrations(oauth2Params); - List savedClientRegistrations = inputClientRegistrations.stream() - .map(clientRegistration -> clientRegistrationDao.save(TenantId.SYS_TENANT_ID, clientRegistration)) - .collect(Collectors.toList()); - return OAuth2Utils.toOAuth2Params(savedClientRegistrations); + clientRegistrationDao.deleteAll(); + clientRegistrationInfoDao.deleteAll(); + oauth2Params.getOAuth2DomainDtos().forEach(domainParams -> { + domainParams.getClientRegistrations().forEach(clientRegistrationDto -> { + OAuth2ClientRegistrationInfo oAuth2ClientRegistrationInfo = OAuth2Utils.toClientRegistrationInfo(oauth2Params.isEnabled(), clientRegistrationDto); + OAuth2ClientRegistrationInfo savedClientRegistrationInfo = clientRegistrationInfoDao.save(TenantId.SYS_TENANT_ID, oAuth2ClientRegistrationInfo); + domainParams.getDomainInfos().forEach(domainInfo -> { + OAuth2ClientRegistration oAuth2ClientRegistration = OAuth2Utils.toClientRegistration(savedClientRegistrationInfo.getId(), + domainInfo.getScheme(), domainInfo.getName()); + clientRegistrationDao.save(TenantId.SYS_TENANT_ID, oAuth2ClientRegistration); + }); + }); + }); } @Override public OAuth2ClientsParams findOAuth2Params() { log.trace("Executing findOAuth2Params"); - return OAuth2Utils.toOAuth2Params(clientRegistrationDao.findAll()); - } - - @Override - public OAuth2ClientRegistration findClientRegistration(UUID id) { - log.trace("Executing findClientRegistration [{}]", id); - validateId(id, INCORRECT_CLIENT_REGISTRATION_ID + id); - return clientRegistrationDao.findById(null, id); - } - - @Override - public List findAllClientRegistrations() { - log.trace("Executing findAllClientRegistrations"); - return clientRegistrationDao.findAll(); + List extendedInfos = clientRegistrationInfoDao.findAllExtended(); + return OAuth2Utils.toOAuth2Params(extendedInfos); } @Override - public void deleteClientRegistrationById(OAuth2ClientRegistrationId id) { - log.trace("Executing deleteClientRegistrationById [{}]", id); + public OAuth2ClientRegistrationInfo findClientRegistrationInfo(UUID id) { + log.trace("Executing findClientRegistrationInfo [{}]", id); validateId(id, INCORRECT_CLIENT_REGISTRATION_ID + id); - clientRegistrationDao.removeById(TenantId.SYS_TENANT_ID, id.getId()); + return clientRegistrationInfoDao.findById(null, id); } @Override - @Transactional - public void deleteClientRegistrationsByDomain(String domain) { - log.trace("Executing deleteClientRegistrationsByDomain [{}]", domain); - validateString(domain, INCORRECT_DOMAIN_NAME + domain); - clientRegistrationDao.removeByDomainName(domain); + public List findAllClientRegistrationInfos() { + log.trace("Executing findAllClientRegistrationInfos"); + return clientRegistrationInfoDao.findAll(); } private final Consumer clientParamsValidator = oauth2Params -> { if (oauth2Params == null - || oauth2Params.getOAuth2DomainDtos() == null - || oauth2Params.getOAuth2DomainDtos().isEmpty()) { + || oauth2Params.getOAuth2DomainDtos() == null) { throw new DataValidationException("Domain params should be specified!"); } for (OAuth2ClientsDomainParams domainParams : oauth2Params.getOAuth2DomainDtos()) { - if (StringUtils.isEmpty(domainParams.getDomainName())) { - throw new DataValidationException("Domain name should be specified!"); + if (domainParams.getDomainInfos() == null + || domainParams.getDomainInfos().isEmpty()) { + throw new DataValidationException("List of domain configuration should be specified!"); } - if (StringUtils.isEmpty(domainParams.getRedirectUriTemplate())) { - throw new DataValidationException("Redirect URI template should be specified!"); + for (DomainInfo domainInfo : domainParams.getDomainInfos()) { + if (StringUtils.isEmpty(domainInfo.getName())) { + throw new DataValidationException("Domain name should be specified!"); + } + if (StringUtils.isEmpty(domainInfo.getScheme())) { + throw new DataValidationException("Domain scheme should be specified!"); + } } if (domainParams.getClientRegistrations() == null || domainParams.getClientRegistrations().isEmpty()) { throw new DataValidationException("Client registrations should be specified!"); diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java index 5b3e6654c1..a96a0d36c8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java @@ -15,93 +15,88 @@ */ package org.thingsboard.server.dao.oauth2; +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId; import org.thingsboard.server.common.data.oauth2.*; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; +import java.util.*; public class OAuth2Utils { public static final String OAUTH2_AUTHORIZATION_PATH_TEMPLATE = "/oauth2/authorization/%s"; - public static OAuth2ClientInfo toClientInfo(OAuth2ClientRegistration clientRegistration) { + public static OAuth2ClientInfo toClientInfo(OAuth2ClientRegistrationInfo clientRegistrationInfo) { OAuth2ClientInfo client = new OAuth2ClientInfo(); - client.setName(clientRegistration.getLoginButtonLabel()); - client.setUrl(String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, clientRegistration.getUuidId().toString())); - client.setIcon(clientRegistration.getLoginButtonIcon()); + client.setName(clientRegistrationInfo.getLoginButtonLabel()); + client.setUrl(String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, clientRegistrationInfo.getUuidId().toString())); + client.setIcon(clientRegistrationInfo.getLoginButtonIcon()); return client; } - public static List toClientRegistrations(OAuth2ClientsParams oAuth2Params) { - return oAuth2Params.getOAuth2DomainDtos().stream() - .flatMap(domainParams -> domainParams.getClientRegistrations().stream() - .map(clientRegistrationDto -> OAuth2Utils.toClientRegistration(oAuth2Params.isEnabled(), - domainParams.getDomainName(), - domainParams.getRedirectUriTemplate(), - clientRegistrationDto) - )) - .collect(Collectors.toList()); - } - - public static OAuth2ClientsParams toOAuth2Params(List clientRegistrations) { - Map domainParamsMap = new HashMap<>(); - boolean enabled = true; - for (OAuth2ClientRegistration clientRegistration : clientRegistrations) { - enabled = clientRegistration.isEnabled(); - String domainName = clientRegistration.getDomainName(); - OAuth2ClientsDomainParams domainParams = domainParamsMap.computeIfAbsent(domainName, - key -> new OAuth2ClientsDomainParams(domainName, clientRegistration.getRedirectUriTemplate(), new ArrayList<>()) - ); - domainParams.getClientRegistrations() - .add(toClientRegistrationDto(clientRegistration)); + public static OAuth2ClientsParams toOAuth2Params(List extendedOAuth2ClientRegistrationInfos) { + Map> domainsByInfoId = new HashMap<>(); + Map infoById = new HashMap<>(); + for (ExtendedOAuth2ClientRegistrationInfo extendedClientRegistrationInfo : extendedOAuth2ClientRegistrationInfos) { + String domainName = extendedClientRegistrationInfo.getDomainName(); + SchemeType domainScheme = extendedClientRegistrationInfo.getDomainScheme(); + domainsByInfoId.computeIfAbsent(extendedClientRegistrationInfo.getId(), key -> new HashSet<>()) + .add(new DomainInfo(domainScheme, domainName)); + infoById.put(extendedClientRegistrationInfo.getId(), extendedClientRegistrationInfo); } - return new OAuth2ClientsParams(enabled, new ArrayList<>(domainParamsMap.values())); + Map, OAuth2ClientsDomainParams> domainParamsMap = new HashMap<>(); + domainsByInfoId.forEach((clientRegistrationInfoId, domainInfos) -> { + domainParamsMap.computeIfAbsent(domainInfos, + key -> new OAuth2ClientsDomainParams(key, new HashSet<>()) + ) + .getClientRegistrations() + .add(toClientRegistrationDto(infoById.get(clientRegistrationInfoId))); + }); + boolean enabled = extendedOAuth2ClientRegistrationInfos.stream() + .map(OAuth2ClientRegistrationInfo::isEnabled) + .findFirst().orElse(false); + return new OAuth2ClientsParams(enabled, new HashSet<>(domainParamsMap.values())); } - public static ClientRegistrationDto toClientRegistrationDto(OAuth2ClientRegistration oAuth2ClientRegistration) { + public static ClientRegistrationDto toClientRegistrationDto(OAuth2ClientRegistrationInfo oAuth2ClientRegistrationInfo) { return ClientRegistrationDto.builder() - .id(oAuth2ClientRegistration.getId()) - .createdTime(oAuth2ClientRegistration.getCreatedTime()) - .mapperConfig(oAuth2ClientRegistration.getMapperConfig()) - .clientId(oAuth2ClientRegistration.getClientId()) - .clientSecret(oAuth2ClientRegistration.getClientSecret()) - .authorizationUri(oAuth2ClientRegistration.getAuthorizationUri()) - .accessTokenUri(oAuth2ClientRegistration.getAccessTokenUri()) - .scope(oAuth2ClientRegistration.getScope()) - .userInfoUri(oAuth2ClientRegistration.getUserInfoUri()) - .userNameAttributeName(oAuth2ClientRegistration.getUserNameAttributeName()) - .jwkSetUri(oAuth2ClientRegistration.getJwkSetUri()) - .clientAuthenticationMethod(oAuth2ClientRegistration.getClientAuthenticationMethod()) - .loginButtonLabel(oAuth2ClientRegistration.getLoginButtonLabel()) - .loginButtonIcon(oAuth2ClientRegistration.getLoginButtonIcon()) - .additionalInfo(oAuth2ClientRegistration.getAdditionalInfo()) + .mapperConfig(oAuth2ClientRegistrationInfo.getMapperConfig()) + .clientId(oAuth2ClientRegistrationInfo.getClientId()) + .clientSecret(oAuth2ClientRegistrationInfo.getClientSecret()) + .authorizationUri(oAuth2ClientRegistrationInfo.getAuthorizationUri()) + .accessTokenUri(oAuth2ClientRegistrationInfo.getAccessTokenUri()) + .scope(oAuth2ClientRegistrationInfo.getScope()) + .userInfoUri(oAuth2ClientRegistrationInfo.getUserInfoUri()) + .userNameAttributeName(oAuth2ClientRegistrationInfo.getUserNameAttributeName()) + .jwkSetUri(oAuth2ClientRegistrationInfo.getJwkSetUri()) + .clientAuthenticationMethod(oAuth2ClientRegistrationInfo.getClientAuthenticationMethod()) + .loginButtonLabel(oAuth2ClientRegistrationInfo.getLoginButtonLabel()) + .loginButtonIcon(oAuth2ClientRegistrationInfo.getLoginButtonIcon()) + .additionalInfo(oAuth2ClientRegistrationInfo.getAdditionalInfo()) .build(); } - private static OAuth2ClientRegistration toClientRegistration(boolean enabled, String domainName, - String redirectUriTemplate, - ClientRegistrationDto clientRegistrationDto) { + public static OAuth2ClientRegistrationInfo toClientRegistrationInfo(boolean enabled, ClientRegistrationDto clientRegistrationDto) { + OAuth2ClientRegistrationInfo clientRegistrationInfo = new OAuth2ClientRegistrationInfo(); + clientRegistrationInfo.setEnabled(enabled); + clientRegistrationInfo.setMapperConfig(clientRegistrationDto.getMapperConfig()); + clientRegistrationInfo.setClientId(clientRegistrationDto.getClientId()); + clientRegistrationInfo.setClientSecret(clientRegistrationDto.getClientSecret()); + clientRegistrationInfo.setAuthorizationUri(clientRegistrationDto.getAuthorizationUri()); + clientRegistrationInfo.setAccessTokenUri(clientRegistrationDto.getAccessTokenUri()); + clientRegistrationInfo.setScope(clientRegistrationDto.getScope()); + clientRegistrationInfo.setUserInfoUri(clientRegistrationDto.getUserInfoUri()); + clientRegistrationInfo.setUserNameAttributeName(clientRegistrationDto.getUserNameAttributeName()); + clientRegistrationInfo.setJwkSetUri(clientRegistrationDto.getJwkSetUri()); + clientRegistrationInfo.setClientAuthenticationMethod(clientRegistrationDto.getClientAuthenticationMethod()); + clientRegistrationInfo.setLoginButtonLabel(clientRegistrationDto.getLoginButtonLabel()); + clientRegistrationInfo.setLoginButtonIcon(clientRegistrationDto.getLoginButtonIcon()); + clientRegistrationInfo.setAdditionalInfo(clientRegistrationDto.getAdditionalInfo()); + return clientRegistrationInfo; + } + + public static OAuth2ClientRegistration toClientRegistration(OAuth2ClientRegistrationInfoId clientRegistrationInfoId, SchemeType domainScheme, String domainName) { OAuth2ClientRegistration clientRegistration = new OAuth2ClientRegistration(); - clientRegistration.setId(clientRegistrationDto.getId()); - clientRegistration.setEnabled(enabled); - clientRegistration.setCreatedTime(clientRegistrationDto.getCreatedTime()); + clientRegistration.setClientRegistrationId(clientRegistrationInfoId); clientRegistration.setDomainName(domainName); - clientRegistration.setRedirectUriTemplate(redirectUriTemplate); - clientRegistration.setMapperConfig(clientRegistrationDto.getMapperConfig()); - clientRegistration.setClientId(clientRegistrationDto.getClientId()); - clientRegistration.setClientSecret(clientRegistrationDto.getClientSecret()); - clientRegistration.setAuthorizationUri(clientRegistrationDto.getAuthorizationUri()); - clientRegistration.setAccessTokenUri(clientRegistrationDto.getAccessTokenUri()); - clientRegistration.setScope(clientRegistrationDto.getScope()); - clientRegistration.setUserInfoUri(clientRegistrationDto.getUserInfoUri()); - clientRegistration.setUserNameAttributeName(clientRegistrationDto.getUserNameAttributeName()); - clientRegistration.setJwkSetUri(clientRegistrationDto.getJwkSetUri()); - clientRegistration.setClientAuthenticationMethod(clientRegistrationDto.getClientAuthenticationMethod()); - clientRegistration.setLoginButtonLabel(clientRegistrationDto.getLoginButtonLabel()); - clientRegistration.setLoginButtonIcon(clientRegistrationDto.getLoginButtonIcon()); - clientRegistration.setAdditionalInfo(clientRegistrationDto.getAdditionalInfo()); + clientRegistration.setDomainScheme(domainScheme); return clientRegistration; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java index c9ba51b813..1fd901b4a3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java @@ -16,18 +16,15 @@ package org.thingsboard.server.dao.sql.oauth2; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; -import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationEntity; import org.thingsboard.server.dao.oauth2.OAuth2ClientRegistrationDao; import org.thingsboard.server.dao.sql.JpaAbstractDao; -import java.util.ArrayList; -import java.util.List; import java.util.UUID; -import java.util.stream.Collectors; @Component @RequiredArgsConstructor @@ -45,23 +42,7 @@ public class JpaOAuth2ClientRegistrationDao extends JpaAbstractDao findAll() { - Iterable entities = repository.findAll(); - List result = new ArrayList<>(); - entities.forEach(entity -> { - result.add(DaoUtil.getData(entity)); - }); - return result; - } - - @Override - public List findByDomainName(String domainName) { - List entities = repository.findAllByDomainName(domainName); - return entities.stream().map(DaoUtil::getData).collect(Collectors.toList()); - } - - @Override - public int removeByDomainName(String domainName) { - return repository.deleteByDomainName(domainName); + public void deleteAll() { + repository.deleteAll(); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationInfoDao.java new file mode 100644 index 0000000000..ec8651bc3a --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationInfoDao.java @@ -0,0 +1,76 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.oauth2; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.oauth2.ExtendedOAuth2ClientRegistrationInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; +import org.thingsboard.server.common.data.oauth2.SchemeType; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationInfoEntity; +import org.thingsboard.server.dao.oauth2.OAuth2ClientRegistrationInfoDao; +import org.thingsboard.server.dao.sql.JpaAbstractDao; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Component +@RequiredArgsConstructor +public class JpaOAuth2ClientRegistrationInfoDao extends JpaAbstractDao implements OAuth2ClientRegistrationInfoDao { + private final OAuth2ClientRegistrationInfoRepository repository; + + @Override + protected Class getEntityClass() { + return OAuth2ClientRegistrationInfoEntity.class; + } + + @Override + protected CrudRepository getCrudRepository() { + return repository; + } + + @Override + public List findAll() { + Iterable entities = repository.findAll(); + List result = new ArrayList<>(); + entities.forEach(entity -> { + result.add(DaoUtil.getData(entity)); + }); + return result; + } + + @Override + public List findAllExtended() { + return repository.findAllExtended().stream() + .map(DaoUtil::getData) + .collect(Collectors.toList()); + } + + @Override + public List findByDomainSchemesAndDomainName(List domainSchemes, String domainName) { + List entities = repository.findAllByDomainSchemesAndName(domainSchemes, domainName); + return entities.stream().map(DaoUtil::getData).collect(Collectors.toList()); + } + + @Override + public void deleteAll() { + repository.deleteAll(); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationInfoRepository.java new file mode 100644 index 0000000000..96094bd2fe --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationInfoRepository.java @@ -0,0 +1,41 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.oauth2; + +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; +import org.thingsboard.server.common.data.oauth2.SchemeType; +import org.thingsboard.server.dao.model.sql.ExtendedOAuth2ClientRegistrationInfoEntity; +import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationInfoEntity; + +import java.util.List; +import java.util.UUID; + +public interface OAuth2ClientRegistrationInfoRepository extends CrudRepository { + @Query("SELECT new OAuth2ClientRegistrationInfoEntity(cr_info) " + + "FROM OAuth2ClientRegistrationInfoEntity cr_info " + + "LEFT JOIN OAuth2ClientRegistrationEntity cr on cr_info.id = cr.clientRegistrationInfoId " + + "WHERE cr.domainName = :domainName " + + "AND cr.domainScheme IN (:domainSchemes)") + List findAllByDomainSchemesAndName(@Param("domainSchemes") List domainSchemes, + @Param("domainName") String domainName); + + @Query("SELECT new org.thingsboard.server.dao.model.sql.ExtendedOAuth2ClientRegistrationInfoEntity(cr_info, cr.domainName, cr.domainScheme) " + + "FROM OAuth2ClientRegistrationInfoEntity cr_info " + + "LEFT JOIN OAuth2ClientRegistrationEntity cr on cr_info.id = cr.clientRegistrationInfoId ") + List findAllExtended(); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationRepository.java index 5c869ddc30..438f73c7d1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationRepository.java @@ -18,11 +18,7 @@ package org.thingsboard.server.dao.sql.oauth2; import org.springframework.data.repository.CrudRepository; import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationEntity; -import java.util.List; import java.util.UUID; public interface OAuth2ClientRegistrationRepository extends CrudRepository { - List findAllByDomainName(String domainName); - - int deleteByDomainName(String domainName); } diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index a78d619361..7e93b47188 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -291,17 +291,15 @@ CREATE TABLE IF NOT EXISTS ts_kv_dictionary ( ); -CREATE TABLE IF NOT EXISTS oauth2_client_registration ( - id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, +CREATE TABLE IF NOT EXISTS oauth2_client_registration_info ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_info_pkey PRIMARY KEY, enabled boolean, created_time bigint NOT NULL, additional_info varchar, - domain_name varchar(255), client_id varchar(255), client_secret varchar(255), authorization_uri varchar(255), token_uri varchar(255), - redirect_uri_template varchar(255), scope varchar(255), user_info_uri varchar(255), user_name_attribute_name varchar(255), @@ -326,6 +324,14 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration ( custom_send_token boolean ); +CREATE TABLE IF NOT EXISTS oauth2_client_registration ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, + created_time bigint NOT NULL, + domain_name varchar(255), + domain_scheme varchar(31), + client_registration_info_id uuid +); + CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( id uuid NOT NULL CONSTRAINT oauth2_client_registration_template_pkey PRIMARY KEY, created_time bigint NOT NULL, diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 3a53f4166e..e74513b897 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -316,17 +316,15 @@ CREATE TABLE IF NOT EXISTS ts_kv_dictionary CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) ); -CREATE TABLE IF NOT EXISTS oauth2_client_registration ( - id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, +CREATE TABLE IF NOT EXISTS oauth2_client_registration_info ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_info_pkey PRIMARY KEY, enabled boolean, created_time bigint NOT NULL, additional_info varchar, - domain_name varchar(255), client_id varchar(255), client_secret varchar(255), authorization_uri varchar(255), token_uri varchar(255), - redirect_uri_template varchar(255), scope varchar(255), user_info_uri varchar(255), user_name_attribute_name varchar(255), @@ -351,6 +349,14 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration ( custom_send_token boolean ); +CREATE TABLE IF NOT EXISTS oauth2_client_registration ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, + created_time bigint NOT NULL, + domain_name varchar(255), + domain_scheme varchar(31), + client_registration_info_id uuid +); + CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( id uuid NOT NULL CONSTRAINT oauth2_client_registration_template_pkey PRIMARY KEY, created_time bigint NOT NULL, diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java index c96ba1f99d..fd5b5752cb 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.service; +import com.google.common.collect.Sets; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -22,170 +23,413 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.oauth2.*; import org.thingsboard.server.dao.oauth2.OAuth2Service; -import org.thingsboard.server.dao.oauth2.OAuth2Utils; import java.util.*; import java.util.stream.Collectors; -import static org.thingsboard.server.dao.oauth2.OAuth2Utils.toClientRegistrations; - public class BaseOAuth2ServiceTest extends AbstractServiceTest { + private static final OAuth2ClientsParams EMPTY_PARAMS = new OAuth2ClientsParams(false, new HashSet<>()); @Autowired protected OAuth2Service oAuth2Service; @Before public void beforeRun() { - Assert.assertTrue(oAuth2Service.findAllClientRegistrations().isEmpty()); + Assert.assertTrue(oAuth2Service.findAllClientRegistrationInfos().isEmpty()); } @After public void after() { - oAuth2Service.findAllClientRegistrations().forEach(clientRegistration -> { - oAuth2Service.deleteClientRegistrationById(clientRegistration.getId()); - }); - Assert.assertTrue(oAuth2Service.findAllClientRegistrations().isEmpty()); + oAuth2Service.saveOAuth2Params(EMPTY_PARAMS); + Assert.assertTrue(oAuth2Service.findAllClientRegistrationInfos().isEmpty()); + Assert.assertTrue(oAuth2Service.findOAuth2Params().getOAuth2DomainDtos().isEmpty()); + } + + @Test + public void testCreateAndFindParams() { + OAuth2ClientsParams clientsParams = createDefaultClientsParams(); + oAuth2Service.saveOAuth2Params(clientsParams); + OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params(); + Assert.assertNotNull(foundClientsParams); + // TODO ask if it's safe to check equality on AdditionalProperties + Assert.assertEquals(clientsParams, foundClientsParams); } @Test - public void testCreateNewParams() { - OAuth2ClientRegistration clientRegistration = validClientRegistration("domain-name"); - OAuth2ClientsParams savedOAuth2Params = oAuth2Service.saveOAuth2Params(OAuth2Utils.toOAuth2Params(Collections.singletonList(clientRegistration))); - Assert.assertNotNull(savedOAuth2Params); + public void testDisableParams() { + OAuth2ClientsParams clientsParams = createDefaultClientsParams(); + clientsParams.setEnabled(true); + oAuth2Service.saveOAuth2Params(clientsParams); + OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params(); + Assert.assertNotNull(foundClientsParams); + Assert.assertEquals(clientsParams, foundClientsParams); - List savedClientRegistrations = OAuth2Utils.toClientRegistrations(savedOAuth2Params); - Assert.assertEquals(1, savedClientRegistrations.size()); + clientsParams.setEnabled(false); + oAuth2Service.saveOAuth2Params(clientsParams); + OAuth2ClientsParams foundDisabledClientsParams = oAuth2Service.findOAuth2Params(); + Assert.assertEquals(clientsParams, foundDisabledClientsParams); + } - OAuth2ClientRegistration savedClientRegistration = savedClientRegistrations.get(0); - Assert.assertNotNull(savedClientRegistration.getId()); - clientRegistration.setId(savedClientRegistration.getId()); - clientRegistration.setCreatedTime(savedClientRegistration.getCreatedTime()); - Assert.assertEquals(clientRegistration, savedClientRegistration); + @Test + public void testClearDomainParams() { + OAuth2ClientsParams clientsParams = createDefaultClientsParams(); + oAuth2Service.saveOAuth2Params(clientsParams); + OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params(); + Assert.assertNotNull(foundClientsParams); + Assert.assertEquals(clientsParams, foundClientsParams); - oAuth2Service.deleteClientRegistrationsByDomain("domain-name"); + oAuth2Service.saveOAuth2Params(EMPTY_PARAMS); + OAuth2ClientsParams foundAfterClearClientsParams = oAuth2Service.findOAuth2Params(); + Assert.assertNotNull(foundAfterClearClientsParams); + Assert.assertEquals(EMPTY_PARAMS, foundAfterClearClientsParams); } @Test - public void testFindDomainParams() { - OAuth2ClientRegistration clientRegistration = validClientRegistration(); - oAuth2Service.saveOAuth2Params(OAuth2Utils.toOAuth2Params(Collections.singletonList(clientRegistration))); - - OAuth2ClientsParams foundOAuth2Params = oAuth2Service.findOAuth2Params(); - Assert.assertEquals(1, foundOAuth2Params.getOAuth2DomainDtos().size()); - Assert.assertEquals(1, oAuth2Service.findAllClientRegistrations().size()); - - List foundClientRegistrations = OAuth2Utils.toClientRegistrations(foundOAuth2Params); - OAuth2ClientRegistration foundClientRegistration = foundClientRegistrations.get(0); - Assert.assertNotNull(foundClientRegistration); - clientRegistration.setId(foundClientRegistration.getId()); - clientRegistration.setCreatedTime(foundClientRegistration.getCreatedTime()); - Assert.assertEquals(clientRegistration, foundClientRegistration); + public void testUpdateClientsParams() { + OAuth2ClientsParams clientsParams = createDefaultClientsParams(); + oAuth2Service.saveOAuth2Params(clientsParams); + OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params(); + Assert.assertNotNull(foundClientsParams); + Assert.assertEquals(clientsParams, foundClientsParams); + + OAuth2ClientsParams newClientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("another-domain").scheme(SchemeType.HTTPS).build() + )) + .clientRegistrations(Sets.newHashSet( + validClientRegistrationDto() + )) + .build(), + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("test-domain").scheme(SchemeType.MIXED).build() + )) + .clientRegistrations(Sets.newHashSet( + validClientRegistrationDto() + )) + .build() + )); + oAuth2Service.saveOAuth2Params(newClientsParams); + OAuth2ClientsParams foundAfterUpdateClientsParams = oAuth2Service.findOAuth2Params(); + Assert.assertNotNull(foundAfterUpdateClientsParams); + Assert.assertEquals(newClientsParams, foundAfterUpdateClientsParams); } @Test public void testGetOAuth2Clients() { - String testDomainName = "test_domain"; - OAuth2ClientRegistration first = validClientRegistration(testDomainName); - first.setEnabled(true); - OAuth2ClientRegistration second = validClientRegistration(testDomainName); - second.setEnabled(true); + Set firstGroup = Sets.newHashSet( + validClientRegistrationDto(), + validClientRegistrationDto(), + validClientRegistrationDto(), + validClientRegistrationDto() + ); + Set secondGroup = Sets.newHashSet( + validClientRegistrationDto(), + validClientRegistrationDto() + ); + Set thirdGroup = Sets.newHashSet( + validClientRegistrationDto() + ); + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), + DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), + DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() + )) + .clientRegistrations(firstGroup) + .build(), + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), + DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() + )) + .clientRegistrations(secondGroup) + .build(), + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), + DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() + )) + .clientRegistrations(thirdGroup) + .build() + )); + + oAuth2Service.saveOAuth2Params(clientsParams); + OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params(); + Assert.assertNotNull(foundClientsParams); + Assert.assertEquals(clientsParams, foundClientsParams); + + List firstGroupClientInfos = firstGroup.stream() + .map(clientRegistrationDto -> new OAuth2ClientInfo( + clientRegistrationDto.getLoginButtonLabel(), clientRegistrationDto.getLoginButtonIcon(), null)) + .collect(Collectors.toList()); + List secondGroupClientInfos = secondGroup.stream() + .map(clientRegistrationDto -> new OAuth2ClientInfo( + clientRegistrationDto.getLoginButtonLabel(), clientRegistrationDto.getLoginButtonIcon(), null)) + .collect(Collectors.toList()); + List thirdGroupClientInfos = thirdGroup.stream() + .map(clientRegistrationDto -> new OAuth2ClientInfo( + clientRegistrationDto.getLoginButtonLabel(), clientRegistrationDto.getLoginButtonIcon(), null)) + .collect(Collectors.toList()); - oAuth2Service.saveOAuth2Params(OAuth2Utils.toOAuth2Params(Collections.singletonList(first))); - oAuth2Service.saveOAuth2Params(OAuth2Utils.toOAuth2Params(Collections.singletonList(second))); + List nonExistentDomainClients = oAuth2Service.getOAuth2Clients("http", "non-existent-domain"); + Assert.assertTrue(nonExistentDomainClients.isEmpty()); - List oAuth2Clients = oAuth2Service.getOAuth2Clients(testDomainName); + List firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain"); + Assert.assertEquals(firstDomainHttpClients.size(), firstDomainHttpClients.size()); + firstGroupClientInfos.forEach(firstGroupClientInfo -> { + Assert.assertTrue( + firstDomainHttpClients.stream().anyMatch(clientInfo -> + clientInfo.getIcon().equals(firstGroupClientInfo.getIcon()) + && clientInfo.getName().equals(firstGroupClientInfo.getName())) + ); + }); - Set actualLabels = new HashSet<>(Arrays.asList(first.getLoginButtonLabel(), - second.getLoginButtonLabel())); + List firstDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "first-domain"); + Assert.assertTrue(firstDomainHttpsClients.isEmpty()); - Set foundLabels = oAuth2Clients.stream().map(OAuth2ClientInfo::getName).collect(Collectors.toSet()); - Assert.assertEquals(actualLabels, foundLabels); + List fourthDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "fourth-domain"); + Assert.assertEquals(fourthDomainHttpClients.size(), secondGroupClientInfos.size()); + secondGroupClientInfos.forEach(secondGroupClientInfo -> { + Assert.assertTrue( + fourthDomainHttpClients.stream().anyMatch(clientInfo -> + clientInfo.getIcon().equals(secondGroupClientInfo.getIcon()) + && clientInfo.getName().equals(secondGroupClientInfo.getName())) + ); + }); + List fourthDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "fourth-domain"); + Assert.assertEquals(fourthDomainHttpsClients.size(), secondGroupClientInfos.size()); + secondGroupClientInfos.forEach(secondGroupClientInfo -> { + Assert.assertTrue( + fourthDomainHttpsClients.stream().anyMatch(clientInfo -> + clientInfo.getIcon().equals(secondGroupClientInfo.getIcon()) + && clientInfo.getName().equals(secondGroupClientInfo.getName())) + ); + }); + + List secondDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "second-domain"); + Assert.assertEquals(secondDomainHttpClients.size(), firstGroupClientInfos.size() + secondGroupClientInfos.size()); + firstGroupClientInfos.forEach(firstGroupClientInfo -> { + Assert.assertTrue( + secondDomainHttpClients.stream().anyMatch(clientInfo -> + clientInfo.getIcon().equals(firstGroupClientInfo.getIcon()) + && clientInfo.getName().equals(firstGroupClientInfo.getName())) + ); + }); + secondGroupClientInfos.forEach(secondGroupClientInfo -> { + Assert.assertTrue( + secondDomainHttpClients.stream().anyMatch(clientInfo -> + clientInfo.getIcon().equals(secondGroupClientInfo.getIcon()) + && clientInfo.getName().equals(secondGroupClientInfo.getName())) + ); + }); + + List secondDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "second-domain"); + Assert.assertEquals(secondDomainHttpsClients.size(), firstGroupClientInfos.size() + thirdGroupClientInfos.size()); + firstGroupClientInfos.forEach(firstGroupClientInfo -> { + Assert.assertTrue( + secondDomainHttpsClients.stream().anyMatch(clientInfo -> + clientInfo.getIcon().equals(firstGroupClientInfo.getIcon()) + && clientInfo.getName().equals(firstGroupClientInfo.getName())) + ); + }); + thirdGroupClientInfos.forEach(thirdGroupClientInfo -> { + Assert.assertTrue( + secondDomainHttpsClients.stream().anyMatch(clientInfo -> + clientInfo.getIcon().equals(thirdGroupClientInfo.getIcon()) + && clientInfo.getName().equals(thirdGroupClientInfo.getName())) + ); + }); } @Test - public void testGetEmptyOAuth2Clients() { - String testDomainName = "test_domain"; - OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(testDomainName); - OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(testDomainName); - oAuth2Service.saveOAuth2Params(OAuth2Utils.toOAuth2Params(Collections.singletonList(tenantClientRegistration))); - oAuth2Service.saveOAuth2Params(OAuth2Utils.toOAuth2Params(Collections.singletonList(sysAdminClientRegistration))); - List oAuth2Clients = oAuth2Service.getOAuth2Clients("random-domain"); - Assert.assertTrue(oAuth2Clients.isEmpty()); + public void testGetDisabledOAuth2Clients() { + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), + DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), + DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() + )) + .clientRegistrations(Sets.newHashSet( + validClientRegistrationDto(), + validClientRegistrationDto(), + validClientRegistrationDto() + )) + .build(), + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), + DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() + )) + .clientRegistrations(Sets.newHashSet( + validClientRegistrationDto(), + validClientRegistrationDto() + )) + .build() + )); + + oAuth2Service.saveOAuth2Params(clientsParams); + + List secondDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "second-domain"); + Assert.assertEquals(5, secondDomainHttpClients.size()); + + clientsParams.setEnabled(false); + oAuth2Service.saveOAuth2Params(clientsParams); + + List secondDomainHttpDisabledClients = oAuth2Service.getOAuth2Clients("http", "second-domain"); + Assert.assertEquals(0, secondDomainHttpDisabledClients.size()); } @Test - public void testDeleteOAuth2ClientRegistration() { - OAuth2ClientRegistration first = validClientRegistration(); - OAuth2ClientRegistration second = validClientRegistration(); - - OAuth2ClientsParams savedFirstOAuth2Params = oAuth2Service.saveOAuth2Params( - OAuth2Utils.toOAuth2Params(Collections.singletonList(first))); - OAuth2ClientsParams savedSecondOAuth2Params = oAuth2Service.saveOAuth2Params( - OAuth2Utils.toOAuth2Params(Collections.singletonList(second))); - - OAuth2ClientRegistration savedFirstRegistration = toClientRegistrations(savedFirstOAuth2Params).get(0); - OAuth2ClientRegistration savedSecondRegistration = toClientRegistrations(savedSecondOAuth2Params).get(0); - - oAuth2Service.deleteClientRegistrationById(savedFirstRegistration.getId()); - List foundRegistrations = oAuth2Service.findAllClientRegistrations(); - Assert.assertEquals(1, foundRegistrations.size()); - Assert.assertEquals(savedSecondRegistration, foundRegistrations.get(0)); + public void testFindAllClientRegistrationInfos() { + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), + DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), + DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() + )) + .clientRegistrations(Sets.newHashSet( + validClientRegistrationDto(), + validClientRegistrationDto(), + validClientRegistrationDto() + )) + .build(), + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), + DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() + )) + .clientRegistrations(Sets.newHashSet( + validClientRegistrationDto(), + validClientRegistrationDto() + )) + .build(), + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), + DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() + )) + .clientRegistrations(Sets.newHashSet( + validClientRegistrationDto() + )) + .build() + )); + + oAuth2Service.saveOAuth2Params(clientsParams); + List foundClientRegistrationInfos = oAuth2Service.findAllClientRegistrationInfos(); + Assert.assertEquals(6, foundClientRegistrationInfos.size()); + clientsParams.getOAuth2DomainDtos().stream() + .flatMap(domainParams -> domainParams.getClientRegistrations().stream()) + .forEach(clientRegistrationDto -> + Assert.assertTrue( + foundClientRegistrationInfos.stream() + .anyMatch(clientRegistrationInfo -> clientRegistrationInfo.getClientId().equals(clientRegistrationDto.getClientId())) + ) + ); } @Test - public void testDeleteDomainOAuth2ClientRegistrations() { - oAuth2Service.saveOAuth2Params(OAuth2Utils.toOAuth2Params(Arrays.asList( - validClientRegistration("domain1"), - validClientRegistration("domain1"), - validClientRegistration("domain2") - ))); - oAuth2Service.saveOAuth2Params(OAuth2Utils.toOAuth2Params(Arrays.asList( - validClientRegistration("domain2") - ))); - Assert.assertEquals(4, oAuth2Service.findAllClientRegistrations().size()); - OAuth2ClientsParams oAuth2Params = oAuth2Service.findOAuth2Params(); - List clientRegistrations = toClientRegistrations(oAuth2Params); - Assert.assertEquals(2, oAuth2Params.getOAuth2DomainDtos().size()); - Assert.assertEquals(4, clientRegistrations.size()); - - oAuth2Service.deleteClientRegistrationsByDomain("domain1"); - Assert.assertEquals(2, oAuth2Service.findAllClientRegistrations().size()); - Assert.assertEquals(1, oAuth2Service.findOAuth2Params().getOAuth2DomainDtos().size()); - Assert.assertEquals(2, toClientRegistrations(oAuth2Service.findOAuth2Params()).size()); - } + public void testFindClientRegistrationById() { + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), + DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), + DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() + )) + .clientRegistrations(Sets.newHashSet( + validClientRegistrationDto(), + validClientRegistrationDto(), + validClientRegistrationDto() + )) + .build(), + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), + DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() + )) + .clientRegistrations(Sets.newHashSet( + validClientRegistrationDto(), + validClientRegistrationDto() + )) + .build(), + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), + DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() + )) + .clientRegistrations(Sets.newHashSet( + validClientRegistrationDto() + )) + .build() + )); - private OAuth2ClientRegistration validClientRegistration() { - return validClientRegistration(UUID.randomUUID().toString()); + oAuth2Service.saveOAuth2Params(clientsParams); + List clientRegistrationInfos = oAuth2Service.findAllClientRegistrationInfos(); + clientRegistrationInfos.forEach(clientRegistrationInfo -> { + OAuth2ClientRegistrationInfo foundClientRegistrationInfo = oAuth2Service.findClientRegistrationInfo(clientRegistrationInfo.getUuidId()); + Assert.assertEquals(clientRegistrationInfo, foundClientRegistrationInfo); + }); } - private OAuth2ClientRegistration validClientRegistration(String domainName) { - OAuth2ClientRegistration clientRegistration = new OAuth2ClientRegistration(); - clientRegistration.setDomainName(domainName); - clientRegistration.setMapperConfig( - OAuth2MapperConfig.builder() - .allowUserCreation(true) - .activateUser(true) - .type(MapperType.CUSTOM) - .custom( - OAuth2CustomMapperConfig.builder() - .url("UUID.randomUUID().toString()") - .build() - ) + private OAuth2ClientsParams createDefaultClientsParams() { + return new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), + DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), + DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() + )) + .clientRegistrations(Sets.newHashSet( + validClientRegistrationDto(), + validClientRegistrationDto(), + validClientRegistrationDto(), + validClientRegistrationDto() + )) + .build(), + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), + DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() + )) + .clientRegistrations(Sets.newHashSet( + validClientRegistrationDto(), + validClientRegistrationDto() + )) .build() - ); - clientRegistration.setClientId(UUID.randomUUID().toString()); - clientRegistration.setClientSecret(UUID.randomUUID().toString()); - clientRegistration.setAuthorizationUri(UUID.randomUUID().toString()); - clientRegistration.setAccessTokenUri(UUID.randomUUID().toString()); - clientRegistration.setRedirectUriTemplate(UUID.randomUUID().toString()); - clientRegistration.setScope(Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString())); - clientRegistration.setUserInfoUri(UUID.randomUUID().toString()); - clientRegistration.setUserNameAttributeName(UUID.randomUUID().toString()); - clientRegistration.setJwkSetUri(UUID.randomUUID().toString()); - clientRegistration.setClientAuthenticationMethod(UUID.randomUUID().toString()); - clientRegistration.setLoginButtonLabel(UUID.randomUUID().toString()); - clientRegistration.setLoginButtonIcon(UUID.randomUUID().toString()); - clientRegistration.setAdditionalInfo(mapper.createObjectNode().put(UUID.randomUUID().toString(), UUID.randomUUID().toString())); - return clientRegistration; + )); + } + + private ClientRegistrationDto validClientRegistrationDto() { + return ClientRegistrationDto.builder() + .clientId(UUID.randomUUID().toString()) + .clientSecret(UUID.randomUUID().toString()) + .authorizationUri(UUID.randomUUID().toString()) + .accessTokenUri(UUID.randomUUID().toString()) + .scope(Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString())) + .userInfoUri(UUID.randomUUID().toString()) + .userNameAttributeName(UUID.randomUUID().toString()) + .jwkSetUri(UUID.randomUUID().toString()) + .clientAuthenticationMethod(UUID.randomUUID().toString()) + .loginButtonLabel(UUID.randomUUID().toString()) + .loginButtonIcon(UUID.randomUUID().toString()) + .additionalInfo(mapper.createObjectNode().put(UUID.randomUUID().toString(), UUID.randomUUID().toString())) + .mapperConfig( + OAuth2MapperConfig.builder() + .allowUserCreation(true) + .activateUser(true) + .type(MapperType.CUSTOM) + .custom( + OAuth2CustomMapperConfig.builder() + .url(UUID.randomUUID().toString()) + .build() + ) + .build() + ) + .build(); } }