Home | History | Annotate | Download | only in slice
      1 /*
      2  * Copyright (C) 2018 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
      5  * except in compliance with the License. You may obtain a copy of the License at
      6  *
      7  *      http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the
     10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     11  * KIND, either express or implied. See the License for the specific language governing
     12  * permissions and limitations under the License.
     13  */
     14 
     15 package com.android.server.slice;
     16 
     17 import android.annotation.NonNull;
     18 import android.content.ContentResolver;
     19 import android.net.Uri;
     20 import android.text.TextUtils;
     21 import android.util.ArrayMap;
     22 import android.util.ArraySet;
     23 import android.util.Slog;
     24 
     25 import com.android.server.slice.DirtyTracker.Persistable;
     26 import com.android.server.slice.SlicePermissionManager.PkgUser;
     27 
     28 import org.xmlpull.v1.XmlPullParser;
     29 import org.xmlpull.v1.XmlPullParserException;
     30 import org.xmlpull.v1.XmlSerializer;
     31 
     32 import java.io.IOException;
     33 import java.util.ArrayList;
     34 import java.util.Collection;
     35 import java.util.Comparator;
     36 import java.util.List;
     37 import java.util.Objects;
     38 import java.util.stream.Collectors;
     39 
     40 public class SliceClientPermissions implements DirtyTracker, Persistable {
     41 
     42     private static final String TAG = "SliceClientPermissions";
     43 
     44     static final String TAG_CLIENT = "client";
     45     private static final String TAG_AUTHORITY = "authority";
     46     private static final String TAG_PATH = "path";
     47     private static final String NAMESPACE = null;
     48 
     49     private static final String ATTR_PKG = "pkg";
     50     private static final String ATTR_AUTHORITY = "authority";
     51     private static final String ATTR_FULL_ACCESS = "fullAccess";
     52 
     53     private final PkgUser mPkg;
     54     // Keyed off (authority, userId) rather than the standard (pkg, userId)
     55     private final ArrayMap<PkgUser, SliceAuthority> mAuths = new ArrayMap<>();
     56     private final DirtyTracker mTracker;
     57     private boolean mHasFullAccess;
     58 
     59     public SliceClientPermissions(@NonNull PkgUser pkg, @NonNull DirtyTracker tracker) {
     60         mPkg = pkg;
     61         mTracker = tracker;
     62     }
     63 
     64     public PkgUser getPkg() {
     65         return mPkg;
     66     }
     67 
     68     public synchronized Collection<SliceAuthority> getAuthorities() {
     69         return new ArrayList<>(mAuths.values());
     70     }
     71 
     72     public synchronized SliceAuthority getOrCreateAuthority(PkgUser authority, PkgUser provider) {
     73         SliceAuthority ret = mAuths.get(authority);
     74         if (ret == null) {
     75             ret = new SliceAuthority(authority.getPkg(), provider, this);
     76             mAuths.put(authority, ret);
     77             onPersistableDirty(ret);
     78         }
     79         return ret;
     80     }
     81 
     82     public synchronized SliceAuthority getAuthority(PkgUser authority) {
     83         return mAuths.get(authority);
     84     }
     85 
     86     public boolean hasFullAccess() {
     87         return mHasFullAccess;
     88     }
     89 
     90     public void setHasFullAccess(boolean hasFullAccess) {
     91         if (mHasFullAccess == hasFullAccess) return;
     92         mHasFullAccess = hasFullAccess;
     93         mTracker.onPersistableDirty(this);
     94     }
     95 
     96     public void removeAuthority(String authority, int userId) {
     97         if (mAuths.remove(new PkgUser(authority, userId)) != null) {
     98             mTracker.onPersistableDirty(this);
     99         }
    100     }
    101 
    102     public synchronized boolean hasPermission(Uri uri, int userId) {
    103         if (!Objects.equals(ContentResolver.SCHEME_CONTENT, uri.getScheme())) return false;
    104         SliceAuthority authority = getAuthority(new PkgUser(uri.getAuthority(), userId));
    105         return authority != null && authority.hasPermission(uri.getPathSegments());
    106     }
    107 
    108     public void grantUri(Uri uri, PkgUser providerPkg) {
    109         SliceAuthority authority = getOrCreateAuthority(
    110                 new PkgUser(uri.getAuthority(), providerPkg.getUserId()),
    111                 providerPkg);
    112         authority.addPath(uri.getPathSegments());
    113     }
    114 
    115     public void revokeUri(Uri uri, PkgUser providerPkg) {
    116         SliceAuthority authority = getOrCreateAuthority(
    117                 new PkgUser(uri.getAuthority(), providerPkg.getUserId()),
    118                 providerPkg);
    119         authority.removePath(uri.getPathSegments());
    120     }
    121 
    122     public void clear() {
    123         if (!mHasFullAccess && mAuths.isEmpty()) return;
    124         mHasFullAccess = false;
    125         mAuths.clear();
    126         onPersistableDirty(this);
    127     }
    128 
    129     @Override
    130     public void onPersistableDirty(Persistable obj) {
    131         mTracker.onPersistableDirty(this);
    132     }
    133 
    134     @Override
    135     public String getFileName() {
    136         return getFileName(mPkg);
    137     }
    138 
    139     public synchronized void writeTo(XmlSerializer out) throws IOException {
    140         out.startTag(NAMESPACE, TAG_CLIENT);
    141         out.attribute(NAMESPACE, ATTR_PKG, mPkg.toString());
    142         out.attribute(NAMESPACE, ATTR_FULL_ACCESS, mHasFullAccess ? "1" : "0");
    143 
    144         final int N = mAuths.size();
    145         for (int i = 0; i < N; i++) {
    146             out.startTag(NAMESPACE, TAG_AUTHORITY);
    147             out.attribute(NAMESPACE, ATTR_AUTHORITY, mAuths.valueAt(i).mAuthority);
    148             out.attribute(NAMESPACE, ATTR_PKG, mAuths.valueAt(i).mPkg.toString());
    149 
    150             mAuths.valueAt(i).writeTo(out);
    151 
    152             out.endTag(NAMESPACE, TAG_AUTHORITY);
    153         }
    154 
    155         out.endTag(NAMESPACE, TAG_CLIENT);
    156     }
    157 
    158     public static SliceClientPermissions createFrom(XmlPullParser parser, DirtyTracker tracker)
    159             throws XmlPullParserException, IOException {
    160         // Get to the beginning of the provider.
    161         while (parser.getEventType() != XmlPullParser.START_TAG
    162                 || !TAG_CLIENT.equals(parser.getName())) {
    163             parser.next();
    164         }
    165         int depth = parser.getDepth();
    166         PkgUser pkgUser = new PkgUser(parser.getAttributeValue(NAMESPACE, ATTR_PKG));
    167         SliceClientPermissions provider = new SliceClientPermissions(pkgUser, tracker);
    168         String fullAccess = parser.getAttributeValue(NAMESPACE, ATTR_FULL_ACCESS);
    169         if (fullAccess == null) {
    170             fullAccess = "0";
    171         }
    172         provider.mHasFullAccess = Integer.parseInt(fullAccess) != 0;
    173         parser.next();
    174 
    175         while (parser.getDepth() > depth) {
    176             if (parser.getEventType() == XmlPullParser.START_TAG
    177                     && TAG_AUTHORITY.equals(parser.getName())) {
    178                 try {
    179                     PkgUser pkg = new PkgUser(parser.getAttributeValue(NAMESPACE, ATTR_PKG));
    180                     SliceAuthority authority = new SliceAuthority(
    181                             parser.getAttributeValue(NAMESPACE, ATTR_AUTHORITY), pkg, provider);
    182                     authority.readFrom(parser);
    183                     provider.mAuths.put(new PkgUser(authority.getAuthority(), pkg.getUserId()),
    184                             authority);
    185                 } catch (IllegalArgumentException e) {
    186                     Slog.e(TAG, "Couldn't read PkgUser", e);
    187                 }
    188             }
    189 
    190             parser.next();
    191         }
    192         return provider;
    193     }
    194 
    195     public static String getFileName(PkgUser pkg) {
    196         return String.format("client_%s", pkg.toString());
    197     }
    198 
    199     public static class SliceAuthority implements Persistable {
    200         public static final String DELIMITER = "/";
    201         private final String mAuthority;
    202         private final DirtyTracker mTracker;
    203         private final PkgUser mPkg;
    204         private final ArraySet<String[]> mPaths = new ArraySet<>();
    205 
    206         public SliceAuthority(String authority, PkgUser pkg, DirtyTracker tracker) {
    207             mAuthority = authority;
    208             mPkg = pkg;
    209             mTracker = tracker;
    210         }
    211 
    212         public String getAuthority() {
    213             return mAuthority;
    214         }
    215 
    216         public PkgUser getPkg() {
    217             return mPkg;
    218         }
    219 
    220         void addPath(List<String> path) {
    221             String[] pathSegs = path.toArray(new String[path.size()]);
    222             for (int i = mPaths.size() - 1; i >= 0; i--) {
    223                 String[] existing = mPaths.valueAt(i);
    224                 if (isPathPrefixMatch(existing, pathSegs)) {
    225                     // Nothing to add here.
    226                     return;
    227                 }
    228                 if (isPathPrefixMatch(pathSegs, existing)) {
    229                     mPaths.removeAt(i);
    230                 }
    231             }
    232             mPaths.add(pathSegs);
    233             mTracker.onPersistableDirty(this);
    234         }
    235 
    236         void removePath(List<String> path) {
    237             boolean changed = false;
    238             String[] pathSegs = path.toArray(new String[path.size()]);
    239             for (int i = mPaths.size() - 1; i >= 0; i--) {
    240                 String[] existing = mPaths.valueAt(i);
    241                 if (isPathPrefixMatch(pathSegs, existing)) {
    242                     changed = true;
    243                     mPaths.removeAt(i);
    244                 }
    245             }
    246             if (changed) {
    247                 mTracker.onPersistableDirty(this);
    248             }
    249         }
    250 
    251         public synchronized Collection<String[]> getPaths() {
    252             return new ArraySet<>(mPaths);
    253         }
    254 
    255         public boolean hasPermission(List<String> path) {
    256             for (String[] p : mPaths) {
    257                 if (isPathPrefixMatch(p, path.toArray(new String[path.size()]))) {
    258                     return true;
    259                 }
    260             }
    261             return false;
    262         }
    263 
    264         private boolean isPathPrefixMatch(String[] prefix, String[] path) {
    265             final int prefixSize = prefix.length;
    266             if (path.length < prefixSize) return false;
    267 
    268             for (int i = 0; i < prefixSize; i++) {
    269                 if (!Objects.equals(path[i], prefix[i])) {
    270                     return false;
    271                 }
    272             }
    273 
    274             return true;
    275         }
    276 
    277         @Override
    278         public String getFileName() {
    279             return null;
    280         }
    281 
    282         public synchronized void writeTo(XmlSerializer out) throws IOException {
    283             final int N = mPaths.size();
    284             for (int i = 0; i < N; i++) {
    285                 out.startTag(NAMESPACE, TAG_PATH);
    286                 out.text(encodeSegments(mPaths.valueAt(i)));
    287                 out.endTag(NAMESPACE, TAG_PATH);
    288             }
    289         }
    290 
    291         public synchronized void readFrom(XmlPullParser parser)
    292                 throws IOException, XmlPullParserException {
    293             parser.next();
    294             int depth = parser.getDepth();
    295             while (parser.getDepth() >= depth) {
    296                 if (parser.getEventType() == XmlPullParser.START_TAG
    297                         && TAG_PATH.equals(parser.getName())) {
    298                     mPaths.add(decodeSegments(parser.nextText()));
    299                 }
    300                 parser.next();
    301             }
    302         }
    303 
    304         private String encodeSegments(String[] s) {
    305             String[] out = new String[s.length];
    306             for (int i = 0; i < s.length; i++) {
    307                 out[i] = Uri.encode(s[i]);
    308             }
    309             return TextUtils.join(DELIMITER, out);
    310         }
    311 
    312         private String[] decodeSegments(String s) {
    313             String[] sets = s.split(DELIMITER, -1);
    314             for (int i = 0; i < sets.length; i++) {
    315                 sets[i] = Uri.decode(sets[i]);
    316             }
    317             return sets;
    318         }
    319 
    320         /**
    321          * Only for testing, no deep equality of these are done normally.
    322          */
    323         @Override
    324         public boolean equals(Object obj) {
    325             if (!getClass().equals(obj != null ? obj.getClass() : null)) return false;
    326             SliceAuthority other = (SliceAuthority) obj;
    327             if (mPaths.size() != other.mPaths.size()) return false;
    328             ArrayList<String[]> p1 = new ArrayList<>(mPaths);
    329             ArrayList<String[]> p2 = new ArrayList<>(other.mPaths);
    330             p1.sort(Comparator.comparing(o -> TextUtils.join(",", o)));
    331             p2.sort(Comparator.comparing(o -> TextUtils.join(",", o)));
    332             for (int i = 0; i < p1.size(); i++) {
    333                 String[] a1 = p1.get(i);
    334                 String[] a2 = p2.get(i);
    335                 if (a1.length != a2.length) return false;
    336                 for (int j = 0; j < a1.length; j++) {
    337                     if (!Objects.equals(a1[j], a2[j])) return false;
    338                 }
    339             }
    340             return Objects.equals(mAuthority, other.mAuthority)
    341                     && Objects.equals(mPkg, other.mPkg);
    342         }
    343 
    344         @Override
    345         public String toString() {
    346             return String.format("(%s, %s: %s)", mAuthority, mPkg.toString(), pathToString(mPaths));
    347         }
    348 
    349         private String pathToString(ArraySet<String[]> paths) {
    350             return TextUtils.join(", ", paths.stream().map(s -> TextUtils.join("/", s))
    351                     .collect(Collectors.toList()));
    352         }
    353     }
    354 }
    355