1、应用权限是什么?为什么需要?

  • 默认情况下,应用只能访问有限的系统资源。但某些情况下,应用存在扩展功能的诉求,需要访问额外的系统数据(包括用户个人数据)和功能,系统也必须以明确的方式对外提供接口来共享其数据或功能。系统通过访问控制的机制,来避免数据或功能被不当或恶意使用。当前访问控制的机制涉及多方面,包括应用沙箱、应用权限、系统控件等方案。
  • 系统根据应用的APL等级设置进程域和数据域标签,并通过访问控制机制限制应用可访问的数据范围,从而实现在机制上消减应用数据泄露的风险。不同APL等级的应用能够申请的权限等级不同,且不同的系统资源(如:通讯录等)或系统能力(如:访问摄像头、麦克风等)受不同的应用权限保护。通过严格的分层权限保护,有效抵御恶意攻击,确保系统安全可靠。

2、应用APL等级和权限APL等级

  • 为了防止应用过度索取和滥用权限,系统基于APL(Ability Privilege Level,元能力权限等级)等级,配置了不同的权限开放范围。

    元能力权限等级APL指的是应用的权限申请优先级的定义,不同APL等级的应用能够申请的权限等级不同。

2.1、应用APL等级

应用的等级可以分为以下三个等级,等级依次提高。

APL级别说明
normal默认情况下,应用的APL等级都为normal等级。
system_basic该等级的应用服务提供系统基础服务。
system_core该等级的应用服务提供操作系统核心能力。
应用APL等级不允许配置为system_core。

2.2、权限APL等级

根据权限对于不同等级应用有不同的开放范围,权限类型对应分为以下三个等级,等级依次提高。

APL级别说明开放范围
normal允许应用访问超出默认规则外的普通系统资源,如配置Wi-Fi信息、调用相机拍摄等。
这些系统资源的开放(包括数据和功能)对用户隐私以及其他应用带来的风险低。
APL等级为normal及以上的应用。
system_basic允许应用访问操作系统基础服务(系统提供或者预置的基础功能)相关的资源,如系统设置、身份认证等。
这些系统资源的开放对用户隐私以及其他应用带来的风险较高。
APL等级为system_basic及以上的应用。
system_core涉及开放操作系统核心资源的访问操作。这部分系统资源是系统最核心的底层服务,如果遭受破坏,操作系统将无法正常运行。- APL等级为system_core的应用。
- 仅对系统应用开放。

2.3、解读

  • 我们开发的是应用,应用APL等级如何设置呢?(写完文章过程中发现5.0DevEco还不支持OpenHarmony项目新建,后面以DevEco 4.1Release为例)

    应用的实际APL等级是根据编译工具链的配置文件(OpenHarmony\Sdk\11\toolchains\lib\UnsgnedDebugProfileTemplate.json和UnsgnedReleasedProfileTemplate.json)来的,一个对应debug按钮编译产物,一个对应Run按钮编译产物。UnsgnedReleasedProfileTemplate文件如下。

    {
        "version-name":"2.0.0",
        "version-code":2,
        "app-distribution-type":"os_integration",
        "uuid":"---",
        "validity":{
            "not-before":1594865258,
            "not-after":1689473258
        },
        "type":"release",
        "bundle-info":{
            "developer-id":"OpenHarmony",
            "distribution-certificate":"",
            "bundle-name":"com",
            "apl":"system_core",
            "app-feature":"hos_system_app"
        },
        "acls":{
            "allowed-acls":[
                ""
            ]
        },
        "permissions":{
            "restricted-permissions":[]
        },
        "issuer":"pki_internal"
    }
    

    我们可以使用acl使能,也可以修改apl和app-feature字段来实现提权。改成上述文件,应用就是系统应用。

  • 权限APL等级如何设置?权限APL等级是编译时就确定的,已经烧录在系统中,无法直接更改,查看方式为

    img


    以这个ohos.permission.CONTROL_LOCATION_SWITCH权限为例,他就是system_core级别的权限,授权方式是system_grant。

3、应用申请权限的步骤

3.1、normal等级应用申请权限的方式

权限类型授权方式操作路径
所有应用可申请system_grant声明权限 > 访问接口
所有应用可申请user_grant声明权限 > 向用户申请授权 > 访问接口
允许normal等级应用通过ACL跨级申请system_grant声明权限 > 声明ACL权限 > 访问接口
允许normal等级应用通过ACL跨级申请user_grant声明权限 > 声明ACL权限 > 向用户申请授权 > 访问接口

说明:

  • 如果system_basic等级的权限,ACL使能为false,则normal等级应用无法申请该权限。
  • 当前可通过DevEco Studio完成ACL方式跨级别申请权限,但该方法仅用于应用调试阶段使用,不可用于发布上架应用市场。如果需要开发商用版本的应用,请在对应的应用市场进行发布证书和Profile文件的申请。

3.2、system_basic等级应用申请权限的方式

