Commit 5a00973b47eb2d807a014d8fd0f279e6e37814a2

Authored by viktor
1 parent c3c889bb

Implemented part of OAuth2Service

... ... @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.id.EntityId;
20 20 import org.thingsboard.server.common.data.id.TenantId;
21 21 import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
22 22 import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration;
  23 +import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams;
23 24
24 25 import java.util.List;
25 26
... ... @@ -28,13 +29,13 @@ public interface OAuth2Service {
28 29
29 30 List<OAuth2ClientInfo> getOAuth2Clients(String domainName);
30 31
31   - List<OAuth2ClientRegistration> getSystemOAuth2ClientRegistrations(TenantId tenantId);
  32 + OAuth2ClientsParams saveSystemOAuth2ClientsParams(OAuth2ClientsParams oAuth2ClientsParams);
32 33
33   - List<OAuth2ClientRegistration> getTenantOAuth2ClientRegistrations(TenantId tenantId);
  34 + OAuth2ClientsParams saveTenantOAuth2ClientsParams(TenantId tenantId, OAuth2ClientsParams oAuth2ClientsParams);
34 35
35   - OAuth2ClientRegistration saveSystemOAuth2ClientRegistration(OAuth2ClientRegistration clientRegistration);
  36 + OAuth2ClientsParams getSystemOAuth2ClientsParams(TenantId tenantId);
36 37
37   - OAuth2ClientRegistration saveTenantOAuth2ClientRegistration(TenantId tenantId, String domainName, OAuth2ClientRegistration clientRegistration);
  38 + OAuth2ClientsParams getTenantOAuth2ClientsParams(TenantId tenantId);
38 39
39 40 void deleteDomainOAuth2ClientRegistrationByTenant(TenantId tenantId);
40 41
... ...
... ... @@ -16,24 +16,35 @@
16 16 package org.thingsboard.server.dao.oauth2;
17 17
18 18 import com.fasterxml.jackson.core.JsonProcessingException;
  19 +import com.fasterxml.jackson.databind.JsonNode;
19 20 import com.fasterxml.jackson.databind.ObjectMapper;
20 21 import com.fasterxml.jackson.databind.node.ObjectNode;
  22 +import com.google.common.util.concurrent.Futures;
  23 +import com.google.common.util.concurrent.ListenableFuture;
  24 +import com.google.common.util.concurrent.MoreExecutors;
21 25 import lombok.extern.slf4j.Slf4j;
22 26 import org.springframework.beans.factory.annotation.Autowired;
23 27 import org.springframework.stereotype.Service;
24 28 import org.springframework.util.StringUtils;
25   -import org.thingsboard.server.common.data.AdminSettings;
  29 +import org.thingsboard.server.common.data.*;
26 30 import org.thingsboard.server.common.data.id.AdminSettingsId;
27 31 import org.thingsboard.server.common.data.id.CustomerId;
28 32 import org.thingsboard.server.common.data.id.EntityId;
29 33 import org.thingsboard.server.common.data.id.TenantId;
  34 +import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  35 +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
  36 +import org.thingsboard.server.common.data.kv.StringDataEntry;
30 37 import org.thingsboard.server.common.data.oauth2.*;
  38 +import org.thingsboard.server.dao.attributes.AttributesService;
  39 +import org.thingsboard.server.dao.exception.DataValidationException;
31 40 import org.thingsboard.server.dao.exception.IncorrectParameterException;
32 41 import org.thingsboard.server.dao.settings.AdminSettingsService;
  42 +import org.thingsboard.server.dao.tenant.TenantService;
33 43
34   -import java.util.Collections;
35   -import java.util.List;
36   -import java.util.UUID;
  44 +import java.io.IOException;
  45 +import java.util.*;
  46 +import java.util.concurrent.ExecutionException;
  47 +import java.util.function.Consumer;
37 48
38 49 @Slf4j
39 50 @Service
... ... @@ -44,65 +55,158 @@ public class OAuth2ServiceImpl implements OAuth2Service {
44 55 private static final String OAUTH2_CLIENT_REGISTRATIONS_PARAMS = "oauth2ClientRegistrationsParams";
45 56 private static final String OAUTH2_CLIENT_REGISTRATIONS_DOMAIN_NAME_PREFIX = "oauth2ClientRegistrationsDomainNamePrefix";
46 57
  58 + private static final String ALLOW_OAUTH2_CONFIGURATION = "allowOAuth2Configuration";
  59 +
  60 +
  61 + private static final String SYSTEM_SETTINGS_OAUTH2_VALUE = "value";
  62 +
47 63 private static final String OAUTH2_AUTHORIZATION_PATH_TEMPLATE = "/oauth2/authorization/%s";
48 64
49 65 @Autowired
50 66 private AdminSettingsService adminSettingsService;
51 67
52   - @Override
53   - public List<OAuth2ClientInfo> getOAuth2Clients(String domainName) {
54   - return Collections.emptyList();
55   - }
  68 + @Autowired
  69 + private AttributesService attributesService;
56 70
57   - @Override
58   - public List<OAuth2ClientRegistration> getSystemOAuth2ClientRegistrations(TenantId tenantId) {
59   - return null;
60   - }
  71 + @Autowired
  72 + private TenantService tenantService;
61 73
62 74 @Override
63   - public List<OAuth2ClientRegistration> getTenantOAuth2ClientRegistrations(TenantId tenantId) {
64   - return null;
  75 + public List<OAuth2ClientInfo> getOAuth2Clients(String domainName) {
  76 + return Collections.emptyList();
65 77 }
66 78
67 79 @Override
68   - public OAuth2ClientRegistration saveSystemOAuth2ClientRegistration(OAuth2ClientRegistration clientRegistration) {
  80 + public OAuth2ClientsParams saveSystemOAuth2ClientsParams(OAuth2ClientsParams oAuth2ClientsParams) {
69 81 // TODO check by registration ID in entities
70   - AdminSettings clientRegistrationParamsSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAUTH2_CLIENT_REGISTRATIONS_PARAMS);
71   - if (clientRegistrationParamsSettings == null) {
72   - clientRegistrationParamsSettings = new AdminSettings();
73   - clientRegistrationParamsSettings.setKey(OAUTH2_CLIENT_REGISTRATIONS_PARAMS);
74   - ObjectNode node = mapper.createObjectNode();
75   - clientRegistrationParamsSettings.setJsonValue(node);
  82 + for (OAuth2ClientRegistration clientRegistration : oAuth2ClientsParams.getClientRegistrations()) {
  83 + validator.accept(clientRegistration);
76 84 }
  85 + AdminSettings clientRegistrationParamsSettings = new AdminSettings();
  86 + clientRegistrationParamsSettings.setKey(OAUTH2_CLIENT_REGISTRATIONS_PARAMS);
  87 + ObjectNode clientRegistrationsNode = mapper.createObjectNode();
  88 +
  89 + oAuth2ClientsParams.setDomainName("");
77 90 String json;
78 91 try {
79   - json = mapper.writeValueAsString(clientRegistration);
  92 + json = mapper.writeValueAsString(oAuth2ClientsParams);
80 93 } catch (JsonProcessingException e) {
81 94 log.error("Unable to convert OAuth2 Client Registration Params to JSON!", e);
82 95 throw new IncorrectParameterException("Unable to convert OAuth2 Client Registration Params to JSON!");
83 96 }
84   - ObjectNode oldClientRegistrations = (ObjectNode) clientRegistrationParamsSettings.getJsonValue();
85   - oldClientRegistrations.put(clientRegistration.getRegistrationId(), json);
  97 + clientRegistrationsNode.put(SYSTEM_SETTINGS_OAUTH2_VALUE, json);
  98 +
  99 + clientRegistrationParamsSettings.setJsonValue(clientRegistrationsNode);
  100 +
86 101 adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, clientRegistrationParamsSettings);
87   - // TODO ask if that's worth it
88   - return getClientRegistration(clientRegistration.getRegistrationId());
  102 +
  103 + return getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID);
89 104 }
90 105
91 106 @Override
92   - public OAuth2ClientRegistration saveTenantOAuth2ClientRegistration(TenantId tenantId, String domainName, OAuth2ClientRegistration clientRegistration) {
  107 + public OAuth2ClientsParams saveTenantOAuth2ClientsParams(TenantId tenantId, OAuth2ClientsParams oAuth2ClientsParams) {
93 108 // TODO ask what if tenant saves config for several different domain names, do we need to check it
94 109 // TODO check by registration ID in system
95   - return null;
  110 + for (OAuth2ClientRegistration clientRegistration : oAuth2ClientsParams.getClientRegistrations()) {
  111 + validator.accept(clientRegistration);
  112 + }
  113 + String clientRegistrationsKey = constructClientRegistrationsKey(oAuth2ClientsParams.getDomainName());
  114 + AdminSettings existentAdminSettingsByKey = adminSettingsService.findAdminSettingsByKey(tenantId, clientRegistrationsKey);
  115 + if (StringUtils.isEmpty(oAuth2ClientsParams.getAdminSettingsId())) {
  116 + if (existentAdminSettingsByKey == null) {
  117 + existentAdminSettingsByKey = saveOAuth2ClientSettings(tenantId, clientRegistrationsKey);
  118 + oAuth2ClientsParams.setAdminSettingsId(existentAdminSettingsByKey.getId().getId().toString());
  119 + } else {
  120 + log.error("Current domain name [{}] already registered in the system!", oAuth2ClientsParams.getDomainName());
  121 + throw new IncorrectParameterException("Current domain name [" + oAuth2ClientsParams.getDomainName() + "] already registered in the system!");
  122 + }
  123 + } else {
  124 + AdminSettings existentOAuth2ClientsSettingsById = adminSettingsService.findAdminSettingsById(
  125 + tenantId,
  126 + new AdminSettingsId(UUID.fromString(oAuth2ClientsParams.getAdminSettingsId()))
  127 + );
  128 +
  129 + if (existentOAuth2ClientsSettingsById == null) {
  130 + log.error("Admin setting ID is already set in login white labeling object, but doesn't exist in the database");
  131 + throw new IllegalStateException("Admin setting ID is already set in login white labeling object, but doesn't exist in the database");
  132 + }
  133 +
  134 + if (!existentOAuth2ClientsSettingsById.getKey().equals(clientRegistrationsKey)) {
  135 + if (existentAdminSettingsByKey == null) {
  136 + adminSettingsService.deleteAdminSettingsByKey(tenantId, existentOAuth2ClientsSettingsById.getKey());
  137 + AdminSettings newOAuth2ClientsSettings = saveOAuth2ClientSettings(tenantId, clientRegistrationsKey);
  138 + oAuth2ClientsParams.setAdminSettingsId(newOAuth2ClientsSettings.getId().getId().toString());
  139 + } else {
  140 + log.error("Current domain name [{}] already registered in the system!", oAuth2ClientsParams.getDomainName());
  141 + throw new IncorrectParameterException("Current domain name [" + oAuth2ClientsParams.getDomainName() + "] already registered in the system!");
  142 + }
  143 + }
  144 + }
  145 + String json;
  146 + try {
  147 + json = mapper.writeValueAsString(oAuth2ClientsParams);
  148 + } catch (JsonProcessingException e) {
  149 + log.error("Unable to convert OAuth2 Client Registration Params to JSON!", e);
  150 + throw new IncorrectParameterException("Unable to convert OAuth2 Client Registration Params to JSON!");
  151 + }
  152 + List<AttributeKvEntry> attributes = new ArrayList<>();
  153 + long ts = System.currentTimeMillis();
  154 + attributes.add(new BaseAttributeKvEntry(new StringDataEntry(OAUTH2_CLIENT_REGISTRATIONS_PARAMS, json), ts));
  155 + try {
  156 + // TODO ask if I need here .get()
  157 + attributesService.save(tenantId, tenantId, DataConstants.SERVER_SCOPE, attributes).get();
  158 + } catch (Exception e) {
  159 + log.error("Unable to save OAuth2 Client Registration Params to attributes!", e);
  160 + throw new IncorrectParameterException("Unable to save OAuth2 Client Registration Params to attributes!");
  161 + }
  162 + return getTenantOAuth2ClientsParams(tenantId);
96 163 }
97 164
98 165 @Override
99   - public void deleteDomainOAuth2ClientRegistrationByTenant(TenantId tenantId) {
  166 + public OAuth2ClientsParams getSystemOAuth2ClientsParams(TenantId tenantId) {
  167 + AdminSettings oauth2ClientsParamsSettings = adminSettingsService.findAdminSettingsByKey(tenantId, OAUTH2_CLIENT_REGISTRATIONS_PARAMS);
  168 + String json = null;
  169 + if (oauth2ClientsParamsSettings != null) {
  170 + json = oauth2ClientsParamsSettings.getJsonValue().get(SYSTEM_SETTINGS_OAUTH2_VALUE).asText();
  171 + }
  172 + return constructOAuth2ClientsParams(json);
  173 + }
100 174
  175 + @Override
  176 + public OAuth2ClientsParams getTenantOAuth2ClientsParams(TenantId tenantId) {
  177 + ListenableFuture<String> jsonFuture;
  178 + if (isOAuth2ClientRegistrationAllowed(tenantId)) {
  179 + jsonFuture = getOAuth2ClientsParamsAttribute(tenantId);
  180 + } else {
  181 + jsonFuture = Futures.immediateFuture("");
  182 + }
  183 + try {
  184 + return Futures.transform(jsonFuture, this::constructOAuth2ClientsParams, MoreExecutors.directExecutor()).get();
  185 + } catch (InterruptedException | ExecutionException e) {
  186 + log.error("Failed to read OAuth2 Clients Params from attributes!", e);
  187 + throw new RuntimeException("Failed to read OAuth2 Clients Params from attributes!", e);
  188 + }
  189 + }
  190 +
  191 + @Override
  192 + public void deleteDomainOAuth2ClientRegistrationByTenant(TenantId tenantId) {
  193 + OAuth2ClientsParams params = getTenantOAuth2ClientsParams(tenantId);
  194 + if (!StringUtils.isEmpty(params.getDomainName())) {
  195 + // TODO don't we need to delete from attributes?
  196 + String oauth2ClientsParamsKey = constructClientRegistrationsKey(params.getDomainName());
  197 + adminSettingsService.deleteAdminSettingsByKey(tenantId, oauth2ClientsParamsKey);
  198 + }
101 199 }
102 200
103 201 @Override
104 202 public boolean isOAuth2ClientRegistrationAllowed(TenantId tenantId) {
105   - return false;
  203 + Tenant tenant = tenantService.findTenantById(tenantId);
  204 + JsonNode allowOAuth2ConfigurationJsonNode = tenant.getAdditionalInfo() != null ? tenant.getAdditionalInfo().get(ALLOW_OAUTH2_CONFIGURATION) : null;
  205 + if (allowOAuth2ConfigurationJsonNode == null) {
  206 + return true;
  207 + } else {
  208 + return allowOAuth2ConfigurationJsonNode.asBoolean();
  209 + }
106 210 }
107 211
108 212 @Override
... ... @@ -110,6 +214,35 @@ public class OAuth2ServiceImpl implements OAuth2Service {
110 214 return null;
111 215 }
112 216
  217 + private ListenableFuture<String> getOAuth2ClientsParamsAttribute(TenantId tenantId) {
  218 + ListenableFuture<List<AttributeKvEntry>> attributeKvEntriesFuture;
  219 + try {
  220 + attributeKvEntriesFuture = attributesService.find(tenantId, tenantId, DataConstants.SERVER_SCOPE,
  221 + Collections.singletonList(OAUTH2_CLIENT_REGISTRATIONS_PARAMS));
  222 + } catch (Exception e) {
  223 + log.error("Unable to read OAuth2 Clients Params from attributes!", e);
  224 + throw new IncorrectParameterException("Unable to read OAuth2 Clients Params from attributes!");
  225 + }
  226 + return Futures.transform(attributeKvEntriesFuture, attributeKvEntries -> {
  227 + if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) {
  228 + AttributeKvEntry kvEntry = attributeKvEntries.get(0);
  229 + return kvEntry.getValueAsString();
  230 + } else {
  231 + return "";
  232 + }
  233 + }, MoreExecutors.directExecutor());
  234 + }
  235 +
  236 + private AdminSettings saveOAuth2ClientSettings(TenantId tenantId, String clientRegistrationsKey) {
  237 + AdminSettings oauth2ClientsSettings = new AdminSettings();
  238 + oauth2ClientsSettings.setKey(clientRegistrationsKey);
  239 + ObjectNode node = mapper.createObjectNode();
  240 + node.put("entityType", EntityType.TENANT.name());
  241 + node.put("entityId", tenantId.toString());
  242 + oauth2ClientsSettings.setJsonValue(node);
  243 + return adminSettingsService.saveAdminSettings(tenantId, oauth2ClientsSettings);
  244 + }
  245 +
113 246 private String constructClientRegistrationsKey(String domainName) {
114 247 String clientRegistrationsKey;
115 248 if (StringUtils.isEmpty(domainName)) {
... ... @@ -119,4 +252,103 @@ public class OAuth2ServiceImpl implements OAuth2Service {
119 252 }
120 253 return clientRegistrationsKey;
121 254 }
  255 +
  256 + private OAuth2ClientsParams constructOAuth2ClientsParams(String json) {
  257 + OAuth2ClientsParams result = null;
  258 + if (!StringUtils.isEmpty(json)) {
  259 + try {
  260 + result = mapper.readValue(json, OAuth2ClientsParams.class);
  261 + } catch (IOException e) {
  262 + log.error("Unable to read OAuth2 Clients Params from JSON!", e);
  263 + throw new IncorrectParameterException("Unable to read OAuth2 Clients Params from JSON!");
  264 + }
  265 + }
  266 + if (result == null) {
  267 + result = new OAuth2ClientsParams();
  268 + }
  269 + return result;
  270 + }
  271 +
  272 + private final Consumer<OAuth2ClientRegistration> validator = clientRegistration -> {
  273 + if (StringUtils.isEmpty(clientRegistration.getRegistrationId())) {
  274 + throw new DataValidationException("Registration ID should be specified!");
  275 + }
  276 + if (StringUtils.isEmpty(clientRegistration.getClientId())) {
  277 + throw new DataValidationException("Client ID should be specified!");
  278 + }
  279 + if (StringUtils.isEmpty(clientRegistration.getClientSecret())) {
  280 + throw new DataValidationException("Client secret should be specified!");
  281 + }
  282 + if (StringUtils.isEmpty(clientRegistration.getAuthorizationUri())) {
  283 + throw new DataValidationException("Authorization uri should be specified!");
  284 + }
  285 + if (StringUtils.isEmpty(clientRegistration.getTokenUri())) {
  286 + throw new DataValidationException("Token uri should be specified!");
  287 + }
  288 + if (StringUtils.isEmpty(clientRegistration.getRedirectUriTemplate())) {
  289 + throw new DataValidationException("Redirect uri template should be specified!");
  290 + }
  291 + if (StringUtils.isEmpty(clientRegistration.getScope())) {
  292 + throw new DataValidationException("Scope should be specified!");
  293 + }
  294 + if (StringUtils.isEmpty(clientRegistration.getAuthorizationGrantType())) {
  295 + throw new DataValidationException("Authorization grant type should be specified!");
  296 + }
  297 + if (StringUtils.isEmpty(clientRegistration.getUserInfoUri())) {
  298 + throw new DataValidationException("User info uri should be specified!");
  299 + }
  300 + if (StringUtils.isEmpty(clientRegistration.getUserNameAttributeName())) {
  301 + throw new DataValidationException("User name attribute name should be specified!");
  302 + }
  303 + if (StringUtils.isEmpty(clientRegistration.getJwkSetUri())) {
  304 + throw new DataValidationException("Jwk set uri should be specified!");
  305 + }
  306 + if (StringUtils.isEmpty(clientRegistration.getClientAuthenticationMethod())) {
  307 + throw new DataValidationException("Client authentication method should be specified!");
  308 + }
  309 + if (StringUtils.isEmpty(clientRegistration.getClientName())) {
  310 + throw new DataValidationException("Client name should be specified!");
  311 + }
  312 + if (StringUtils.isEmpty(clientRegistration.getLoginButtonLabel())) {
  313 + throw new DataValidationException("Login button label should be specified!");
  314 + }
  315 + OAuth2MapperConfig mapperConfig = clientRegistration.getMapperConfig();
  316 + if (mapperConfig == null) {
  317 + throw new DataValidationException("Mapper config should be specified!");
  318 + }
  319 + if (mapperConfig.getType() == null) {
  320 + throw new DataValidationException("Mapper config type should be specified!");
  321 + }
  322 + if (mapperConfig.getType() == MapperType.BASIC) {
  323 + OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasicConfig();
  324 + if (basicConfig == null) {
  325 + throw new DataValidationException("Basic config should be specified!");
  326 + }
  327 + if (StringUtils.isEmpty(basicConfig.getEmailAttributeKey())) {
  328 + throw new DataValidationException("Email attribute key should be specified!");
  329 + }
  330 + if (basicConfig.getTenantNameStrategy() == null) {
  331 + throw new DataValidationException("Tenant name strategy should be specified!");
  332 + }
  333 + if (basicConfig.getTenantNameStrategy() == TenantNameStrategyType.CUSTOM
  334 + && StringUtils.isEmpty(basicConfig.getTenantNamePattern())) {
  335 + throw new DataValidationException("Tenant name pattern should be specified!");
  336 + }
  337 + }
  338 + if (mapperConfig.getType() == MapperType.CUSTOM) {
  339 + OAuth2CustomMapperConfig customConfig = mapperConfig.getCustomConfig();
  340 + if (customConfig == null) {
  341 + throw new DataValidationException("Custom config should be specified!");
  342 + }
  343 + if (StringUtils.isEmpty(customConfig.getUrl())) {
  344 + throw new DataValidationException("Custom mapper URL should be specified!");
  345 + }
  346 + if (StringUtils.isEmpty(customConfig.getUsername())) {
  347 + throw new DataValidationException("Custom mapper username should be specified!");
  348 + }
  349 + if (StringUtils.isEmpty(customConfig.getPassword())) {
  350 + throw new DataValidationException("Custom mapper password should be specified!");
  351 + }
  352 + }
  353 + };
122 354 }
... ...