content: /*
   * Copyright (C) 2018 The Android Open Source Project
   *
   * 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 com.android.server.uri;
  
  import android.app.GrantedUriPermission;
  import android.content.Intent;
  import android.os.Binder;
  import android.os.UserHandle;
  import android.util.ArraySet;
  import android.util.Log;
  import android.util.Slog;
  
  import com.google.android.collect.Sets;
  
  import java.io.PrintWriter;
  import java.util.Comparator;
  
  /**
   * Description of a permission granted to an app to access a particular URI.
   *
   * CTS tests for this functionality can be run with "runtest cts-appsecurity".
   *
   * Test cases are at cts/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/
   *      src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
   */
  final class UriPermission {
      private static final String TAG = "UriPermission";
  
      public static final int STRENGTH_NONE = 0;
      public static final int STRENGTH_OWNED = 1;
      public static final int STRENGTH_GLOBAL = 2;
      public static final int STRENGTH_PERSISTABLE = 3;
  
      final int targetUserId;
      final String sourcePkg;
      final String targetPkg;
  
      /** Cached UID of {@link #targetPkg}; should not be persisted */
      final int targetUid;
  
      final GrantUri uri;
  
      /**
       * Allowed modes. All permission enforcement should use this field. Must
       * always be a combination of {@link #ownedModeFlags},
       * {@link #globalModeFlags}, {@link #persistableModeFlags}, and
       * {@link #persistedModeFlags}. Mutations <em>must</em> only be performed by
       * the owning class.
       */
      int modeFlags = 0;
  
      /** Allowed modes with active owner. */
      int ownedModeFlags = 0;
      /** Allowed modes without explicit owner. */
      int globalModeFlags = 0;
      /** Allowed modes that have been offered for possible persisting. */
      int persistableModeFlags = 0;
  
      /** Allowed modes that should be persisted across device boots. */
      int persistedModeFlags = 0;
  
      /**
       * Timestamp when {@link #persistedModeFlags} was first defined in
       * {@link System#currentTimeMillis()} time base.
       */
      long persistedCreateTime = INVALID_TIME;
  
      private static final long INVALID_TIME = Long.MIN_VALUE;
  
      private ArraySet<UriPermissionOwner> mReadOwners;
      private ArraySet<UriPermissionOwner> mWriteOwners;
  
      private String stringName;
  
      UriPermission(String sourcePkg, String targetPkg, int targetUid, GrantUri uri) {
          this.targetUserId = UserHandle.getUserId(targetUid);
          this.sourcePkg = sourcePkg;
          this.targetPkg = targetPkg;
          this.targetUid = targetUid;
          this.uri = uri;
      }
  
      private void updateModeFlags() {
          final int oldModeFlags = modeFlags;
          modeFlags = ownedModeFlags | globalModeFlags | persistableModeFlags | persistedModeFlags;
  
          if (Log.isLoggable(TAG, Log.VERBOSE) && (modeFlags != oldModeFlags)) {
              Slog.d(TAG,
                      "Permission for " + targetPkg + " to " + uri + " is changing from 0x"
                              + Integer.toHexString(oldModeFlags) + " to 0x"
                              + Integer.toHexString(modeFlags) + " via calling UID "
                              + Binder.getCallingUid() + " PID " + Binder.getCallingPid(),
                      new Throwable());
          }
      }
  
      /**
       * Initialize persisted modes as read from file. This doesn't issue any
       * global or owner grants.
       */
      void initPersistedModes(int modeFlags, long createdTime) {
          modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
                  | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
  
          persistableModeFlags = modeFlags;
          persistedModeFlags = modeFlags;
          persistedCreateTime = createdTime;
  
          updateModeFlags();
      }
  
      void grantModes(int modeFlags, UriPermissionOwner owner) {
          final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0;
          modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
                  | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
  
          if (persistable) {
              persistableModeFlags |= modeFlags;
          }
  
          if (owner == null) {
              globalModeFlags |= modeFlags;
          } else {
              if ((modeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
                  addReadOwner(owner);
              }
              if ((modeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
                  addWriteOwner(owner);
              }
          }
  
          updateModeFlags();
      }
  
      /**
       * @return if mode changes should trigger persisting.
       */
      boolean takePersistableModes(int modeFlags) {
          modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
                  | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
  
          if ((modeFlags & persistableModeFlags) != modeFlags) {
              Slog.w(TAG, "Requested flags 0x"
                      + Integer.toHexString(modeFlags) + ", but only 0x"
                      + Integer.toHexString(persistableModeFlags) + " are allowed");
              return false;
          }
  
          final int before = persistedModeFlags;
          persistedModeFlags |= (persistableModeFlags & modeFlags);
  
          if (persistedModeFlags != 0) {
              persistedCreateTime = System.currentTimeMillis();
          }
  
          updateModeFlags();
          return persistedModeFlags != before;
      }
  
      boolean releasePersistableModes(int modeFlags) {
          modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
                  | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
  
          final int before = persistedModeFlags;
  
          persistableModeFlags &= ~modeFlags;
          persistedModeFlags &= ~modeFlags;
  
          if (persistedModeFlags == 0) {
              persistedCreateTime = INVALID_TIME;
          }
  
          updateModeFlags();
          return persistedModeFlags != before;
      }
  
      /**
       * @return if mode changes should trigger persisting.
       */
      boolean revokeModes(int modeFlags, boolean includingOwners) {
          final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0;
          modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
                  | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
  
          final int before = persistedModeFlags;
  
          if ((modeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
              if (persistable) {
                  persistableModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
                  persistedModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
              }
              globalModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
              if (mReadOwners != null && includingOwners) {
                  ownedModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
                  for (UriPermissionOwner r : mReadOwners) {
                      r.removeReadPermission(this);
                  }
                  mReadOwners = null;
              }
          }
          if ((modeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
              if (persistable) {
                  persistableModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
                  persistedModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
              }
              globalModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
              if (mWriteOwners != null && includingOwners) {
                  ownedModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
                  for (UriPermissionOwner r : mWriteOwners) {
                      r.removeWritePermission(this);
                  }
                  mWriteOwners = null;
              }
          }
  
          if (persistedModeFlags == 0) {
              persistedCreateTime = INVALID_TIME;
          }
  
          updateModeFlags();
          return persistedModeFlags != before;
      }
  
      /**
       * Return strength of this permission grant for the given flags.
       */
      public int getStrength(int modeFlags) {
          modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
                  | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
          if ((persistableModeFlags & modeFlags) == modeFlags) {
              return STRENGTH_PERSISTABLE;
          } else if ((globalModeFlags & modeFlags) == modeFlags) {
              return STRENGTH_GLOBAL;
          } else if ((ownedModeFlags & modeFlags) == modeFlags) {
              return STRENGTH_OWNED;
          } else {
              return STRENGTH_NONE;
          }
      }
  
      private void addReadOwner(UriPermissionOwner owner) {
          if (mReadOwners == null) {
              mReadOwners = Sets.newArraySet();
              ownedModeFlags |= Intent.FLAG_GRANT_READ_URI_PERMISSION;
              updateModeFlags();
          }
          if (mReadOwners.add(owner)) {
              owner.addReadPermission(this);
          }
      }
  
      /**
       * Remove given read owner, updating {@Link #modeFlags} as needed.
       */
      void removeReadOwner(UriPermissionOwner owner) {
          if (!mReadOwners.remove(owner)) {
              Slog.wtf(TAG, "Unknown read owner " + owner + " in " + this);
          }
          if (mReadOwners.size() == 0) {
              mReadOwners = null;
              ownedModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
              updateModeFlags();
          }
      }
  
      private void addWriteOwner(UriPermissionOwner owner) {
          if (mWriteOwners == null) {
              mWriteOwners = Sets.newArraySet();
              ownedModeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
              updateModeFlags();
          }
          if (mWriteOwners.add(owner)) {
              owner.addWritePermission(this);
          }
      }
  
      /**
       * Remove given write owner, updating {@Link #modeFlags} as needed.
       */
      void removeWriteOwner(UriPermissionOwner owner) {
          if (!mWriteOwners.remove(owner)) {
              Slog.wtf(TAG, "Unknown write owner " + owner + " in " + this);
          }
          if (mWriteOwners.size() == 0) {
              mWriteOwners = null;
              ownedModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
              updateModeFlags();
          }
      }
  
      @Override
      public String toString() {
          if (stringName != null) {
              return stringName;
          }
          StringBuilder sb = new StringBuilder(128);
          sb.append("UriPermission{");
          sb.append(Integer.toHexString(System.identityHashCode(this)));
          sb.append(' ');
          sb.append(uri);
          sb.append('}');
          return stringName = sb.toString();
      }
  
      void dump(PrintWriter pw, String prefix) {
          pw.print(prefix);
          pw.print("targetUserId=" + targetUserId);
          pw.print(" sourcePkg=" + sourcePkg);
          pw.println(" targetPkg=" + targetPkg);
  
          pw.print(prefix);
          pw.print("mode=0x" + Integer.toHexString(modeFlags));
          pw.print(" owned=0x" + Integer.toHexString(ownedModeFlags));
          pw.print(" global=0x" + Integer.toHexString(globalModeFlags));
          pw.print(" persistable=0x" + Integer.toHexString(persistableModeFlags));
          pw.print(" persisted=0x" + Integer.toHexString(persistedModeFlags));
          if (persistedCreateTime != INVALID_TIME) {
              pw.print(" persistedCreate=" + persistedCreateTime);
          }
          pw.println();
  
          if (mReadOwners != null) {
              pw.print(prefix);
              pw.println("readOwners:");
              for (UriPermissionOwner owner : mReadOwners) {
                  pw.print(prefix);
                  pw.println("  * " + owner);
              }
          }
          if (mWriteOwners != null) {
              pw.print(prefix);
              pw.println("writeOwners:");
              for (UriPermissionOwner owner : mReadOwners) {
                  pw.print(prefix);
                  pw.println("  * " + owner);
              }
          }
      }
  
      public static class PersistedTimeComparator implements Comparator<UriPermission> {
          @Override
          public int compare(UriPermission lhs, UriPermission rhs) {
              return Long.compare(lhs.persistedCreateTime, rhs.persistedCreateTime);
          }
      }
  
      /**
       * Snapshot of {@link UriPermission} with frozen
       * {@link UriPermission#persistedModeFlags} state.
       */
      public static class Snapshot {
          final int targetUserId;
          final String sourcePkg;
          final String targetPkg;
          final GrantUri uri;
          final int persistedModeFlags;
          final long persistedCreateTime;
  
          private Snapshot(UriPermission perm) {
              this.targetUserId = perm.targetUserId;
              this.sourcePkg = perm.sourcePkg;
              this.targetPkg = perm.targetPkg;
              this.uri = perm.uri;
              this.persistedModeFlags = perm.persistedModeFlags;
              this.persistedCreateTime = perm.persistedCreateTime;
          }
      }
  
      public Snapshot snapshot() {
          return new Snapshot(this);
      }
  
      public android.content.UriPermission buildPersistedPublicApiObject() {
          return new android.content.UriPermission(uri.uri, persistedModeFlags, persistedCreateTime);
      }
  
      public GrantedUriPermission buildGrantedUriPermission() {
          return new GrantedUriPermission(uri.uri, targetPkg);
      }
  }
  