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 SliceClientPermissions(@onNull PkgUser pkg, @NonNull DirtyTracker tracker)59 public SliceClientPermissions(@NonNull PkgUser pkg, @NonNull DirtyTracker tracker) { 60 mPkg = pkg; 61 mTracker = tracker; 62 } 63 getPkg()64 public PkgUser getPkg() { 65 return mPkg; 66 } 67 getAuthorities()68 public synchronized Collection<SliceAuthority> getAuthorities() { 69 return new ArrayList<>(mAuths.values()); 70 } 71 getOrCreateAuthority(PkgUser authority, PkgUser provider)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 getAuthority(PkgUser authority)82 public synchronized SliceAuthority getAuthority(PkgUser authority) { 83 return mAuths.get(authority); 84 } 85 hasFullAccess()86 public boolean hasFullAccess() { 87 return mHasFullAccess; 88 } 89 setHasFullAccess(boolean hasFullAccess)90 public void setHasFullAccess(boolean hasFullAccess) { 91 if (mHasFullAccess == hasFullAccess) return; 92 mHasFullAccess = hasFullAccess; 93 mTracker.onPersistableDirty(this); 94 } 95 removeAuthority(String authority, int userId)96 public void removeAuthority(String authority, int userId) { 97 if (mAuths.remove(new PkgUser(authority, userId)) != null) { 98 mTracker.onPersistableDirty(this); 99 } 100 } 101 hasPermission(Uri uri, int userId)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 grantUri(Uri uri, PkgUser providerPkg)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 revokeUri(Uri uri, PkgUser providerPkg)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 clear()122 public void clear() { 123 if (!mHasFullAccess && mAuths.isEmpty()) return; 124 mHasFullAccess = false; 125 mAuths.clear(); 126 onPersistableDirty(this); 127 } 128 129 @Override onPersistableDirty(Persistable obj)130 public void onPersistableDirty(Persistable obj) { 131 mTracker.onPersistableDirty(this); 132 } 133 134 @Override getFileName()135 public String getFileName() { 136 return getFileName(mPkg); 137 } 138 writeTo(XmlSerializer out)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 createFrom(XmlPullParser parser, DirtyTracker tracker)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 if (parser.getEventType() == XmlPullParser.END_DOCUMENT) { 164 throw new XmlPullParserException("Can't find client tag in xml"); 165 } 166 parser.next(); 167 } 168 int depth = parser.getDepth(); 169 PkgUser pkgUser = new PkgUser(parser.getAttributeValue(NAMESPACE, ATTR_PKG)); 170 SliceClientPermissions provider = new SliceClientPermissions(pkgUser, tracker); 171 String fullAccess = parser.getAttributeValue(NAMESPACE, ATTR_FULL_ACCESS); 172 if (fullAccess == null) { 173 fullAccess = "0"; 174 } 175 provider.mHasFullAccess = Integer.parseInt(fullAccess) != 0; 176 parser.next(); 177 178 while (parser.getDepth() > depth) { 179 if (parser.getEventType() == XmlPullParser.END_DOCUMENT) { 180 return provider; 181 } 182 if (parser.getEventType() == XmlPullParser.START_TAG 183 && TAG_AUTHORITY.equals(parser.getName())) { 184 try { 185 PkgUser pkg = new PkgUser(parser.getAttributeValue(NAMESPACE, ATTR_PKG)); 186 SliceAuthority authority = new SliceAuthority( 187 parser.getAttributeValue(NAMESPACE, ATTR_AUTHORITY), pkg, provider); 188 authority.readFrom(parser); 189 provider.mAuths.put(new PkgUser(authority.getAuthority(), pkg.getUserId()), 190 authority); 191 } catch (IllegalArgumentException e) { 192 Slog.e(TAG, "Couldn't read PkgUser", e); 193 } 194 } 195 196 parser.next(); 197 } 198 return provider; 199 } 200 getFileName(PkgUser pkg)201 public static String getFileName(PkgUser pkg) { 202 return String.format("client_%s", pkg.toString()); 203 } 204 205 public static class SliceAuthority implements Persistable { 206 public static final String DELIMITER = "/"; 207 private final String mAuthority; 208 private final DirtyTracker mTracker; 209 private final PkgUser mPkg; 210 private final ArraySet<String[]> mPaths = new ArraySet<>(); 211 SliceAuthority(String authority, PkgUser pkg, DirtyTracker tracker)212 public SliceAuthority(String authority, PkgUser pkg, DirtyTracker tracker) { 213 mAuthority = authority; 214 mPkg = pkg; 215 mTracker = tracker; 216 } 217 getAuthority()218 public String getAuthority() { 219 return mAuthority; 220 } 221 getPkg()222 public PkgUser getPkg() { 223 return mPkg; 224 } 225 addPath(List<String> path)226 void addPath(List<String> path) { 227 String[] pathSegs = path.toArray(new String[path.size()]); 228 for (int i = mPaths.size() - 1; i >= 0; i--) { 229 String[] existing = mPaths.valueAt(i); 230 if (isPathPrefixMatch(existing, pathSegs)) { 231 // Nothing to add here. 232 return; 233 } 234 if (isPathPrefixMatch(pathSegs, existing)) { 235 mPaths.removeAt(i); 236 } 237 } 238 mPaths.add(pathSegs); 239 mTracker.onPersistableDirty(this); 240 } 241 removePath(List<String> path)242 void removePath(List<String> path) { 243 boolean changed = false; 244 String[] pathSegs = path.toArray(new String[path.size()]); 245 for (int i = mPaths.size() - 1; i >= 0; i--) { 246 String[] existing = mPaths.valueAt(i); 247 if (isPathPrefixMatch(pathSegs, existing)) { 248 changed = true; 249 mPaths.removeAt(i); 250 } 251 } 252 if (changed) { 253 mTracker.onPersistableDirty(this); 254 } 255 } 256 getPaths()257 public synchronized Collection<String[]> getPaths() { 258 return new ArraySet<>(mPaths); 259 } 260 hasPermission(List<String> path)261 public boolean hasPermission(List<String> path) { 262 for (String[] p : mPaths) { 263 if (isPathPrefixMatch(p, path.toArray(new String[path.size()]))) { 264 return true; 265 } 266 } 267 return false; 268 } 269 isPathPrefixMatch(String[] prefix, String[] path)270 private boolean isPathPrefixMatch(String[] prefix, String[] path) { 271 final int prefixSize = prefix.length; 272 if (path.length < prefixSize) return false; 273 274 for (int i = 0; i < prefixSize; i++) { 275 if (!Objects.equals(path[i], prefix[i])) { 276 return false; 277 } 278 } 279 280 return true; 281 } 282 283 @Override getFileName()284 public String getFileName() { 285 return null; 286 } 287 writeTo(XmlSerializer out)288 public synchronized void writeTo(XmlSerializer out) throws IOException { 289 final int N = mPaths.size(); 290 for (int i = 0; i < N; i++) { 291 final String[] segments = mPaths.valueAt(i); 292 if (segments != null) { 293 out.startTag(NAMESPACE, TAG_PATH); 294 out.text(encodeSegments(segments)); 295 out.endTag(NAMESPACE, TAG_PATH); 296 } 297 } 298 } 299 readFrom(XmlPullParser parser)300 public synchronized void readFrom(XmlPullParser parser) 301 throws IOException, XmlPullParserException { 302 parser.next(); 303 int depth = parser.getDepth(); 304 while (parser.getDepth() >= depth) { 305 if (parser.getEventType() == XmlPullParser.START_TAG 306 && TAG_PATH.equals(parser.getName())) { 307 mPaths.add(decodeSegments(parser.nextText())); 308 } 309 parser.next(); 310 } 311 } 312 encodeSegments(String[] s)313 private String encodeSegments(String[] s) { 314 String[] out = new String[s.length]; 315 for (int i = 0; i < s.length; i++) { 316 out[i] = Uri.encode(s[i]); 317 } 318 return TextUtils.join(DELIMITER, out); 319 } 320 decodeSegments(String s)321 private String[] decodeSegments(String s) { 322 String[] sets = s.split(DELIMITER, -1); 323 for (int i = 0; i < sets.length; i++) { 324 sets[i] = Uri.decode(sets[i]); 325 } 326 return sets; 327 } 328 329 /** 330 * Only for testing, no deep equality of these are done normally. 331 */ 332 @Override equals(Object obj)333 public boolean equals(Object obj) { 334 if (!getClass().equals(obj != null ? obj.getClass() : null)) return false; 335 SliceAuthority other = (SliceAuthority) obj; 336 if (mPaths.size() != other.mPaths.size()) return false; 337 ArrayList<String[]> p1 = new ArrayList<>(mPaths); 338 ArrayList<String[]> p2 = new ArrayList<>(other.mPaths); 339 p1.sort(Comparator.comparing(o -> TextUtils.join(",", o))); 340 p2.sort(Comparator.comparing(o -> TextUtils.join(",", o))); 341 for (int i = 0; i < p1.size(); i++) { 342 String[] a1 = p1.get(i); 343 String[] a2 = p2.get(i); 344 if (a1.length != a2.length) return false; 345 for (int j = 0; j < a1.length; j++) { 346 if (!Objects.equals(a1[j], a2[j])) return false; 347 } 348 } 349 return Objects.equals(mAuthority, other.mAuthority) 350 && Objects.equals(mPkg, other.mPkg); 351 } 352 353 @Override toString()354 public String toString() { 355 return String.format("(%s, %s: %s)", mAuthority, mPkg.toString(), pathToString(mPaths)); 356 } 357 pathToString(ArraySet<String[]> paths)358 private String pathToString(ArraySet<String[]> paths) { 359 return TextUtils.join(", ", paths.stream().map(s -> TextUtils.join("/", s)) 360 .collect(Collectors.toList())); 361 } 362 } 363 } 364