diff options
author | aberent <aberent@chromium.org> | 2016-01-05 05:04:48 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2016-01-05 13:05:38 +0000 |
commit | ec7d1d143650ceef3969c9181b8e7fe4f9d6b820 (patch) | |
tree | 862081b17670fb457a0a4c00c253a04fdb4a6f27 /components | |
parent | 7433487e4082c928a06ddff51fe5c963a28da3ef (diff) | |
download | chromium_src-ec7d1d143650ceef3969c9181b8e7fe4f9d6b820.zip chromium_src-ec7d1d143650ceef3969c9181b8e7fe4f9d6b820.tar.gz chromium_src-ec7d1d143650ceef3969c9181b8e7fe4f9d6b820.tar.bz2 |
Resubmit Supervised user web restrictions content provider
This resubmits https://codereview.chromium.org/1452603002/ which
was reverted by https://codereview.chromium.org/1539653002/
The previous version of SupervisedUserContentProviderUnitTest was
using Java Thread objects, which seems to confuse Robolectric. I
have now removed this, which slightly reduces the coverage of the
tests (it no longer tests that the threading is correct) but allows
them to run.
TBR=jochen@chromium.org
BUG=570768
Review URL: https://codereview.chromium.org/1552333002
Cr-Commit-Position: refs/heads/master@{#367512}
Diffstat (limited to 'components')
7 files changed, 348 insertions, 1 deletions
diff --git a/components/components.gyp b/components/components.gyp index a4b3169..c83d0aa 100644 --- a/components/components.gyp +++ b/components/components.gyp @@ -101,6 +101,7 @@ 'version_info.gypi', 'version_ui.gypi', 'web_resource.gypi', + 'web_restriction.gypi', 'webdata.gypi', 'webdata_services.gypi', ], diff --git a/components/components_tests.gyp b/components/components_tests.gyp index ea2fb5f..82deb50 100644 --- a/components/components_tests.gyp +++ b/components/components_tests.gyp @@ -1629,6 +1629,7 @@ 'components.gyp:invalidation_java', 'components.gyp:policy_java', 'components.gyp:policy_java_test_support', + 'components.gyp:web_restriction_java', '../base/base.gyp:base_java', '../base/base.gyp:base_java_test_support', '../testing/android/junit/junit_test.gyp:junit_test_support', @@ -1637,7 +1638,8 @@ 'main_class': 'org.chromium.testing.local.JunitTestMain', 'src_paths': [ 'invalidation/impl/android/junit/', - 'policy/android/junit/' + 'policy/android/junit/', + 'web_restriction/junit/' ], }, 'includes': [ '../build/host_jar.gypi' ], diff --git a/components/web_restriction.gypi b/components/web_restriction.gypi new file mode 100644 index 0000000..01aa9bd --- /dev/null +++ b/components/web_restriction.gypi @@ -0,0 +1,23 @@ +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'conditions': [ + ['OS == "android"', { + 'targets': [ + { + # GN: //components/web_restriction:web_restriction_java + 'target_name': 'web_restriction_java', + 'type': 'none', + 'variables': { + 'java_in_dir': 'web_restriction/java', + }, + 'dependencies': [ + '../base/base.gyp:base', + ], + 'includes': [ '../build/java.gypi' ], + }, + ], + }]] +} diff --git a/components/web_restriction/BUILD.gn b/components/web_restriction/BUILD.gn new file mode 100644 index 0000000..b6974db --- /dev/null +++ b/components/web_restriction/BUILD.gn @@ -0,0 +1,20 @@ +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//build/config/android/rules.gni") + +# GYP: components.gyp:web_restriction_java +android_library("web_restriction_java") { + java_files = [ "java/src/org/chromium/components/webrestriction/WebRestrictionsContentProvider.java" ] +} + +# GYP: //components/components_test.gyp:components_junit_tests +junit_binary("components_web_restrictions_junit_tests") { + java_files = [ "junit/src/org/chromium/components/webrestriction/WebRestrictionsContentProviderTest.java" ] + deps = [ + ":web_restriction_java", + "//base:base_java", + "//third_party/junit:hamcrest", + ] +} diff --git a/components/web_restriction/OWNERS b/components/web_restriction/OWNERS new file mode 100644 index 0000000..b9540e1 --- /dev/null +++ b/components/web_restriction/OWNERS @@ -0,0 +1,2 @@ +aberent@chromium.org +bauerb@chromium.org
\ No newline at end of file diff --git a/components/web_restriction/java/src/org/chromium/components/webrestriction/WebRestrictionsContentProvider.java b/components/web_restriction/java/src/org/chromium/components/webrestriction/WebRestrictionsContentProvider.java new file mode 100644 index 0000000..e549de6a --- /dev/null +++ b/components/web_restriction/java/src/org/chromium/components/webrestriction/WebRestrictionsContentProvider.java @@ -0,0 +1,178 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.components.webrestriction; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Context; +import android.content.UriMatcher; +import android.content.pm.ProviderInfo; +import android.database.AbstractCursor; +import android.database.Cursor; +import android.net.Uri; +import android.util.Pair; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Abstract content provider for providing web restrictions, i.e. for providing a filter for URLs so + * that they can be blocked or permitted, and a means of requesting permission for new URLs. It + * provides two (virtual) tables; an 'authorized' table listing the the status of every URL, and a + * 'requested' table containing the requests for access to new URLs. The 'authorized' table is read + * only and the 'requested' table is write only. + */ +public abstract class WebRestrictionsContentProvider extends ContentProvider { + public static final int BLOCKED = 0; + public static final int PROCEED = 1; + private static final int WEB_RESTRICTIONS = 1; + private static final int AUTHORIZED = 2; + private static final int REQUESTED = 3; + + private final Pattern mSelectionPattern; + private UriMatcher mContentUriMatcher; + private Uri mContentUri; + + protected WebRestrictionsContentProvider() { + // Pattern to extract the URL from the selection. + // Matches patterns of the form "url = '<url>'" with arbitrary spacing around the "=" etc. + mSelectionPattern = Pattern.compile("\\s*url\\s*=\\s*'([^']*)'"); + } + + @Override + public boolean onCreate() { + return true; + } + + @Override + public void attachInfo(Context context, ProviderInfo info) { + super.attachInfo(context, info); + mContentUri = new Uri.Builder().scheme("content").authority(info.authority).build(); + mContentUriMatcher = new UriMatcher(WEB_RESTRICTIONS); + mContentUriMatcher.addURI(info.authority, "authorized", AUTHORIZED); + mContentUriMatcher.addURI(info.authority, "requested", REQUESTED); + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + // Check that this is the a query on the 'authorized' table + // TODO(aberent): Provide useful queries on the 'requested' table. + if (mContentUriMatcher.match(uri) != AUTHORIZED) return null; + // If the selection is of the right form get the url we are querying. + Matcher matcher = mSelectionPattern.matcher(selection); + if (!matcher.find()) return null; + final String url = matcher.group(1); + final Pair<Boolean, String> result = shouldProceed(url); + + return new AbstractCursor() { + + @Override + public int getCount() { + return 1; + } + + @Override + public String[] getColumnNames() { + return new String[] {"Should Proceed", "Error message"}; + } + + @Override + public String getString(int column) { + if (column == 1) return result.second; + return null; + } + + @Override + public short getShort(int column) { + return 0; + } + + @Override + public int getInt(int column) { + if (column == 0) return result.first ? PROCEED : BLOCKED; + return 0; + } + + @Override + public long getLong(int column) { + return 0; + } + + @Override + public float getFloat(int column) { + return 0; + } + + @Override + public double getDouble(int column) { + return 0; + } + + @Override + public boolean isNull(int column) { + return false; + } + }; + } + + @Override + public String getType(Uri uri) { + // Abused to return whether we can insert + if (mContentUriMatcher.match(uri) != REQUESTED) return null; + return canInsert() ? "text/plain" : null; + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + if (mContentUriMatcher.match(uri) != REQUESTED) return null; + String url = values.getAsString("url"); + if (requestInsert(url)) { + // TODO(aberent): If we ever make the 'requested' table readable then we might want to + // change this to a more conventional content URI (with a row number). + return uri.buildUpon().appendPath(url).build(); + } else { + return null; + } + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + return 0; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + return 0; + } + + /** + * @param url the URL that is wanted. + * @return a pair containing the Result and the HTML Error Message. result is true if safe to + * proceed, false otherwise. error message is only meaningful if result is false, a null + * error message means use application default. + */ + protected abstract Pair<Boolean, String> shouldProceed(final String url); + + /** + * @return whether the content provider allows insertions. + */ + protected abstract boolean canInsert(); + + /** + * Start a request that a URL should be permitted + * + * @param url the URL that is wanted. + */ + protected abstract boolean requestInsert(final String url); + + /** + * Call to tell observers that the filter has changed. + */ + protected void onFilterChanged() { + getContext().getContentResolver().notifyChange( + mContentUri.buildUpon().appendPath("authorized").build(), null); + } +} diff --git a/components/web_restriction/junit/src/org/chromium/components/webrestriction/WebRestrictionsContentProviderTest.java b/components/web_restriction/junit/src/org/chromium/components/webrestriction/WebRestrictionsContentProviderTest.java new file mode 100644 index 0000000..0da6bd8 --- /dev/null +++ b/components/web_restriction/junit/src/org/chromium/components/webrestriction/WebRestrictionsContentProviderTest.java @@ -0,0 +1,121 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.components.webrestriction; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.pm.ProviderInfo; +import android.database.Cursor; +import android.net.Uri; +import android.util.Pair; + +import org.chromium.testing.local.LocalRobolectricTestRunner; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.robolectric.Robolectric; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowContentResolver; + +/** + * Tests of WebRestrictionsContentProvider. + */ +@RunWith(LocalRobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class WebRestrictionsContentProviderTest { + private static final String AUTHORITY = "org.chromium.browser.DummyProvider"; + + private WebRestrictionsContentProvider mContentProvider; + private ContentResolver mContentResolver; + private Uri mUri; + + @Before + public void setUp() { + // The test needs a concrete version of the test class, but mocks the additional members as + // necessary. + mContentProvider = Mockito.spy(new WebRestrictionsContentProvider() { + @Override + protected Pair<Boolean, String> shouldProceed(String url) { + return null; + } + + @Override + protected boolean canInsert() { + return false; + } + + @Override + protected boolean requestInsert(String url) { + return false; + } + }); + mContentProvider.onCreate(); + ShadowContentResolver.registerProvider(AUTHORITY, mContentProvider); + ProviderInfo info = new ProviderInfo(); + info.authority = AUTHORITY; + mContentProvider.attachInfo(null, info); + mContentResolver = Robolectric.application.getContentResolver(); + mUri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(AUTHORITY) + .build(); + } + + @Test + public void testQuery() { + when(mContentProvider.shouldProceed(anyString())) + .thenReturn(new Pair<Boolean, String>(false, "Error Message")); + Cursor cursor = mContentResolver.query(mUri.buildUpon().appendPath("authorized").build(), + null, "url = 'dummy'", null, null); + verify(mContentProvider).shouldProceed("dummy"); + assertThat(cursor, is(not(nullValue()))); + assertThat(cursor.getInt(0), is(WebRestrictionsContentProvider.BLOCKED)); + assertThat(cursor.getString(1), is("Error Message")); + when(mContentProvider.shouldProceed(anyString())) + .thenReturn(new Pair<Boolean, String>(true, null)); + cursor = mContentResolver.query(mUri.buildUpon().appendPath("authorized").build(), null, + "url = 'dummy'", null, null); + assertThat(cursor, is(not(nullValue()))); + assertThat(cursor.getInt(0), is(WebRestrictionsContentProvider.PROCEED)); + } + + @Test + public void testInsert() { + ContentValues values = new ContentValues(); + values.put("url", "dummy2"); + when(mContentProvider.requestInsert(anyString())).thenReturn(false); + assertThat( + mContentResolver.insert(mUri.buildUpon().appendPath("requested").build(), values), + is(nullValue())); + verify(mContentProvider).requestInsert("dummy2"); + values.put("url", "dummy3"); + when(mContentProvider.requestInsert(anyString())).thenReturn(true); + assertThat( + mContentResolver.insert(mUri.buildUpon().appendPath("requested").build(), values), + is(not(nullValue()))); + verify(mContentProvider).requestInsert("dummy3"); + } + + @Test + public void testGetType() { + assertThat(mContentResolver.getType(mUri.buildUpon().appendPath("junk").build()), + is(nullValue())); + when(mContentProvider.canInsert()).thenReturn(false); + assertThat(mContentResolver.getType(mUri.buildUpon().appendPath("requested").build()), + is(nullValue())); + when(mContentProvider.canInsert()).thenReturn(true); + assertThat(mContentResolver.getType(mUri.buildUpon().appendPath("requested").build()), + is(not(nullValue()))); + } +} |