目录

数据防泄漏服务开发指导(ArkTS)

DLP是系统提供的系统级的数据防泄漏解决方案,提供一种称为DLP的文件格式。后缀格式为“原始文件名(包含原始文件后缀).dlp”,例如“test.docx.dlp”,文件由授权凭证和原始文件密文组成。

通过端云协同认证(需要联网)来获取文件的访问授权,授权类型包含只读、编辑、文件拥有者三种。

应用需要访问DLP文件时,系统会自动安装应用的DLP沙箱分身应用,相当于完全独立的应用,数据和配置会继承原应用,但相互之间并不共享。分身应用在运行时会处于DLP沙箱环境中,访问外部的权限会被限制,以防止数据的泄漏。每当打开一个新的DLP文件会生成一个应用沙箱分身,沙箱应用之间也是相互隔离的,当应用关闭后应用分身会自动卸载,沙箱期间产生的临时数据也会丢弃。

正常情况下,应用不会感知到沙箱的存在,访问的也是解密后的明文,和访问普通文件没有区别,但由于DLP沙箱会限制其访问外部的权限(例如网络、剪切板、截屏、录屏、蓝牙等)。为了更好的用户体验,需要应用进行适配,例如文件只读的情况下,不应显示“保存”按钮,不应主动联网等。

沙箱限制

当应用进入DLP沙箱状态时,可以申请的权限将受到限制,根据DLP文件授权类型不同,限制也不相同,如下表:

权限名 说明 授权类型:只读 授权类型:编辑/文件拥有者
ohos.permission.USE_BLUETOOTH 允许应用使用蓝牙。 禁止 禁止
ohos.permission.INTERNET 允许应用访问网络。 禁止 禁止
ohos.permission.DISTRIBUTED_DATASYNC 允许应用与远程设备交换用户数据(如图片、音乐、视频、及应用数据等)。 禁止 禁止
ohos.permission.WRITE_MEDIA 应用读写用户媒体文件,如视频、音频、图片等,需要申请此权限。 禁止 允许
ohos.permission.NFC_TAG 允许应用使用NFC。 禁止 允许

接口说明

接口名 描述

isDLPFile(fd: number): Promise<boolean>

isDLPFile(fd: number, callback: AsyncCallback<boolean>): void

判断是否是dlp文件。

getDLPPermissionInfo(): Promise<DLPPermissionInfo>

getDLPPermissionInfo(callback: AsyncCallback<DLPPermissionInfo>): void

获取当前沙箱应用的权限类型。
getOriginalFileName(fileName: string): string 获取dlp文件原始文件名。
getDLPSuffix(): string 获取dlp文件dlp后缀名。
on(type: 'openDLPFile', listener: Callback<AccessedDLPFileInfo>): void 注册dlp文件打开事件监听,用于原始应用获取dlp文件打开事件。
off(type: 'openDLPFile', listener?: Callback<AccessedDLPFileInfo>): void 取消dlp文件打开事件监听。

isInSandbox(): Promise<boolean>

isInSandbox(callback: AsyncCallback<boolean>): void

判断当前是否是dlp沙箱应用。

getDLPSupportedFileTypes(): Promise<Array<string>>

getDLPSupportedFileTypes(callback: AsyncCallback<Array<string>>): void

获取当前系统支持添加权限保护的文件格式类型。

setRetentionState(docUris: Array<string>): Promise<void>

setRetentionState(docUris: Array<string>, callback: AsyncCallback<void>): void

设置dlp分身应用保留状态。

cancelRetentionState(docUris: Array<string>): Promise<void>

cancelRetentionState(docUris: Array<string>, callback: AsyncCallback<void>): void

取消dlp分身应用保留状态。

getRetentionSandboxList(bundleName?: string): Promise<Array<RetentionSandboxInfo>>

getRetentionSandboxList(bundleName: string, callback: AsyncCallback<Array<RetentionSandboxInfo>>): void

getRetentionSandboxList(callback: AsyncCallback<Array<RetentionSandboxInfo>>): void

获取当前保留沙箱列表。

getDLPFileAccessRecords(): Promise<Array<AccessedDLPFileInfo>>

getDLPFileAccessRecords(callback: AsyncCallback<Array<AccessedDLPFileInfo>>): void

获取dlp文件访问记录。
setSandboxAppConfig(configInfo: string): Promise<void> 设置沙箱应用配置信息。
getSandboxAppConfig(): Promise<string> 查询沙箱应用配置信息。
cleanSandboxAppConfig(): Promise<void> 清理沙箱应用配置信息。
startDLPManagerForResult(context: common.UIAbilityContext, want: Want): Promise<DLPManagerResult> 在当前UIAbility界面以无边框形式打开DLP权限管理应用(只支持Stage模型)。

开发步骤

DLP作为HarmonyOS系统级数据防泄漏方案,可以让应用在零适配或低代码适配的情况下接入DLP能力,打开DLP文件。

