系统应用根据Uri授予权限的正确姿势

在我们印象中,Android6.0以后访问外部的媒体资源文件都是需要申请READ_EXTERNAL_STORAGE才可以正常访问,思考一个场景,假如我们不申请该权限,使用系统的Intent.ACTION_PICK意图跳转系统图库选取图片是否可以正常显示该图片?

答案是可以的,这是为什么呢?我们都没有申请权限,或者说是谁给了我们这个权限?带着这个疑问我们先来了解下UriPermission

UriPermission

Allow access on a per-URI basis

You can also grant permissions on a per-URI basis. When starting an activity or returning a result to an activity, set the Intent.FLAG_GRANT_READ_URI_PERMISSION, intent flag, the Intent.FLAG_GRANT_WRITE_URI_PERMISSION, intent flag, or both flags. This gives another app read, write, and read/write permissions, respectively, for the data URI that's included in the intent. The other app gains these permissions for the specific URI regardless of whether it has permission to access data in the content provider more generally.

上面是Android官网的解释,大概意思是,你可以进一步对其他应用如何访问你应用的Contete Provider或者数据URI进行精细控制,可以通过读写权限来保护自己,根据 URI 授予权限。

在启动 activity 或将结果返回给 activity 时,请设置 Intent.FLAG_GRANT_READ_URI_PERMISSION intent 标志、Intent.FLAG_GRANT_WRITE_URI_PERMISSIONintent 标志或者同时设置两者。

这样便可针对 intent 中包含的数据 URI 分别向另一个应用授予读取权限、写入权限和读写权限。

理解完UriPermission,上面的问题就可以解释了,虽然我们没有申请读取的权限,但是系统图库在选图后将结果返回activity时设置了Intent.FLAG_GRANT_READ_URI_PERMISSION,这样我们就具有了读取该Uri指定图片的权限。

理想很丰满,现实很骨感。

背景

最近在项目中上线一款自研的图库应用(systemUid),支持系统默认选图action跳转,给调用者返回已选的Uri资源地址,因为安全合规整改的原因,一些第三方应用去掉了读写权限的申请,问题就被暴露出来了,第三方应用无法正常通过图库获取到图片资源。

分析

分析堆栈信息可以定位到,是调用者没有访问Uri的权限导致的异常,而我们自研图库在选图回传的时候是有设置FLAG_GRANT_READ_URI_PERMISSION,把URI的临时访问权限传递给调用者,且报错的堆栈打印是在第三方应用,所以可以初步判断问题应该是出自系统的权限授予过程。

我们可以通过context.grantUriPermission()作为切入点,来分析下系统是如何授予Uri权限

最终调用的是UriGrantsManagerService$checkGrantUriPermission()

checkGrantUriPermission

int checkGrantUriPermission(int callingUid, String targetPkg, GrantUri grantUri,
        final int modeFlags, int lastTargetUid) {
   ....
    // Bail early if system is trying to hand out permissions directly; it
    // must always grant permissions on behalf of someone explicit.
    final int callingAppId = UserHandle.getAppId(callingUid);
    if ((callingAppId == SYSTEM_UID) || (callingAppId == ROOT_UID)) {
        if ("com.android.settings.files".equals(grantUri.uri.getAuthority())
                || "com.android.settings.module_licenses".equals(grantUri.uri.getAuthority())) {
            // Exempted authority for
            // 1. cropping user photos and sharing a generated license html
            //    file in Settings app
            // 2. sharing a generated license html file in TvSettings app
            // 3. Sharing module license files from Settings app
        } else {
            Slog.w(TAG, "For security reasons, the system cannot issue a Uri permission"
                      " grant to "   grantUri   "; use startActivityAsCaller() instead");
            return -1;
        }
    }
    ....
}
复制代码

问题就是出现在这里,如果你的应用是root用户,或者是具有系统级权限(systemUid),并且提供的Uri的authority不是指定的,就会拒绝授权 (return -1),也就是说这个Uri的权限并没有传递成功。

这一点在上面的日志也有体现。

/system_process W UriGrantsManagerService: For security reasons, the system cannot issue a Uri permission grant to

系统为什么要这么做?

注释里面有说,出于安全原因,系统应用不能使用startActivityAsCaller()来直接授予Uri权限,它必须显式地让应用自己授予权限。只有以下几种情况才会豁免授权

  • 裁剪用户照片并共享生成的许可HTML文件的设置应用程序
  • 在TvSettings app中共享生成的许可html文件
  • 从设置应用程序共享模块license文件

调用者端

分析完图库端,我们再来看下调用者端的异常堆栈打印

客户端远程调用服务端打开指定文件,然后服务端把文件描述符跨进程传递到客户端(后面Binder驱动跨进程传递文件描述符就不展开分析)

通过分析时序图,可以定位到报错的堆栈信息是发生在enforceCallingPermissionInternal()

