summaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
authoraberent <aberent@chromium.org>2016-01-05 05:04:48 -0800
committerCommit bot <commit-bot@chromium.org>2016-01-05 13:05:38 +0000
commitec7d1d143650ceef3969c9181b8e7fe4f9d6b820 (patch)
tree862081b17670fb457a0a4c00c253a04fdb4a6f27 /components
parent7433487e4082c928a06ddff51fe5c963a28da3ef (diff)
downloadchromium_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')
-rw-r--r--components/components.gyp1
-rw-r--r--components/components_tests.gyp4
-rw-r--r--components/web_restriction.gypi23
-rw-r--r--components/web_restriction/BUILD.gn20
-rw-r--r--components/web_restriction/OWNERS2
-rw-r--r--components/web_restriction/java/src/org/chromium/components/webrestriction/WebRestrictionsContentProvider.java178
-rw-r--r--components/web_restriction/junit/src/org/chromium/components/webrestriction/WebRestrictionsContentProviderTest.java121
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())));
+ }
+}