当用户使用默认应用或指定应用打开DLP文件时,DLP框架将会完成:

  1. 安装此应用的DLP沙箱分身应用。

  2. 为这个DLP文件绑定一个FUSE文件。

  3. 将FUSE文件分享给DLP沙箱分身应用。

实现DLP沙箱分身在无感加解密流程下访问DLP文件解密后的内容。

当三方应用接入DLP(支持打开DLP文件)时,为了更优的体验,可从以下方面完成适配。

[h2]预置操作

本文档提供接口示例代码,如需要了解工程项目创建方式,可参考工程创建。

应用接入DLP能力,支持被安装为DLP沙箱分身应用,打开DLP文件,需要具备以下条件:

[h2]导入模块

引入dlpPermission模块。

import { dlpPermission } from '@kit.DataProtectionKit';

[h2]应用支持打开DLP文件绑定的FUSE文件

一般情况下,应用如果支持打开预置操作中指定文件类型的文件,没有对传入的Want做特定限制的情况下,不需要适配即可打开FUSE文件。

打开DLP文件时,应用被安装为DLP沙箱分身应用(后续简称为分身),分身会收到want请求,分身可以对其中一些字段进行解析:

import { Want } from '@kit.AbilityKit';

interface DLPUriObj {
  name: string
};

interface DLPWriteable {
  name:boolean
};

interface DLPNameObj {
  dateModified: string,
  displayName: string,
  relativePath: string,
};

interface DLPLinkNameObj {
  name: string
};

function getParams(want: Want) {
  // 接收打开DLP文件传过来的参数
  let dlpFuseUri: string = want.uri? want.uri : '';  // FUSE文件的uri, 存放解密后的明文
  let dlpFuseWriteable: boolean = (want.parameters?.linkFileWriteable as DLPWriteable).name; // 对FUSE文件是否有写权限
  let dlpUri: string = (want.parameters?.dlpUri as DLPUriObj).name; // DLP文件的uri
  let dlpName: string = (want.parameters?.fileAsset as DLPNameObj).displayName; // DLP文件的文件名
  let dlpFuseName: string = (want.parameters?.linkFileName as DLPLinkNameObj).name; // FUSE文件的文件名
}

分身可以通过把want.uri打开为fd,获取FUSE文件的内容:

import { fileIo } from '@kit.CoreFileKit';
import { util } from '@kit.ArkTS';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG: string = 'dlp';

function readFileContent(dlpFuseUri: string): string {
  let content: string = '';
  let file: fileIo.File | undefined = undefined;
  try {
    file = fileIo.openSync(dlpFuseUri, fileIo.OpenMode.READ_ONLY);
  } catch (err) {
    hilog.error(0x0000, TAG, 'openSync failed. ' + err);
    if (file) {
      fileIo.closeSync(file);
    }
    return content;
  }

  try {
    let buffer = new ArrayBuffer(4096);
    let bytesRead = fileIo.readSync(file.fd, buffer);
    let actualBuffer = buffer.slice(0, bytesRead);
    content = bufferToString(actualBuffer);
  } catch (err) {
    hilog.error(0x0000, TAG,'readSync failed. ' + err);
  } finally {
    if (file) {
      fileIo.closeSync(file);
    }
  }
  return content;
}

function bufferToString(buffer: ArrayBuffer): string {
  let textDecoder = new util.TextDecoder('utf-8', {
    ignoreBOM: true
  });
  return textDecoder.decodeToString(new Uint8Array(buffer), {
    stream: true
  });
}

如果有FUSE文件的读写权限,也可以更新FUSE文件内容:

import { fileIo } from '@kit.CoreFileKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG: string = 'dlp';

function writeFileContent(dlpFuseUri: string, content: string): void {
  let file: fileIo.File | undefined = undefined;

  try {
    file = fileIo.openSync(dlpFuseUri, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE);
    // O_RDWR: 读写模式, O_CREAT: 文件不存在时创建
    let writeLen: number = fileIo.writeSync(file.fd, content);

  } catch (err) {
    hilog.error(0x0000, TAG, '文件操作失败: ' + err);
  } finally {
    if (file) {
      fileIo.closeSync(file);
    }
  }
}

[h2]应用根据DLP文件的权限对界面进行适配

DLP沙箱分身中可以调用getDLPPermissionInfo方法查询当前系统登陆的域账号用户对本DLP文件的用户权限和操作权限,不同用户权限可以对应不同的对文档的操作权限。

import { dlpPermission } from '@kit.DataProtectionKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG: string = 'dlp';

dlpPermission.getDLPPermissionInfo().then((data: dlpPermission.DLPPermissionInfo)=> {
  let userAccess: dlpPermission.DLPFileAccess = data.dlpFileAccess; // 用户对本DLP文件的用户权限
  let isEditable: number = data.flags & dlpPermission.ActionFlagType.ACTION_EDIT; // 用户对本DLP文件的操作权限
}).catch((err: BusinessError) => {
  hilog.error(0x0000, TAG, 'getDLPPermissionInfo: ' + JSON.stringify(err));
});