enforceCallingPermissionInternal()

 private void enforceCallingPermissionInternal(Uri uri, boolean forWrite) {
          ... // 省略部分代码
          // First, check to see if caller has direct write access
          if (forWrite) {
              final SQLiteQueryBuilder qb = getQueryBuilder(TYPE_UPDATE, uri, table, null);
              try (Cursor c = qb.query(db, new String[0], null, null, null, null, null)) {
                  if (c.moveToFirst()) {
                      // Direct write access granted, yay!
                      return;
                  }
              }
          }
          ... // 省略部分代码
          // Second, check to see if caller has direct read access
          final SQLiteQueryBuilder qb = getQueryBuilder(TYPE_QUERY, uri, table, null);
          try (Cursor c = qb.query(db, new String[0], null, null, null, null, null)) {
              if (c.moveToFirst()) {
                  if (!forWrite) {
                      // Direct read access granted, yay!
                      return;
                  } else if (allowUserGrant) {
                      // Caller has read access, but they wanted to write, and
                      // they'll need to get the user to grant that access
                      final Context context = getContext();
                      final PendingIntent intent = PendingIntent.getActivity(context, 42,
                              new Intent(null, uri, context, PermissionActivity.class),
                              FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE);
 
                      final Icon icon = getCollectionIcon(uri);
                      final RemoteAction action = new RemoteAction(icon,
                              context.getText(R.string.permission_required_action),
                              context.getText(R.string.permission_required_action),
                              intent);
 
                      throw new RecoverableSecurityException(new SecurityException(
                              getCallingPackageOrSelf()   " has no access to "   uri),
                              context.getText(R.string.permission_required), action);
                  }
              }
          }
 
          throw new SecurityException(getCallingPackageOrSelf()   " has no access to "   uri);
      }

在这个方法里面,它会根据参数forWrite判断当前调用者是否拥有读写权限,如果没有则会抛出异常提示,和上面报错的异常堆栈符合。

解决办法

以下两种方法都可以

  • 去除应用systemUid配置
  • 修改framework源码

考虑到修改系统源码的影响面比较大,所以采用去除systemUid的方式,再次验证跳转选图后可以正常加载。查看系统原生图库的清单文件配置,也是没有设置systemUid,原生图库也是采用了这种方式。

扩展

系统原生图库既不是系统应用,也没有动态申请存储权限,那它是怎么获取系统的存储权限的?

如果有了解过系统权限的授予流程,可以知道Android系统在开机后会对一些特殊的应用进行自动授权,而运行时权限的默认授予工作由DefaultPermissionGrantPolicy类的grantDefaultPermissions方法完成。

在这个方法里面可以看到它对图库进行默认授予存储权限的代码,具体是通过查找清单文件配置的category是Intent.CATEGORY_APP_GALLERY的应用。

以上就是系统应用根据Uri授予权限方法详解的详细内容,更多关于系统应用Uri授权的资料请关注Devmax其它相关文章!

系统应用根据Uri授予权限方法详解的更多相关文章

  1. ios – 使用简洁的NSManagedObjectID URI形式?

    我想避免字符串操作,因为感觉到icky,但我会考虑它,如果这是唯一的方法.我唯一的混淆是在上面的例子中“EE13EA1E-D5F4-4E38-986D-3F4B0B03AEE4”部分来自哪里.为了重建一个有效的URI,我该如何访问该值?

  2. swift – 如何URI编码图像?

    究竟是什么意思?这是否意味着将图像转换为base64字符串,然后将其传递给请求?

  3. 如何在Android 4.0中的HTML5VFullScreen $SurfaceVideoView中获取HTML5视频URI?

    我想在用户点击视频控制栏中的全屏按钮时获取HTML5视频URI.根据this,Android4.0中的HTML5视频视图是SurfaceView,而不是VideoView.有人能告诉我如何在SurfaceVideoView中获取URI吗?这是我的代码.非常感谢.解决方法容易,使用反射.把它放在onShowCustomView()方法中:

  4. uri – 将android手机号码标签ID翻译成字符串

    嗨,我正在写一个小型的Android应用程序,密切工作的白色手机标签,但我不明白我是如何调整翻译Documentation中描述的uri值.我想要做的是将TYPE_HOME转换为Home等等.我目前的解决方案是列出所有已翻译的字符串,但它已经提出了很多问题.但我希望能够像地址簿和其他应用程序一样使用它.解决方法Android有一个内置的方法来做到这一点……

  5. 如何在我的Android应用程序中获取SQLite数据库的URI?

    我有一个带有名为“myTestDB”的数据库的Android应用程序,其中包含一个名为“list_items”的表.我想使用CursorgetContentResolver().query()方法来获取要添加到SimpleCursorAdapter的游标.query()方法的第一个参数是一个URI,我不确定URI应该是什么样子.解决方法它是相当简单的方法调用看起来像这样mDataBase.quer

  6. android.database.CursorIndexOutOfBoundsException:索引-1请求

    试试这个

  7. 在Android设备中使用ACTION_PICK意图仅显示电话号码的联系人

    我尝试浏览stackoverflow和其他网站中的所有线程,但找不到解决此问题的任何解决方案,尽管许多人已发布此问题.我没有在Android平台上工作太多,我可能错过了一些细微的细节,我相信必须有一个简单的方法来实现这一点.请建议.感谢您的帮助.谢谢.解决方法请使用以下代码

  8. android – 如何获取刚从相机捕获的图像路径

    下面是我的代码但是没有给我onActivity结果中的图像路径解决方法这对我有用……

  9. Android从Google云端硬盘获取Uri路径

    我有这个代码将文件上传到我的应用程序,当用文件管理器,dropBox或其他任何东西打开文件时,返回的路径是正确的,我可以访问它,我只是遇到谷歌驱动器的问题,它返回一些以“exposed_content”开头的路径,我不能以任何方式“解码”它,我搜索过并没有找到办法,任何人都有任何想法?解决方法使用附加的代码…从onActivity结果你将得到内容uri…将此uri传递给给定的方法…

  10. Android SyncAdapter回调

    我已经在SDK中的SimpleSyncAdapter示例项目中实现了SyncAdapter,AccountManager和私有ContentProvider.一切运作良好.现在,我想在从具有特定标志集的远程服务器下载新行时向用户显示一条消息.当Sync完成时,我需要从SyncAdapter进行回调,以便我可以执行查询并显示来自活动的消息.我在StackOverflow上看到了一些关于这个问题的问题

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部