diff --git a/api/current.xml b/api/current.xml index d97a0f4dbebf1..a396c4d845d7e 100644 --- a/api/current.xml +++ b/api/current.xml @@ -496,6 +496,17 @@ visibility="public" > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0) { + i--; + final PathPermission pp = pps[i]; + final String pprperm = pp.getReadPermission(); + if (pprperm != null && pp.match(path)) { + if (context.checkPermission(pprperm, pid, uid) + == PackageManager.PERMISSION_GRANTED) { + return; + } + } + } + } + + if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION) == PackageManager.PERMISSION_GRANTED) { return; } + String msg = "Permission Denial: reading " + ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + Binder.getCallingPid() @@ -193,20 +222,57 @@ public abstract class ContentProvider implements ComponentCallbacks { throw new SecurityException(msg); } - private void checkWritePermission(Uri uri) { + private boolean hasWritePermission(Uri uri) { + final int uid = Binder.getCallingUid(); + if (uid == mMyUid) { + return true; + } + + final Context context = getContext(); final String wperm = getWritePermission(); final int pid = Binder.getCallingPid(); - final int uid = Binder.getCallingUid(); - if (getContext().checkUriPermission(uri, null, wperm, pid, uid, + if (wperm == null + || context.checkPermission(wperm, pid, uid) + == PackageManager.PERMISSION_GRANTED) { + return true; + } + + PathPermission[] pps = getPathPermissions(); + if (pps != null) { + final String path = uri.getPath(); + int i = pps.length; + while (i > 0) { + i--; + final PathPermission pp = pps[i]; + final String ppwperm = pp.getWritePermission(); + if (ppwperm != null && pp.match(path)) { + if (context.checkPermission(ppwperm, pid, uid) + == PackageManager.PERMISSION_GRANTED) { + return true; + } + } + } + } + + if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == PackageManager.PERMISSION_GRANTED) { + return true; + } + + return false; + } + + private void enforceWritePermission(Uri uri) { + if (hasWritePermission(uri)) { return; } + String msg = "Permission Denial: writing " + ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() - + " requires " + wperm; + + " requires " + getWritePermission(); throw new SecurityException(msg); } } @@ -265,6 +331,28 @@ public abstract class ContentProvider implements ComponentCallbacks { return mWritePermission; } + /** + * Change the path-based permission required to read and/or write data in + * the content provider. This is normally set for you from its manifest + * information when the provider is first created. + * + * @param permissions Array of path permission descriptions. + */ + protected final void setPathPermissions(PathPermission[] permissions) { + mPathPermissions = permissions; + } + + /** + * Return the path-based permissions required for read and/or write access to + * this content provider. This method can be called from multiple + * threads, as described in + * Application Fundamentals: + * Processes and Threads. + */ + public final PathPermission[] getPathPermissions() { + return mPathPermissions; + } + /** * Called when the provider is being started. * @@ -600,9 +688,11 @@ public abstract class ContentProvider implements ComponentCallbacks { */ if (mContext == null) { mContext = context; + mMyUid = Process.myUid(); if (info != null) { setReadPermission(info.readPermission); setWritePermission(info.writePermission); + setPathPermissions(info.pathPermissions); } ContentProvider.this.onCreate(); } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index b293636501619..0e2deed4d5e7e 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -1918,6 +1918,7 @@ public class PackageParser { outInfo.metaData, outError)) == null) { return false; } + } else if (parser.getName().equals("grant-uri-permission")) { TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifestGrantUriPermission); @@ -1941,7 +1942,7 @@ public class PackageParser { if (str != null) { pa = new PatternMatcher(str, PatternMatcher.PATTERN_SIMPLE_GLOB); } - + sa.recycle(); if (pa != null) { @@ -1956,6 +1957,101 @@ public class PackageParser { outInfo.info.uriPermissionPatterns = newp; } outInfo.info.grantUriPermissions = true; + } else { + if (!RIGID_PARSER) { + Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":"); + Log.w(TAG, "No path, pathPrefix, or pathPattern for "); + XmlUtils.skipCurrentTag(parser); + continue; + } + outError[0] = "No path, pathPrefix, or pathPattern for "; + return false; + } + XmlUtils.skipCurrentTag(parser); + + } else if (parser.getName().equals("path-permission")) { + TypedArray sa = res.obtainAttributes(attrs, + com.android.internal.R.styleable.AndroidManifestPathPermission); + + PathPermission pa = null; + + String permission = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestPathPermission_permission); + String readPermission = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestPathPermission_readPermission); + if (readPermission == null) { + readPermission = permission; + } + String writePermission = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestPathPermission_writePermission); + if (writePermission == null) { + writePermission = permission; + } + + boolean havePerm = false; + if (readPermission != null) { + readPermission = readPermission.intern(); + havePerm = true; + } + if (writePermission != null) { + writePermission = readPermission.intern(); + havePerm = true; + } + + if (!havePerm) { + if (!RIGID_PARSER) { + Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":"); + Log.w(TAG, "No readPermission or writePermssion for "); + XmlUtils.skipCurrentTag(parser); + continue; + } + outError[0] = "No readPermission or writePermssion for "; + return false; + } + + String path = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestPathPermission_path); + if (path != null) { + pa = new PathPermission(path, + PatternMatcher.PATTERN_LITERAL, readPermission, writePermission); + } + + path = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestPathPermission_pathPrefix); + if (path != null) { + pa = new PathPermission(path, + PatternMatcher.PATTERN_PREFIX, readPermission, writePermission); + } + + path = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestPathPermission_pathPattern); + if (path != null) { + pa = new PathPermission(path, + PatternMatcher.PATTERN_SIMPLE_GLOB, readPermission, writePermission); + } + + sa.recycle(); + + if (pa != null) { + if (outInfo.info.pathPermissions == null) { + outInfo.info.pathPermissions = new PathPermission[1]; + outInfo.info.pathPermissions[0] = pa; + } else { + final int N = outInfo.info.pathPermissions.length; + PathPermission[] newp = new PathPermission[N+1]; + System.arraycopy(outInfo.info.pathPermissions, 0, newp, 0, N); + newp[N] = pa; + outInfo.info.pathPermissions = newp; + } + } else { + if (!RIGID_PARSER) { + Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":"); + Log.w(TAG, "No path, pathPrefix, or pathPattern for "); + XmlUtils.skipCurrentTag(parser); + continue; + } + outError[0] = "No path, pathPrefix, or pathPattern for "; + return false; } XmlUtils.skipCurrentTag(parser); diff --git a/core/java/android/content/pm/PathPermission.java b/core/java/android/content/pm/PathPermission.java new file mode 100644 index 0000000000000..7e49d7df63cea --- /dev/null +++ b/core/java/android/content/pm/PathPermission.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2009 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 android.content.pm; + +import android.os.Parcel; +import android.os.Parcelable; +import android.os.PatternMatcher; + +/** + * Description of permissions needed to access a particular path + * in a {@link ProviderInfo}. + */ +public class PathPermission extends PatternMatcher { + private final String mReadPermission; + private final String mWritePermission; + + public PathPermission(String pattern, int type, String readPermission, + String writePermission) { + super(pattern, type); + mReadPermission = readPermission; + mWritePermission = writePermission; + } + + public String getReadPermission() { + return mReadPermission; + } + + public String getWritePermission() { + return mWritePermission; + } + + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeString(mReadPermission); + dest.writeString(mWritePermission); + } + + public PathPermission(Parcel src) { + super(src); + mReadPermission = src.readString(); + mWritePermission = src.readString(); + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public PathPermission createFromParcel(Parcel source) { + return new PathPermission(source); + } + + public PathPermission[] newArray(int size) { + return new PathPermission[size]; + } + }; +} \ No newline at end of file diff --git a/core/java/android/content/pm/ProviderInfo.java b/core/java/android/content/pm/ProviderInfo.java index b67ddf6b2a15f..d01460e4216e9 100644 --- a/core/java/android/content/pm/ProviderInfo.java +++ b/core/java/android/content/pm/ProviderInfo.java @@ -28,6 +28,7 @@ import android.os.PatternMatcher; */ public final class ProviderInfo extends ComponentInfo implements Parcelable { + /** The name provider is published under content:// */ public String authority = null; @@ -56,6 +57,14 @@ public final class ProviderInfo extends ComponentInfo */ public PatternMatcher[] uriPermissionPatterns = null; + /** + * If non-null, these are path-specific permissions that are allowed for + * accessing the provider. Any permissions listed here will allow a + * holding client to access the provider, and the provider will check + * the URI it provides when making calls against the patterns here. + */ + public PathPermission[] pathPermissions = null; + /** If true, this content provider allows multiple instances of itself * to run in different process. If false, a single instances is always * run in {@link #processName}. */ @@ -78,6 +87,7 @@ public final class ProviderInfo extends ComponentInfo writePermission = orig.writePermission; grantUriPermissions = orig.grantUriPermissions; uriPermissionPatterns = orig.uriPermissionPatterns; + pathPermissions = orig.pathPermissions; multiprocess = orig.multiprocess; initOrder = orig.initOrder; isSyncable = orig.isSyncable; @@ -94,6 +104,7 @@ public final class ProviderInfo extends ComponentInfo out.writeString(writePermission); out.writeInt(grantUriPermissions ? 1 : 0); out.writeTypedArray(uriPermissionPatterns, parcelableFlags); + out.writeTypedArray(pathPermissions, parcelableFlags); out.writeInt(multiprocess ? 1 : 0); out.writeInt(initOrder); out.writeInt(isSyncable ? 1 : 0); @@ -122,6 +133,7 @@ public final class ProviderInfo extends ComponentInfo writePermission = in.readString(); grantUriPermissions = in.readInt() != 0; uriPermissionPatterns = in.createTypedArray(PatternMatcher.CREATOR); + pathPermissions = in.createTypedArray(PathPermission.CREATOR); multiprocess = in.readInt() != 0; initOrder = in.readInt(); isSyncable = in.readInt() != 0; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 599360f062445..23967f4f8dfcb 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -995,6 +995,29 @@ android:description="@string/permdesc_changeBackgroundDataSetting" android:label="@string/permlab_changeBackgroundDataSetting" /> + + + + + + + + + + + + + + + +