getDLPPermissionInfo返回的data为DLPPermissionInfo类型,其中dlpFileAccess表示用户权限,flags表示操作权限的按位组合的结果。可以根据返回的flags字段对照ActionFlagType判断DLP沙箱分身是否具有对应的操作权限,可以用于界面按钮置灰操作等。

[h2]应用与DLP沙箱分身数据共享

DLP沙箱分身是普通应用的分身,所有数据都是全新的,如果二者之间有些数据需要实现共享,可以通过DLP框架提供的应用与DLP沙箱分身数据共享机制实现。一种典型的使用场景是原应用与DLP沙箱分身之间共用是否已经弹出过隐私声明弹框的配置信息。

一般包括下面四种读写配置信息前后顺序组合:

约束与限制

具体步骤

  1. 设置配置信息。

    把需要保存的配置信息转成string类型,调用setSandboxAppConfig接口设置配置信息。

    普通应用和DLP沙箱分身都可以调用该接口,但DLP沙箱分身必须在读取DLP文件内容之前才允许调用。

    import { dlpPermission } from '@kit.DataProtectionKit';
    import { BusinessError } from '@kit.BasicServicesKit';
    import { hilog } from '@kit.PerformanceAnalysisKit';
    
    const TAG: string = 'dlp';
    
    async function setSandboxAppConfig() {
      try {
        await dlpPermission.setSandboxAppConfig('configInfo'); // 设置配置信息
      } catch (err) {
        hilog.error(0x0000, TAG, 'setSandboxAppConfig error: ' + JSON.stringify(err)); // 失败报错
      }
    }
  2. 清理配置信息。

    调用cleanSandboxAppConfig接口清理该应用的所有配置信息。

    该接口只允许普通应用中调用。

    import { dlpPermission } from '@kit.DataProtectionKit';
    import { BusinessError } from '@kit.BasicServicesKit';
    import { hilog } from '@kit.PerformanceAnalysisKit';
    
    const TAG: string = 'dlp';
    
    async function cleanSandboxAppConfig() {
      try {
        await dlpPermission.cleanSandboxAppConfig(); // 清理配置信息
      } catch (err) {
        hilog.error(0x0000, TAG, 'cleanSandboxAppConfig error: ' + JSON.stringify(err)); // 失败报错
      }
    }
  3. 获取配置信息。

    调用getSandboxAppConfig查询该应用的所有配置信息。

    普通应用和DLP沙箱分身都可以调用。

    import { dlpPermission } from '@kit.DataProtectionKit';
    import { BusinessError } from '@kit.BasicServicesKit';
    import { hilog } from '@kit.PerformanceAnalysisKit';
    
    const TAG: string = 'dlp';
    
    async function getSandboxAppConfig() {
      try {
        let res:string = await dlpPermission.getSandboxAppConfig(); // 查询配置信息
      } catch (err) {
        hilog.error(0x0000, TAG, 'getSandboxAppConfig error: ' + JSON.stringify(err)); // 失败报错
      }
    }

[h2]应用支持更新最近打开记录

当应用有最近打开记录场景时,可以使用DLP框架提供的接口适配最近打开记录。可从以下场景适配:

[h2]应用内支持打开选定的DLP文件

应用可以支持从最近打开列表、文件选择器中选择DLP文件,打开DLP文件的场景,按如下流程适配:

  1. 设置Want参数,指定action为"ohos.want.action.viewData",bundleName、abilityName分别为选择打开DLP文件的应用的bundleName、abilityName,uri为需要打开的DLP文件的uri,在parameters中设置fileName的name值为DLP文件的文件名。

  2. 获取UIAbilityContext的context。

  3. 调用context的startAbility方法传入want参数,打开dlp文件。

import { Want, common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { UIContext } from '@kit.ArkUI';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG: string = 'dlp';

function openDlpFile(dlpUri: string, fileName: string) {
  let want: Want = {
    "action": "ohos.want.action.viewData",
    "bundleName": "com.example.example_bundle_name",
    "abilityName": "exampleAbility",
    "uri": dlpUri,
    "parameters": {
      "fileName": {
        "name": fileName
      }
    }
  }
  let context = new UIContext().getHostContext() as common.UIAbilityContext; // 获取当前UIAbilityContext
  try {
    hilog.info(0x0000, TAG, 'openDLPFile:' + JSON.stringify(want));
    hilog.info(0x0000, TAG, 'openDLPFile: delegator:' + JSON.stringify(context));
    context.startAbility(want);
  } catch (err) {
    hilog.error(0x0000, TAG, 'openDLPFile startAbility failed:' + JSON.stringify(err));
    return;
  }
}

[h2]应用内支持对DLP文件权限设置

应用内可以集成权限设置按钮,当已打开一个普通文件后,点击权限设置按钮,拉起DLP管理应用的模态设置权限页面,生成DLP文件。也可以在DLP沙箱分身中查看当前正在打开的DLP文件的操作权限。

[h2]其他DLP能力增强

典型问题自排查

[h2]应用可以打开正常文件,无法打开FUSE文件