权限等级授权方式ACL使能操作路径
normal、system_basicsystem_grant-声明权限 > 访问接口
normal、system_basicuser_grant-声明权限 > 向用户申请授权 > 访问接口
system_coresystem_granttrue声明权限 > 声明ACL权限 > 访问接口
system_coreuser_granttrue声明权限 > 声明ACL权限 > 向用户申请授权 > 访问接口

如果应用需要将自身的APL等级声明为system_basic及以上,在开发应用安装包时,需要修改应用的HarmonyAppProvision配置文件即SDK目录下的“Toolchains / _{Version} _/ lib / UnsgnedReleasedProfileTemplate.json”文件),并重新进行应用签名。

修改方式:

HarmonyAppProvision配置文件示例如下所示,修改"bundle-info" > “apl” 字段。

"bundle-info" : {
    // ...
    "apl": "system_basic",
    // ...
},
json

说明: 直接修改HarmonyAppProvision配置文件的方式,仅用于应用调试阶段使用,不可用于发布上架应用市场。如果需要开发商用版本的应用,请在对应的应用市场进行发布证书和Profile文件的申请。

3.3、解读

  • 应用可以申请的权限,不能大于自身权限(system_core>system_basic>normal),如果越权,要么ACL使能,要么修改自身应用权限等级。
  • 所有权限使用第一步都是在配置文件module.json5中声明,如果是user_grant,需要弹窗让用户手动点击授权,具体实现方式在(('4、如何获取应用权限?'))
  • 如果修改了应用APL等级((('2.3、解读'))),就不需要ACL使能步骤,参考(('3.2、system_basic等级应用申请权限的方式'))的前两行。

4、如何获取应用权限?

  • 权限类型可分为system_grant(系统授权)和user_grant(用户授权),所有权限获取都必须先在配置文件中声明。

4.1、如何获取system_grant权限

  • 如果在应用中申请了system_grant权限,那么系统会在用户安装应用时,自动把相应权限授予给应用

4.2、如何获取user_grant权限

  • user_grant权限在用户手动允许授权后,应用才会真正获取相应权限,从而成功访问操作目标对象。

  • 首先明确一点, ArkTs应用和Native(C++)应用都是ArkTs写页面逻辑,唯一区别是函数实现是JS还是C++。而对于申请权限,我们可以看成是页面逻辑的一部分,此时申请权限对ArkTs应用和Native(C++)应用是通用的。先给出配置文件申明,再给出具体ets实现。

        "requestPermissions": [
          //user_grant权限申明方式
          {
            "name": "ohos.permission.GET_DEFAULT_APPLICATION",
            "reason": "$string:module_desc",
            "usedScene": {
              "abilities": [
                "UIAbility"
              ],
              "when": "inuse"
            }
          },
          //system_grant权限申明方式1
          {
            "name": "ohos.permission.GET_NETWORK_INFO",
            "reason": "$string:module_desc",
            "usedScene": {
              "abilities": [
                "UIAbility"
              ],
              "when": "always"
            }
          },
          //system_grant权限申明方式2,不推荐
          {
            "name": "ohos.permission.GET_NETWORK_INFO",
          },
        ]
    
    import abilityAccessCtrl, { PermissionRequestResult } from '@ohos.abilityAccessCtrl';
    import { Permissions } from '@ohos.abilityAccessCtrl';
    import bundleManager from '@ohos.bundle.bundleManager';
    import common from '@ohos.app.ability.common';
    
    const TAG: string = '[Permission]';
    
    const PERMISSIONS: Array<Permissions> = [
      "ohos.permission.MICROPHONE",
    ];
    
    export default async function grantPermission(context: common.UIAbilityContext): Promise<boolean> {
      try {
        // 获取应用程序的accessTokenID
        let bundleInfo: bundleManager.BundleInfo =
          await bundleManager.getBundleInfoForSelf(
            bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION
          );
        let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
        let tokenId = appInfo.accessTokenId;
    
        let atManager = abilityAccessCtrl.createAtManager();
        let pems: Array<Permissions> = [];
    
        for (let i = 0; i < PERMISSIONS.length; i++) {
          let state = await atManager.checkAccessToken(tokenId, PERMISSIONS[i]);
          console.info(TAG + `grantPermission  checkAccessToken ${PERMISSIONS[i]} + : ${JSON.stringify(state)}`);
          if (state !== abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
            pems.push(PERMISSIONS[i]);
          }
        }
        if (pems.length > 0) {
          console.info(TAG + 'grantPermission  requestPermissionsFromUser :' + JSON.stringify(pems));
          let ctx: common.UIAbilityContext = context;
          let result: PermissionRequestResult = await atManager.requestPermissionsFromUser(ctx, pems);
    
          let grantStatus: Array<number> = result.authResults;
          let length: number = grantStatus.length;
          for (let i = 0; i < length; i++) {
            console.info(TAG +
              `grantPermission  requestPermissionsFromUser ${result.permissions[i]} + : ${grantStatus[i]}`);
    
            if (grantStatus[i] === 0) {
              // 用户授权,可以继续访问目标操作
            } else {
              // 用户拒绝授权,提示用户必须授权才能访问当前页面的功能
              console.log(TAG + 'grantPermission  fail ');
              return false;
            }
          }
        }
        // 授权成功
        console.info(TAG + 'grantPermission  success ');
        return true;
      } catch (e) {
        console.info(TAG + 'grantPermission  fail ');
        return false;
      }
    }
    

    console.log可以换成Hilog,context可以通过createWindow时set全局变量,再使用时get全局变量的方式获取,PERMISSIONS也可以通过传参获取。为了尽量减少额外依赖,换成了上面这个版本。

    使用方法如下,在页面初始化时弹窗获取。

    import grantPermission from '../utils/PerssionUtils';
    import { BusinessError } from '@kit.BasicServicesKit';
    import { common } from '@kit.AbilityKit';
    
    @Entry
    @Component
    struct Index {
      async aboutToAppear(): Promise<void> {
        // 获取权限
        await grantPermission(getContext(this) as common.UIAbilityContext).catch((rej: BusinessError) => {
          console.info('TAG' + `权限申请失败  ${JSON.stringify(rej)}`);
        });
      }
    
      build() {
        Column() {
          Text('Hello')
            .fontSize(50)
        }
        .justifyContent(FlexAlign.Center)
        .alignItems(HorizontalAlign.Center)
        .backgroundColor('#EDF1FC')
        .height('100%')
        .width('100%')
      }
    }
    

    效果(5.0弹窗默认居中显示,4.1默认底部显示)

    img

