|
|
|
@ -16,24 +16,35 @@ |
|
|
|
package org.thingsboard.server.dao.oauth2; |
|
|
|
|
|
|
|
import com.fasterxml.jackson.core.JsonProcessingException; |
|
|
|
import com.fasterxml.jackson.databind.JsonNode; |
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper; |
|
|
|
import com.fasterxml.jackson.databind.node.ObjectNode; |
|
|
|
import com.google.common.util.concurrent.Futures; |
|
|
|
import com.google.common.util.concurrent.ListenableFuture; |
|
|
|
import com.google.common.util.concurrent.MoreExecutors; |
|
|
|
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.AdminSettings; |
|
|
|
import org.thingsboard.server.common.data.*; |
|
|
|
import org.thingsboard.server.common.data.id.AdminSettingsId; |
|
|
|
import org.thingsboard.server.common.data.id.CustomerId; |
|
|
|
import org.thingsboard.server.common.data.id.EntityId; |
|
|
|
import org.thingsboard.server.common.data.id.TenantId; |
|
|
|
import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
|
|
|
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; |
|
|
|
import org.thingsboard.server.common.data.kv.StringDataEntry; |
|
|
|
import org.thingsboard.server.common.data.oauth2.*; |
|
|
|
import org.thingsboard.server.dao.attributes.AttributesService; |
|
|
|
import org.thingsboard.server.dao.exception.DataValidationException; |
|
|
|
import org.thingsboard.server.dao.exception.IncorrectParameterException; |
|
|
|
import org.thingsboard.server.dao.settings.AdminSettingsService; |
|
|
|
import org.thingsboard.server.dao.tenant.TenantService; |
|
|
|
|
|
|
|
import java.util.Collections; |
|
|
|
import java.util.List; |
|
|
|
import java.util.UUID; |
|
|
|
import java.io.IOException; |
|
|
|
import java.util.*; |
|
|
|
import java.util.concurrent.ExecutionException; |
|
|
|
import java.util.function.Consumer; |
|
|
|
|
|
|
|
@Slf4j |
|
|
|
@Service |
|
|
|
@ -44,65 +55,158 @@ public class OAuth2ServiceImpl implements OAuth2Service { |
|
|
|
private static final String OAUTH2_CLIENT_REGISTRATIONS_PARAMS = "oauth2ClientRegistrationsParams"; |
|
|
|
private static final String OAUTH2_CLIENT_REGISTRATIONS_DOMAIN_NAME_PREFIX = "oauth2ClientRegistrationsDomainNamePrefix"; |
|
|
|
|
|
|
|
private static final String ALLOW_OAUTH2_CONFIGURATION = "allowOAuth2Configuration"; |
|
|
|
|
|
|
|
|
|
|
|
private static final String SYSTEM_SETTINGS_OAUTH2_VALUE = "value"; |
|
|
|
|
|
|
|
private static final String OAUTH2_AUTHORIZATION_PATH_TEMPLATE = "/oauth2/authorization/%s"; |
|
|
|
|
|
|
|
@Autowired |
|
|
|
private AdminSettingsService adminSettingsService; |
|
|
|
|
|
|
|
@Override |
|
|
|
public List<OAuth2ClientInfo> getOAuth2Clients(String domainName) { |
|
|
|
return Collections.emptyList(); |
|
|
|
} |
|
|
|
@Autowired |
|
|
|
private AttributesService attributesService; |
|
|
|
|
|
|
|
@Override |
|
|
|
public List<OAuth2ClientRegistration> getSystemOAuth2ClientRegistrations(TenantId tenantId) { |
|
|
|
return null; |
|
|
|
} |
|
|
|
@Autowired |
|
|
|
private TenantService tenantService; |
|
|
|
|
|
|
|
@Override |
|
|
|
public List<OAuth2ClientRegistration> getTenantOAuth2ClientRegistrations(TenantId tenantId) { |
|
|
|
return null; |
|
|
|
public List<OAuth2ClientInfo> getOAuth2Clients(String domainName) { |
|
|
|
return Collections.emptyList(); |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public OAuth2ClientRegistration saveSystemOAuth2ClientRegistration(OAuth2ClientRegistration clientRegistration) { |
|
|
|
public OAuth2ClientsParams saveSystemOAuth2ClientsParams(OAuth2ClientsParams oAuth2ClientsParams) { |
|
|
|
// TODO check by registration ID in entities
|
|
|
|
AdminSettings clientRegistrationParamsSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAUTH2_CLIENT_REGISTRATIONS_PARAMS); |
|
|
|
if (clientRegistrationParamsSettings == null) { |
|
|
|
clientRegistrationParamsSettings = new AdminSettings(); |
|
|
|
clientRegistrationParamsSettings.setKey(OAUTH2_CLIENT_REGISTRATIONS_PARAMS); |
|
|
|
ObjectNode node = mapper.createObjectNode(); |
|
|
|
clientRegistrationParamsSettings.setJsonValue(node); |
|
|
|
for (OAuth2ClientRegistration clientRegistration : oAuth2ClientsParams.getClientRegistrations()) { |
|
|
|
validator.accept(clientRegistration); |
|
|
|
} |
|
|
|
AdminSettings clientRegistrationParamsSettings = new AdminSettings(); |
|
|
|
clientRegistrationParamsSettings.setKey(OAUTH2_CLIENT_REGISTRATIONS_PARAMS); |
|
|
|
ObjectNode clientRegistrationsNode = mapper.createObjectNode(); |
|
|
|
|
|
|
|
oAuth2ClientsParams.setDomainName(""); |
|
|
|
String json; |
|
|
|
try { |
|
|
|
json = mapper.writeValueAsString(clientRegistration); |
|
|
|
json = mapper.writeValueAsString(oAuth2ClientsParams); |
|
|
|
} catch (JsonProcessingException e) { |
|
|
|
log.error("Unable to convert OAuth2 Client Registration Params to JSON!", e); |
|
|
|
throw new IncorrectParameterException("Unable to convert OAuth2 Client Registration Params to JSON!"); |
|
|
|
} |
|
|
|
ObjectNode oldClientRegistrations = (ObjectNode) clientRegistrationParamsSettings.getJsonValue(); |
|
|
|
oldClientRegistrations.put(clientRegistration.getRegistrationId(), json); |
|
|
|
clientRegistrationsNode.put(SYSTEM_SETTINGS_OAUTH2_VALUE, json); |
|
|
|
|
|
|
|
clientRegistrationParamsSettings.setJsonValue(clientRegistrationsNode); |
|
|
|
|
|
|
|
adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, clientRegistrationParamsSettings); |
|
|
|
// TODO ask if that's worth it
|
|
|
|
return getClientRegistration(clientRegistration.getRegistrationId()); |
|
|
|
|
|
|
|
return getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID); |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public OAuth2ClientRegistration saveTenantOAuth2ClientRegistration(TenantId tenantId, String domainName, OAuth2ClientRegistration clientRegistration) { |
|
|
|
public OAuth2ClientsParams saveTenantOAuth2ClientsParams(TenantId tenantId, OAuth2ClientsParams oAuth2ClientsParams) { |
|
|
|
// TODO ask what if tenant saves config for several different domain names, do we need to check it
|
|
|
|
// TODO check by registration ID in system
|
|
|
|
return null; |
|
|
|
for (OAuth2ClientRegistration clientRegistration : oAuth2ClientsParams.getClientRegistrations()) { |
|
|
|
validator.accept(clientRegistration); |
|
|
|
} |
|
|
|
String clientRegistrationsKey = constructClientRegistrationsKey(oAuth2ClientsParams.getDomainName()); |
|
|
|
AdminSettings existentAdminSettingsByKey = adminSettingsService.findAdminSettingsByKey(tenantId, clientRegistrationsKey); |
|
|
|
if (StringUtils.isEmpty(oAuth2ClientsParams.getAdminSettingsId())) { |
|
|
|
if (existentAdminSettingsByKey == null) { |
|
|
|
existentAdminSettingsByKey = saveOAuth2ClientSettings(tenantId, clientRegistrationsKey); |
|
|
|
oAuth2ClientsParams.setAdminSettingsId(existentAdminSettingsByKey.getId().getId().toString()); |
|
|
|
} else { |
|
|
|
log.error("Current domain name [{}] already registered in the system!", oAuth2ClientsParams.getDomainName()); |
|
|
|
throw new IncorrectParameterException("Current domain name [" + oAuth2ClientsParams.getDomainName() + "] already registered in the system!"); |
|
|
|
} |
|
|
|
} else { |
|
|
|
AdminSettings existentOAuth2ClientsSettingsById = adminSettingsService.findAdminSettingsById( |
|
|
|
tenantId, |
|
|
|
new AdminSettingsId(UUID.fromString(oAuth2ClientsParams.getAdminSettingsId())) |
|
|
|
); |
|
|
|
|
|
|
|
if (existentOAuth2ClientsSettingsById == null) { |
|
|
|
log.error("Admin setting ID is already set in login white labeling object, but doesn't exist in the database"); |
|
|
|
throw new IllegalStateException("Admin setting ID is already set in login white labeling object, but doesn't exist in the database"); |
|
|
|
} |
|
|
|
|
|
|
|
if (!existentOAuth2ClientsSettingsById.getKey().equals(clientRegistrationsKey)) { |
|
|
|
if (existentAdminSettingsByKey == null) { |
|
|
|
adminSettingsService.deleteAdminSettingsByKey(tenantId, existentOAuth2ClientsSettingsById.getKey()); |
|
|
|
AdminSettings newOAuth2ClientsSettings = saveOAuth2ClientSettings(tenantId, clientRegistrationsKey); |
|
|
|
oAuth2ClientsParams.setAdminSettingsId(newOAuth2ClientsSettings.getId().getId().toString()); |
|
|
|
} else { |
|
|
|
log.error("Current domain name [{}] already registered in the system!", oAuth2ClientsParams.getDomainName()); |
|
|
|
throw new IncorrectParameterException("Current domain name [" + oAuth2ClientsParams.getDomainName() + "] already registered in the system!"); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
String json; |
|
|
|
try { |
|
|
|
json = mapper.writeValueAsString(oAuth2ClientsParams); |
|
|
|
} catch (JsonProcessingException e) { |
|
|
|
log.error("Unable to convert OAuth2 Client Registration Params to JSON!", e); |
|
|
|
throw new IncorrectParameterException("Unable to convert OAuth2 Client Registration Params to JSON!"); |
|
|
|
} |
|
|
|
List<AttributeKvEntry> attributes = new ArrayList<>(); |
|
|
|
long ts = System.currentTimeMillis(); |
|
|
|
attributes.add(new BaseAttributeKvEntry(new StringDataEntry(OAUTH2_CLIENT_REGISTRATIONS_PARAMS, json), ts)); |
|
|
|
try { |
|
|
|
// TODO ask if I need here .get()
|
|
|
|
attributesService.save(tenantId, tenantId, DataConstants.SERVER_SCOPE, attributes).get(); |
|
|
|
} catch (Exception e) { |
|
|
|
log.error("Unable to save OAuth2 Client Registration Params to attributes!", e); |
|
|
|
throw new IncorrectParameterException("Unable to save OAuth2 Client Registration Params to attributes!"); |
|
|
|
} |
|
|
|
return getTenantOAuth2ClientsParams(tenantId); |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public void deleteDomainOAuth2ClientRegistrationByTenant(TenantId tenantId) { |
|
|
|
public OAuth2ClientsParams getSystemOAuth2ClientsParams(TenantId tenantId) { |
|
|
|
AdminSettings oauth2ClientsParamsSettings = adminSettingsService.findAdminSettingsByKey(tenantId, OAUTH2_CLIENT_REGISTRATIONS_PARAMS); |
|
|
|
String json = null; |
|
|
|
if (oauth2ClientsParamsSettings != null) { |
|
|
|
json = oauth2ClientsParamsSettings.getJsonValue().get(SYSTEM_SETTINGS_OAUTH2_VALUE).asText(); |
|
|
|
} |
|
|
|
return constructOAuth2ClientsParams(json); |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public OAuth2ClientsParams getTenantOAuth2ClientsParams(TenantId tenantId) { |
|
|
|
ListenableFuture<String> jsonFuture; |
|
|
|
if (isOAuth2ClientRegistrationAllowed(tenantId)) { |
|
|
|
jsonFuture = getOAuth2ClientsParamsAttribute(tenantId); |
|
|
|
} else { |
|
|
|
jsonFuture = Futures.immediateFuture(""); |
|
|
|
} |
|
|
|
try { |
|
|
|
return Futures.transform(jsonFuture, this::constructOAuth2ClientsParams, MoreExecutors.directExecutor()).get(); |
|
|
|
} catch (InterruptedException | ExecutionException e) { |
|
|
|
log.error("Failed to read OAuth2 Clients Params from attributes!", e); |
|
|
|
throw new RuntimeException("Failed to read OAuth2 Clients Params from attributes!", e); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public void deleteDomainOAuth2ClientRegistrationByTenant(TenantId tenantId) { |
|
|
|
OAuth2ClientsParams params = getTenantOAuth2ClientsParams(tenantId); |
|
|
|
if (!StringUtils.isEmpty(params.getDomainName())) { |
|
|
|
// TODO don't we need to delete from attributes?
|
|
|
|
String oauth2ClientsParamsKey = constructClientRegistrationsKey(params.getDomainName()); |
|
|
|
adminSettingsService.deleteAdminSettingsByKey(tenantId, oauth2ClientsParamsKey); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public boolean isOAuth2ClientRegistrationAllowed(TenantId tenantId) { |
|
|
|
return false; |
|
|
|
Tenant tenant = tenantService.findTenantById(tenantId); |
|
|
|
JsonNode allowOAuth2ConfigurationJsonNode = tenant.getAdditionalInfo() != null ? tenant.getAdditionalInfo().get(ALLOW_OAUTH2_CONFIGURATION) : null; |
|
|
|
if (allowOAuth2ConfigurationJsonNode == null) { |
|
|
|
return true; |
|
|
|
} else { |
|
|
|
return allowOAuth2ConfigurationJsonNode.asBoolean(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
@ -110,6 +214,35 @@ public class OAuth2ServiceImpl implements OAuth2Service { |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
private ListenableFuture<String> getOAuth2ClientsParamsAttribute(TenantId tenantId) { |
|
|
|
ListenableFuture<List<AttributeKvEntry>> attributeKvEntriesFuture; |
|
|
|
try { |
|
|
|
attributeKvEntriesFuture = attributesService.find(tenantId, tenantId, DataConstants.SERVER_SCOPE, |
|
|
|
Collections.singletonList(OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); |
|
|
|
} catch (Exception e) { |
|
|
|
log.error("Unable to read OAuth2 Clients Params from attributes!", e); |
|
|
|
throw new IncorrectParameterException("Unable to read OAuth2 Clients Params from attributes!"); |
|
|
|
} |
|
|
|
return Futures.transform(attributeKvEntriesFuture, attributeKvEntries -> { |
|
|
|
if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) { |
|
|
|
AttributeKvEntry kvEntry = attributeKvEntries.get(0); |
|
|
|
return kvEntry.getValueAsString(); |
|
|
|
} else { |
|
|
|
return ""; |
|
|
|
} |
|
|
|
}, MoreExecutors.directExecutor()); |
|
|
|
} |
|
|
|
|
|
|
|
private AdminSettings saveOAuth2ClientSettings(TenantId tenantId, String clientRegistrationsKey) { |
|
|
|
AdminSettings oauth2ClientsSettings = new AdminSettings(); |
|
|
|
oauth2ClientsSettings.setKey(clientRegistrationsKey); |
|
|
|
ObjectNode node = mapper.createObjectNode(); |
|
|
|
node.put("entityType", EntityType.TENANT.name()); |
|
|
|
node.put("entityId", tenantId.toString()); |
|
|
|
oauth2ClientsSettings.setJsonValue(node); |
|
|
|
return adminSettingsService.saveAdminSettings(tenantId, oauth2ClientsSettings); |
|
|
|
} |
|
|
|
|
|
|
|
private String constructClientRegistrationsKey(String domainName) { |
|
|
|
String clientRegistrationsKey; |
|
|
|
if (StringUtils.isEmpty(domainName)) { |
|
|
|
@ -119,4 +252,103 @@ public class OAuth2ServiceImpl implements OAuth2Service { |
|
|
|
} |
|
|
|
return clientRegistrationsKey; |
|
|
|
} |
|
|
|
|
|
|
|
private OAuth2ClientsParams constructOAuth2ClientsParams(String json) { |
|
|
|
OAuth2ClientsParams result = null; |
|
|
|
if (!StringUtils.isEmpty(json)) { |
|
|
|
try { |
|
|
|
result = mapper.readValue(json, OAuth2ClientsParams.class); |
|
|
|
} catch (IOException e) { |
|
|
|
log.error("Unable to read OAuth2 Clients Params from JSON!", e); |
|
|
|
throw new IncorrectParameterException("Unable to read OAuth2 Clients Params from JSON!"); |
|
|
|
} |
|
|
|
} |
|
|
|
if (result == null) { |
|
|
|
result = new OAuth2ClientsParams(); |
|
|
|
} |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
private final Consumer<OAuth2ClientRegistration> validator = clientRegistration -> { |
|
|
|
if (StringUtils.isEmpty(clientRegistration.getRegistrationId())) { |
|
|
|
throw new DataValidationException("Registration ID should be specified!"); |
|
|
|
} |
|
|
|
if (StringUtils.isEmpty(clientRegistration.getClientId())) { |
|
|
|
throw new DataValidationException("Client ID should be specified!"); |
|
|
|
} |
|
|
|
if (StringUtils.isEmpty(clientRegistration.getClientSecret())) { |
|
|
|
throw new DataValidationException("Client secret should be specified!"); |
|
|
|
} |
|
|
|
if (StringUtils.isEmpty(clientRegistration.getAuthorizationUri())) { |
|
|
|
throw new DataValidationException("Authorization uri should be specified!"); |
|
|
|
} |
|
|
|
if (StringUtils.isEmpty(clientRegistration.getTokenUri())) { |
|
|
|
throw new DataValidationException("Token uri should be specified!"); |
|
|
|
} |
|
|
|
if (StringUtils.isEmpty(clientRegistration.getRedirectUriTemplate())) { |
|
|
|
throw new DataValidationException("Redirect uri template should be specified!"); |
|
|
|
} |
|
|
|
if (StringUtils.isEmpty(clientRegistration.getScope())) { |
|
|
|
throw new DataValidationException("Scope should be specified!"); |
|
|
|
} |
|
|
|
if (StringUtils.isEmpty(clientRegistration.getAuthorizationGrantType())) { |
|
|
|
throw new DataValidationException("Authorization grant type should be specified!"); |
|
|
|
} |
|
|
|
if (StringUtils.isEmpty(clientRegistration.getUserInfoUri())) { |
|
|
|
throw new DataValidationException("User info uri should be specified!"); |
|
|
|
} |
|
|
|
if (StringUtils.isEmpty(clientRegistration.getUserNameAttributeName())) { |
|
|
|
throw new DataValidationException("User name attribute name should be specified!"); |
|
|
|
} |
|
|
|
if (StringUtils.isEmpty(clientRegistration.getJwkSetUri())) { |
|
|
|
throw new DataValidationException("Jwk set uri should be specified!"); |
|
|
|
} |
|
|
|
if (StringUtils.isEmpty(clientRegistration.getClientAuthenticationMethod())) { |
|
|
|
throw new DataValidationException("Client authentication method should be specified!"); |
|
|
|
} |
|
|
|
if (StringUtils.isEmpty(clientRegistration.getClientName())) { |
|
|
|
throw new DataValidationException("Client name should be specified!"); |
|
|
|
} |
|
|
|
if (StringUtils.isEmpty(clientRegistration.getLoginButtonLabel())) { |
|
|
|
throw new DataValidationException("Login button label should be specified!"); |
|
|
|
} |
|
|
|
OAuth2MapperConfig mapperConfig = clientRegistration.getMapperConfig(); |
|
|
|
if (mapperConfig == null) { |
|
|
|
throw new DataValidationException("Mapper config should be specified!"); |
|
|
|
} |
|
|
|
if (mapperConfig.getType() == null) { |
|
|
|
throw new DataValidationException("Mapper config type should be specified!"); |
|
|
|
} |
|
|
|
if (mapperConfig.getType() == MapperType.BASIC) { |
|
|
|
OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasicConfig(); |
|
|
|
if (basicConfig == null) { |
|
|
|
throw new DataValidationException("Basic config should be specified!"); |
|
|
|
} |
|
|
|
if (StringUtils.isEmpty(basicConfig.getEmailAttributeKey())) { |
|
|
|
throw new DataValidationException("Email attribute key should be specified!"); |
|
|
|
} |
|
|
|
if (basicConfig.getTenantNameStrategy() == null) { |
|
|
|
throw new DataValidationException("Tenant name strategy should be specified!"); |
|
|
|
} |
|
|
|
if (basicConfig.getTenantNameStrategy() == TenantNameStrategyType.CUSTOM |
|
|
|
&& StringUtils.isEmpty(basicConfig.getTenantNamePattern())) { |
|
|
|
throw new DataValidationException("Tenant name pattern should be specified!"); |
|
|
|
} |
|
|
|
} |
|
|
|
if (mapperConfig.getType() == MapperType.CUSTOM) { |
|
|
|
OAuth2CustomMapperConfig customConfig = mapperConfig.getCustomConfig(); |
|
|
|
if (customConfig == null) { |
|
|
|
throw new DataValidationException("Custom config should be specified!"); |
|
|
|
} |
|
|
|
if (StringUtils.isEmpty(customConfig.getUrl())) { |
|
|
|
throw new DataValidationException("Custom mapper URL should be specified!"); |
|
|
|
} |
|
|
|
if (StringUtils.isEmpty(customConfig.getUsername())) { |
|
|
|
throw new DataValidationException("Custom mapper username should be specified!"); |
|
|
|
} |
|
|
|
if (StringUtils.isEmpty(customConfig.getPassword())) { |
|
|
|
throw new DataValidationException("Custom mapper password should be specified!"); |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
} |
|
|
|
|