Composer 中的 ThingWorx 模型定义 > 系统 > 子系统 > 文件传输子系统 > 远程文件传输的可选安全增强功能
远程文件传输的可选安全增强功能
对于需要与远程文件传输相关的附加控制和安全性的客户,PTC 建议实施以下部分或全部示例实体以及随附的订阅。
下面的第一个实体是数据形状,它允许管理员根据所需的频率和远程文件传输大小来配置所需的参数。数据形状允许根据需要为不同的 ThingWorx 用户 (和/或与其关联的应用程序密钥) 配置不同的设置。
第二个实体是值流,用于以每个用户为单位记录文件传输的大小和频率。
下面的第三个实体用于实现由每个远程文件传输事件触发的订阅。订阅将自动执行以下操作:
1. 提供提醒。
2. 冻结文件传输。
3. 每当超出特定文件传输频率时,将终止相关 WebSocket 连接。
* 
在下面的示例中,记录器语句已被注释掉且仅用于调试目的。PTC 建议客户不要在生产中启用它们,以避免意外泄露信息。
数据形状实体
首先,导入或创建由以下三个字段组成的数据形状:SizeUserFrequency。这将跟踪用户允许的最大文件传输限制。
<?xml version="1.0" encoding="UTF-8"?>
<!-- This entity is for informational and example purposes only, and you must configure/validate it to ensure that it meets your functional and security requirements. -->
<Entities build="latest" majorVersion="0" minorVersion="0" modelPersistenceProviderPackage="PostgresPersistenceProviderPackage" revision="0" schemaVersion="1045" universal="">
<DataShapes>
<DataShape baseDataShape="" description="DataShape for permissible file transfer limits" documentationContent="" homeMashup="" lastModifiedDate="2020-10-01T00:00:00.000-00:00" name="SizeLimits" projectName="FileTransfer" tags="">
<Owner name="Administrator" type="User" />
<avatar />
<DesignTimePermissions>
<Create />
<Read />
<Update />
<Delete />
<Metadata />
</DesignTimePermissions>
<RunTimePermissions />
<VisibilityPermissions>
<Visibility />
</VisibilityPermissions>
<ConfigurationTableDefinitions />
<ConfigurationTables />
<FieldDefinitions>
<FieldDefinition aspect.isPrimaryKey="false" aspect.minimumValue="1.0" baseType="NUMBER" description="" name="Frequency" ordinal="3" />
<FieldDefinition aspect.isPrimaryKey="false" aspect.minimumValue="1.0" baseType="NUMBER" description="SizeLimit for user in bytes" name="Size" ordinal="1" />
<FieldDefinition aspect.isPrimaryKey="false" baseType="STRING" description="User name" name="User" ordinal="2" />
</FieldDefinitions>
</DataShape>
</DataShapes>
</Entities>
值流实体
导入或创建数据形状后,导入或创建以下实体以记录每个用户的文件传输历史记录:
<?xml version="1.0" encoding="UTF-8"?>
<!-- This entity is for informational and example purposes only, and you must configure/validate it to ensure that it meets your functional and security requirements. -->
<Entities build="latest" majorVersion="0" minorVersion="0" modelPersistenceProviderPackage="PostgresPersistenceProviderPackage" revision="0" schemaVersion="1045" universal="">
<Things>
<Thing description="ValueStream to log history of file transfers" documentationContent="" effectiveThingPackage="ValueStreamThing" enabled="true" homeMashup="StreamMashup" identifier="" lastModifiedDate="2020-10-01T00:00:00.000-00:00" name="VS" projectName="FileTransfer" published="false" tags="" thingTemplate="ValueStream" valueStream="">
<Owner name="Administrator" type="User" />
<avatar />
<DesignTimePermissions>
<Create />
<Read />
<Update />
<Delete />
<Metadata />
</DesignTimePermissions>
<RunTimePermissions />
<VisibilityPermissions>
<Visibility />
</VisibilityPermissions>
<ConfigurationTableDefinitions />
<ConfigurationTables>
<ConfigurationTable description="Data Thing Configuration" isMultiRow="false" name="DataThingSettings" ordinal="0">
<DataShape>
<FieldDefinitions>
<FieldDefinition baseType="STRING" description="Persistence Provider Name" name="persistenceProvider" ordinal="0" />
</FieldDefinitions>
</DataShape>
<Rows>
<Row>
<persistenceProvider><![CDATA[ThingworxPersistenceProvider]]></persistenceProvider>
</Row>
</Rows>
</ConfigurationTable>
<ConfigurationTable description="Configurable options to tune Value Stream performance" isMultiRow="false" name="PersistenceProviderCustomSettings" ordinal="2">
<DataShape>
<FieldDefinitions>
<FieldDefinition aspect.friendlyName="Persistence Provider Custom Settings Table" baseType="INFOTABLE" description="Persistence Provider Custom Config Table" name="customConfigTable" ordinal="0" />
</FieldDefinitions>
</DataShape>
<Rows>
<Row>
<customConfigTable>
<infoTable>
<DataShape>
<FieldDefinitions />
</DataShape>
<Rows />
</infoTable>
</customConfigTable>
</Row>
</Rows>
</ConfigurationTable>
</ConfigurationTables>
<ThingShape>
<PropertyDefinitions />
<ServiceDefinitions />
<EventDefinitions />
<ServiceMappings />
<ServiceImplementations />
<Subscriptions />
</ThingShape>
<PropertyBindings />
<RemotePropertyBindings />
<RemoteServiceBindings />
<RemoteEventBindings />
<AlertConfigurations />
<ImplementedShapes />
<ThingProperties />
</Thing>
</Things>
</Entities>
远程文件传输实体
最后,导入或创建包含以下订阅的事物,其中每个文件传输事件都会触发此订阅:
<?xml version="1.0" encoding="UTF-8"?>
<!-- This entity is for informational and example purposes only, and you must configure/validate it to ensure that it meets your functional and security requirements. -->
<Entities build="latest" majorVersion="0" minorVersion="0" modelPersistenceProviderPackage="PostgresPersistenceProviderPackage" revision="0" schemaVersion="1045" universal="">
<Things>
<Thing description="Thing for regulating Remote File Transfer" documentationContent="" effectiveThingPackage="RemoteThingWithFileTransfer" enabled="true" homeMashup="" identifier="FileRepo@ServerA" lastModifiedDate="2020-10-01T00:00:00.000-00:00" name="RemoteFileTransfer" projectName="FileTransfer" published="false" tags="" thingTemplate="RemoteThingWithFileTransfer" valueStream="VS">
<Owner name="Administrator" type="User" />
<avatar />
<DesignTimePermissions>
<Create />
<Read />
<Update />
<Delete />
<Metadata />
</DesignTimePermissions>
<RunTimePermissions />
<VisibilityPermissions>
<Visibility />
</VisibilityPermissions>
<ConfigurationTableDefinitions />
<ConfigurationTables>
<ConfigurationTable description="Reporting Settings" isMultiRow="false" name="ReportingConfiguration" ordinal="0">
<DataShape>
<FieldDefinitions>
<FieldDefinition aspect.defaultValue="NotReporting" aspect.isPrimaryKey="true" aspect.isReadOnly="false" aspect.thingTemplate="ReportingStrategy" baseType="THINGNAME" description="Strategy to determine health" name="reportingStrategy" ordinal="0" />
</FieldDefinitions>
</DataShape>
<Rows>
<Row>
<reportingStrategy><![CDATA[AlwaysOnReporting]]></reportingStrategy>
</Row>
</Rows>
</ConfigurationTable>
</ConfigurationTables>
<ThingShape>
<PropertyDefinitions>
<PropertyDefinition aspect.cacheTime="0.0" aspect.dataChangeType="VALUE" aspect.isLogged="true" aspect.isPersistent="true" baseType="NUMBER" category="" description="" isLocalOnly="false" name="Administrator" ordinal="2" />
<PropertyDefinition aspect.cacheTime="0.0" aspect.dataChangeType="VALUE" aspect.defaultValue="0.0" aspect.isLogged="true" aspect.isPersistent="true" baseType="NUMBER" category="" description="" isLocalOnly="false" name="EventProp" ordinal="4" />
<PropertyDefinition aspect.cacheTime="0.0" aspect.dataChangeType="VALUE" aspect.dataShape="SizeLimits" aspect.isLogged="false" aspect.isPersistent="true" baseType="INFOTABLE" category="" description="" isLocalOnly="false" name="Users" ordinal="3" />
</PropertyDefinitions>
<ServiceDefinitions>
<ServiceDefinition aspect.isAsync="false" category="" description="" isAllowOverride="false" isLocalOnly="false" isOpen="false" isPrivate="false" name="s1">
<ResultType baseType="INFOTABLE" description="" name="result" ordinal="0" />
<ParameterDefinitions />
</ServiceDefinition>
</ServiceDefinitions>
<EventDefinitions />
<ServiceMappings />
<ServiceImplementations>
<ServiceImplementation description="" handlerName="Script" name="s1">
<ConfigurationTables>
<ConfigurationTable description="" isMultiRow="false" name="Script" ordinal="0">
<DataShape>
<FieldDefinitions>
<FieldDefinition baseType="STRING" description="code" name="code" ordinal="0" />
</FieldDefinitions>
</DataShape>
<Rows>
<Row>
<code><![CDATA[var ed = new Date();
var result = me.QueryNumberPropertyHistory({
oldestFirst: undefined /* BOOLEAN */,
maxItems: undefined /* NUMBER */,
endDate: ed /* DATETIME */,
propertyName: "Administrator" /* STRING */,
query: undefined /* QUERY */,
startDate: ed-(24*3600*1000) /* DATETIME */
});]]></code>
</Row>
</Rows>
</ConfigurationTable>
</ConfigurationTables>
</ServiceImplementation>
</ServiceImplementations>
<Subscriptions>
<Subscription description="" enabled="false" eventName="FileTransfer" name="FileTransferSub" source="" sourceProperty="" sourceType="Thing">
<ServiceImplementation description="" handlerName="Script" name="FileTransferSub">
<ConfigurationTables>
<ConfigurationTable description="" isMultiRow="false" name="Script" ordinal="0">
<DataShape>
<FieldDefinitions>
<FieldDefinition baseType="STRING" description="code" name="code" ordinal="0" />
</FieldDefinitions>
</DataShape>
<Rows>
<Row>
<code><![CDATA[var activeTransferJobs = Subsystems["FileTransferSubsystem"].GetActiveTransferJobs();
for each (activeTransferJob in activeTransferJobs.rows) {
var bytesTransferred = activeTransferJob.bytesTransferred;
var transferSize = activeTransferJob.maxSize;
//QueryPropertyHistoryfor24hours
var ed = new Date();
var user = eventData.user;
var last24HrsFileTransfer = me.QueryNumberPropertyHistory({
oldestFirst: undefined /* BOOLEAN */,
maxItems: undefined /* NUMBER */,
endDate: ed /* DATETIME */,
propertyName: user /* STRING */,
query: undefined /* QUERY */,
startDate: ed-(24*3600*1000) /* DATETIME */
});
//Aggregate for 24 hrs.
var totalTransferredBytes = Resources["InfoTableFunctions"].Aggregate({
t: last24HrsFileTransfer,
columns: "value",
aggregates: "SUM"
});
var totalFrequency = last24HrsFileTransfer.length;
var defaultSizeLimitForUser;
var defaultFrequencyForUser;
for each(var user in me.Users.rows){
if(user.User == eventData.user){
defaultSizeLimitForUser = user.Size;
defaultFrequencyForUser = user.Frequency;
}
}
if(totalFrequency>defaultFrequencyForUser || (totalTransferredBytes.SUM_value + transferSize)> defaultSizeLimitForUser){
//logger.error("File Transfer Limit Exceeded : " + activeTransferJob.transferId);
//Send Alert
var eventPropValue = Things['RemoteFileTransfer']['EventProp'];
Things['RemoteFileTransfer']['EventProp'] = eventPropValue + 1;

//Freeze Transfer
//logger.warn("FreezingTransfer : " + activeTransferJob.transferId);
Subsystems["FileTransferSubsystem"].CancelTransfer({
reason: "Surpassed file transfer limit of : " + defaultSizeLimitForUser + " bytes or frequency of " + defaultFrequencyForUser,
transferId: activeTransferJob.transferId
});
//logger.warn("Transfer Freezed : " + activeTransferJob.transferId);
//Closing Endpoint Session for User
result = Subsystems["WSCommunicationsSubsystem"].CloseEndpointSessions({
userName: activeTransferJob.user
});
//logger.warn("Session Closed for userContext : " + activeTransferJob.user);
}
me[eventData.user] = bytesTransferred;
}]]></code>
</Row>
</Rows>
</ConfigurationTable>
</ConfigurationTables>
</ServiceImplementation>
</Subscription>
</Subscriptions>
</ThingShape>
<PropertyBindings />
<RemotePropertyBindings />
<RemoteServiceBindings />
<RemoteEventBindings />
<AlertConfigurations>
<AlertDefinitions name="Administrator" />
<AlertDefinitions name="EventProp" />
<AlertDefinitions name="Users" />
</AlertConfigurations>
<ImplementedShapes />
<ThingProperties>
<Users>
<Value>
<infoTable>
<DataShape>
<FieldDefinitions>
<FieldDefinition aspect.isPrimaryKey="false" aspect.minimumValue="1.0" baseType="NUMBER" description="" name="Frequency" ordinal="3" />
<FieldDefinition aspect.isPrimaryKey="false" aspect.minimumValue="1.0" baseType="NUMBER" description="SizeLimit for user in bytes" name="Size" ordinal="1" />
<FieldDefinition aspect.isPrimaryKey="false" baseType="STRING" description="User name" name="User" ordinal="2" />
</FieldDefinitions>
</DataShape>
<Rows>
<Row>
<Frequency>10.0</Frequency>
<Size>1500000.0</Size>
<User><![CDATA[Administrator]]></User>
</Row>
</Rows>
</infoTable>
</Value>
<Timestamp>2020-10-01T00:00:00.000-00:00</Timestamp>
<Quality>GOOD</Quality>
</Users>
</ThingProperties>
</Thing>
</Things>
</Entities>