5、应用权限列表及使用方法

5.1、public-sdk下normal权限user_grant授权

  1. 假设我们的应用需要使用分布式能力的查询可信设备列表接口,可以看到标明需要ohos.permission.DISTRIBUTED_DATASYNC权限。

    img

  2. 然后我们根据权限名去那两个网址查询,可以看到为normal权限,普通应用可以用。授权方式是user_grant,需要弹窗让用户确认。

    img

  3. 修改配置文件module.json5。然后使用(('4.2、如何获取user_grant权限'))的grantPermission函数进行弹窗授权。

    "requestPermissions": [
          //user_grant权限申明方式
          {
            "name": "ohos.permission.DISTRIBUTED_DATASYNC",
            "reason": "$string:module_desc",
            "usedScene": {
              "abilities": [
                "UIAbility"
              ],
              "when": "inuse"
            }
          },
    ]
    
  4. 用户点击允许,就可以开始调用getAvailableDeviceListSync。否则会报权限相关错误。

    import { distributedDeviceManager } from '@kit.DistributedServiceKit';
    import { BusinessError } from '@kit.BasicServicesKit';
    
    try {
      let deviceInfoList: Array<distributedDeviceManager.DeviceBasicInfo> = dmInstance.getAvailableDeviceListSync();
    } catch (err) {
      let e: BusinessError = err as BusinessError;
      console.error('getAvailableDeviceListSync errCode:' + e.code + ',errMessage:' + e.message);
    }
    

5.2、fullsdk下system_basic权限system_grant授权

  1. 假设我们的应用需要使用分布式回复用户UI的操作,可以看到标明需要ohos.permission.ACCESS_SERVICE_DM权限,同时也是系统接口。

    • 对于系统接口,需要使用full_sdk,可以看我的另一篇文章应用开发基础之fullsdk使用。
    • 对于权限ohos.permission.ACCESS_SERVICE_DM,我们直接去上面两个网址查询。

    img

  2. 可以看到为system_basic权限,普通应用不可以用。授权方式是system_grant。此处我们选择修改应用APL等级。直接修改配置文件就可以使用

    img

  3. 参考(('2.3、解读'))修改应用APL等级为system_core,然后修改配置文件module.json5。

        "requestPermissions": [
          //system_grant权限申明方式
          {
            "name": "ohos.permission.GET_DEFAULT_APPLICATION",
            "reason": "$string:module_desc",
            "usedScene": {
              "abilities": [
                "UIAbility"
              ],
              "when": "always"
            }
          },
        ]
    
  4. 至此就可以开始使用权限对应的接口。

    import { BusinessError } from '@kit.BasicServicesKit';
    
    try {
      /*
        action = 0 - 允许授权
        action = 1 - 取消授权
        action = 2 - 授权框用户操作超时
        action = 3 - 取消pin码框展示
        action = 4 - 取消pin码输入框展示
        action = 5 - pin码输入框确定操作
      */
      let operation = 0;
      dmInstance.replyUiAction(operation, 'extra');
    } catch (err) {
      let e: BusinessError = err as BusinessError;
      console.error('replyUiAction errCode:' + e.code + ',errMessage:' + e.message);
    }
    

6、结语

应该会更新文档,有问题可以先提,到时候统一改。

参考链接

访问控制概述

应用权限管控概述

选择申请权限的方式

声明权限

向用户申请授权

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