Commit 33de31fd authored by jstritar@chromium.org's avatar jstritar@chromium.org

Add origins to the extension permissions API.

BUG=48119,84507
TEST=*Extension*


Review URL: http://codereview.chromium.org/7508029

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@95704 0039d316-1c4b-4281-b951-d872f2087c98
parent 93bbefaf
......@@ -5,7 +5,6 @@
#include "chrome/browser/extensions/extension_permissions_api.h"
#include "base/json/json_writer.h"
#include "base/stringprintf.h"
#include "base/values.h"
#include "chrome/browser/extensions/extension_event_router.h"
#include "chrome/browser/extensions/extension_permissions_api_constants.h"
......@@ -14,6 +13,7 @@
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_error_utils.h"
#include "chrome/common/extensions/extension_messages.h"
#include "chrome/common/extensions/extension_permission_set.h"
#include "chrome/common/extensions/url_pattern_set.h"
......@@ -42,11 +42,14 @@ DictionaryValue* PackPermissionsToValue(const ExtensionPermissionSet* set) {
i != set->apis().end(); ++i)
apis->Append(Value::CreateStringValue(info->GetByID(*i)->name()));
// TODO(jstritar): Include hosts once the API supports them. At that point,
// we could also shared this code with ExtensionPermissionSet methods in
// ExtensionPrefs.
// Generate the list of origin permissions.
URLPatternSet hosts = set->explicit_hosts();
ListValue* origins = new ListValue();
for (URLPatternSet::const_iterator i = hosts.begin(); i != hosts.end(); ++i)
origins->Append(Value::CreateStringValue(i->GetAsString()));
value->Set(keys::kApisKey, apis);
value->Set(keys::kOriginsKey, origins);
return value;
}
......@@ -74,17 +77,43 @@ bool UnpackPermissionsFromValue(DictionaryValue* value,
ExtensionAPIPermission* permission = info->GetByName(api_name);
if (!permission) {
*error = base::StringPrintf(
keys::kUnknownPermissionError, api_name.c_str());
*error = ExtensionErrorUtils::FormatErrorMessage(
keys::kUnknownPermissionError, api_name);
return false;
}
apis.insert(permission->id());
}
}
// Ignore host permissions for now.
URLPatternSet empty_set;
*ptr = new ExtensionPermissionSet(apis, empty_set, empty_set);
URLPatternSet origins;
if (value->HasKey(keys::kOriginsKey)) {
ListValue* origin_list = NULL;
if (!value->GetList(keys::kOriginsKey, &origin_list)) {
*bad_message = true;
return false;
}
for (size_t i = 0; i < origin_list->GetSize(); ++i) {
std::string pattern;
if (!origin_list->GetString(i, &pattern)) {
*bad_message = true;
return false;
}
URLPattern origin(Extension::kValidHostPermissionSchemes);
URLPattern::ParseResult parse_result =
origin.Parse(pattern, URLPattern::IGNORE_PORTS);
if (URLPattern::PARSE_SUCCESS != parse_result) {
*error = ExtensionErrorUtils::FormatErrorMessage(
keys::kInvalidOrigin,
pattern,
URLPattern::GetParseResultString(parse_result));
return false;
}
origins.AddPattern(origin);
}
}
*ptr = new ExtensionPermissionSet(apis, origins, URLPatternSet());
return true;
}
......@@ -110,7 +139,7 @@ void ExtensionPermissionsManager::AddPermissions(
// Update the granted permissions so we don't auto-disable the extension.
extension_service_->GrantPermissions(extension);
NotifyPermissionsUpdated(extension, total.get(), added.get(), ADDED);
NotifyPermissionsUpdated(ADDED, extension, added.get());
}
void ExtensionPermissionsManager::RemovePermissions(
......@@ -127,7 +156,7 @@ void ExtensionPermissionsManager::RemovePermissions(
// extension to add them again without prompting the user.
extension_service_->UpdateActivePermissions(extension, total.get());
NotifyPermissionsUpdated(extension, total.get(), removed.get(), REMOVED);
NotifyPermissionsUpdated(REMOVED, extension, removed.get());
}
void ExtensionPermissionsManager::DispatchEvent(
......@@ -146,10 +175,9 @@ void ExtensionPermissionsManager::DispatchEvent(
}
void ExtensionPermissionsManager::NotifyPermissionsUpdated(
EventType event_type,
const Extension* extension,
const ExtensionPermissionSet* active,
const ExtensionPermissionSet* changed,
EventType event_type) {
const ExtensionPermissionSet* changed) {
if (!changed || changed->IsEmpty())
return;
......@@ -183,10 +211,11 @@ void ExtensionPermissionsManager::NotifyPermissionsUpdated(
Profile* profile = Profile::FromBrowserContext(host->browser_context());
if (extension_service_->profile()->IsSameProfile(profile))
host->Send(new ExtensionMsg_UpdatePermissions(
static_cast<int>(reason),
extension->id(),
active->apis(),
active->explicit_hosts(),
active->scriptable_hosts()));
changed->apis(),
changed->explicit_hosts(),
changed->scriptable_hosts()));
}
}
......@@ -235,7 +264,8 @@ bool RemovePermissionsFunction::RunImpl() {
i != apis.end(); ++i) {
const ExtensionAPIPermission* api = info->GetByID(*i);
if (!api->supports_optional()) {
error_ = base::StringPrintf(keys::kNotWhitelistedError, api->name());
error_ = ExtensionErrorUtils::FormatErrorMessage(
keys::kNotWhitelistedError, api->name());
return false;
}
}
......@@ -286,7 +316,8 @@ bool RequestPermissionsFunction::RunImpl() {
i != apis.end(); ++i) {
const ExtensionAPIPermission* api = info->GetByID(*i);
if (!api->supports_optional()) {
error_ = base::StringPrintf(keys::kNotWhitelistedError, api->name());
error_ = ExtensionErrorUtils::FormatErrorMessage(
keys::kNotWhitelistedError, api->name());
return false;
}
}
......
......@@ -48,16 +48,14 @@ class ExtensionPermissionsManager {
const char* event_name,
const ExtensionPermissionSet* changed_permissions);
// Issues the relevant events, messages and notifications when the permissions
// have changed for the |extension| (|changed| is the permission delta, while
// |active| is the new permission set). Specifically, this sends the
// EXTENSION_PERMISSIONS_UPDATED notification, the
// ExtensionMsg_UpdatePermissions IPC message, and fires the onAdded/onRemoved
// events in the extension.
void NotifyPermissionsUpdated(const Extension* extension,
const ExtensionPermissionSet* active,
const ExtensionPermissionSet* changed,
EventType event_type);
// Issues the relevant events, messages and notifications when the
// |extension|'s permissions have |changed| (|changed| is the delta).
// Specifically, this sends the EXTENSION_PERMISSIONS_UPDATED notification,
// the ExtensionMsg_UpdatePermissions IPC message, and fires the
// onAdded/onRemoved events in the extension.
void NotifyPermissionsUpdated(EventType event_type,
const Extension* extension,
const ExtensionPermissionSet* changed);
ExtensionService* extension_service_;
};
......
......@@ -7,15 +7,18 @@
namespace extension_permissions_module_constants {
const char kApisKey[] = "permissions";
const char kOriginsKey[] = "origins";
const char kCantRemoveRequiredPermissionsError[] =
"You cannot remove required permissions.";
const char kNotInOptionalPermissionsError[] =
"Optional permissions must be listed in extension manifest.";
const char kNotWhitelistedError[] =
"The optional permissions API does not support '%s'.";
"The optional permissions API does not support '*'.";
const char kUnknownPermissionError[] =
"'%s' is not a recognized permission.";
"'*' is not a recognized permission.";
const char kInvalidOrigin[] =
"Invalid value for origin pattern *: *";
const char kOnAdded[] = "experimental.permissions.onAdded";
const char kOnRemoved[] = "experimental.permissions.onRemoved";
......
......@@ -12,12 +12,14 @@ namespace extension_permissions_module_constants {
// Keys used in serializing permissions data and events.
extern const char kApisKey[];
extern const char kOriginsKey[];
// Error messages.
extern const char kCantRemoveRequiredPermissionsError[];
extern const char kNotInOptionalPermissionsError[];
extern const char kNotWhitelistedError[];
extern const char kUnknownPermissionError[];
extern const char kInvalidOrigin[];
// Event names.
extern const char kOnAdded[];
......
......@@ -9,6 +9,16 @@
#include "chrome/browser/ui/browser.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension_permission_set.h"
#include "net/base/mock_host_resolver.h"
namespace {
static void AddPattern(URLPatternSet* extent, const std::string& pattern) {
int schemes = URLPattern::SCHEME_ALL;
extent->AddPattern(URLPattern(schemes, pattern));
}
} // namespace
class ExperimentalApiTest : public ExtensionApiTest {
public:
......@@ -60,14 +70,19 @@ IN_PROC_BROWSER_TEST_F(ExperimentalApiTest, OptionalPermissionsGranted) {
apis.insert(ExtensionAPIPermission::kTab);
apis.insert(ExtensionAPIPermission::kManagement);
apis.insert(ExtensionAPIPermission::kPermissions);
URLPatternSet explicit_hosts;
AddPattern(&explicit_hosts, "http://a.com/*");
AddPattern(&explicit_hosts, "http://*.c.com/*");
scoped_refptr<ExtensionPermissionSet> granted_permissions =
new ExtensionPermissionSet(apis, URLPatternSet(), URLPatternSet());
new ExtensionPermissionSet(apis, explicit_hosts, URLPatternSet());
ExtensionPrefs* prefs =
browser()->profile()->GetExtensionService()->extension_prefs();
prefs->AddGrantedPermissions("kjmkgkdkpedkejedfhmfcenooemhbpbo",
granted_permissions);
host_resolver()->AddRule("*.com", "127.0.0.1");
ASSERT_TRUE(StartTestServer());
EXPECT_TRUE(RunExtensionTest("permissions/optional")) << message_;
}
......@@ -76,11 +91,15 @@ IN_PROC_BROWSER_TEST_F(ExperimentalApiTest, OptionalPermissionsAutoConfirm) {
// Rather than setting the granted permissions, set the UI autoconfirm flag
// and run the same tests.
RequestPermissionsFunction::SetAutoConfirmForTests(true);
host_resolver()->AddRule("*.com", "127.0.0.1");
ASSERT_TRUE(StartTestServer());
EXPECT_TRUE(RunExtensionTest("permissions/optional")) << message_;
}
// Test that denying the optional permissions confirmation dialog works.
IN_PROC_BROWSER_TEST_F(ExperimentalApiTest, OptionalPermissionsDeny) {
RequestPermissionsFunction::SetAutoConfirmForTests(false);
host_resolver()->AddRule("*.com", "127.0.0.1");
ASSERT_TRUE(StartTestServer());
EXPECT_TRUE(RunExtensionTest("permissions/optional_deny")) << message_;
}
......@@ -1071,6 +1071,12 @@
"items": {"type": "string"},
"optional": true,
"description": "List of named permissions (does not include hosts or origins)."
},
"origins": {
"type": "array",
"items": {"type": "string"},
"optional": true,
"description": "List of origin permissions."
}
}
}
......
......@@ -1715,6 +1715,85 @@
<div></div>
</dd>
</div>
</div><div>
<div>
<dt>
<var>origins</var>
<em>
<!-- TYPE -->
<div style="display:inline">
(
<span class="optional">optional</span>
<span class="enum" style="display: none; ">enumerated</span>
<span id="typeTemplate">
<span style="display: none; ">
<a> Type</a>
</span>
<span>
<span>
array of <span><span>
<span style="display: none; ">
<a> Type</a>
</span>
<span>
<span style="display: none; ">
array of <span><span></span></span>
</span>
<span>string</span>
<span style="display: none; "></span>
</span>
</span></span>
</span>
<span style="display: none; ">paramType</span>
<span style="display: none; "></span>
</span>
</span>
)
</div>
</em>
</dt>
<dd class="todo" style="display: none; ">
Undocumented.
</dd>
<dd>List of origin permissions.</dd>
<dd style="display: none; ">
This parameter was added in version
<b><span></span></b>.
You must omit this parameter in earlier versions,
and you may omit it in any version. If you require this
parameter, the manifest key
<a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
can ensure that your extension won't be run in an earlier browser version.
</dd>
<!-- OBJECT PROPERTIES -->
<dd style="display: none; ">
<dl>
<div>
<div>
</div>
</div>
</dl>
</dd>
<!-- OBJECT METHODS -->
<dd style="display: none; ">
<div></div>
</dd>
<!-- OBJECT EVENT FIELDS -->
<dd style="display: none; ">
<div></div>
</dd>
<!-- FUNCTION PARAMETERS -->
<dd style="display: none; ">
<div></div>
</dd>
</div>
</div>
</dl>
......
......@@ -234,11 +234,12 @@ IPC_MESSAGE_ROUTED1(ExtensionMsg_UpdateBrowserWindowId,
int /* id of browser window */)
// Tell the renderer to update an extension's permission set.
IPC_MESSAGE_CONTROL4(ExtensionMsg_UpdatePermissions,
IPC_MESSAGE_CONTROL5(ExtensionMsg_UpdatePermissions,
int /* UpdateExtensionPermissionsInfo::REASON */,
std::string /* extension_id*/,
ExtensionAPIPermissionSet,
URLPatternSet,
URLPatternSet)
ExtensionAPIPermissionSet /* permissions */,
URLPatternSet /* explicit_hosts */,
URLPatternSet /* scriptable_hosts */)
// Tell the renderer which type this view is.
IPC_MESSAGE_ROUTED1(ExtensionMsg_NotifyRenderViewType,
......
......@@ -98,7 +98,7 @@ void ExtensionDispatcher::WebKitInitialized() {
iter != active_extension_ids_.end(); ++iter) {
const Extension* extension = extensions_.GetByID(*iter);
if (extension)
InitHostPermissions(extension);
InitOriginPermissions(extension);
}
is_webkit_initialized_ = true;
......@@ -234,10 +234,13 @@ void ExtensionDispatcher::OnActivateExtension(
return;
if (is_webkit_initialized_)
InitHostPermissions(extension);
InitOriginPermissions(extension);
}
void ExtensionDispatcher::InitHostPermissions(const Extension* extension) {
void ExtensionDispatcher::InitOriginPermissions(const Extension* extension) {
// TODO(jstritar): We should try to remove this special case. Also, these
// whitelist entries need to be updated when the kManagement permission
// changes.
if (extension->HasAPIPermission(ExtensionAPIPermission::kManagement)) {
WebSecurityPolicy::addOriginAccessWhitelistEntry(
extension->url(),
......@@ -246,10 +249,17 @@ void ExtensionDispatcher::InitHostPermissions(const Extension* extension) {
false);
}
const URLPatternSet& permissions =
extension->GetActivePermissions()->explicit_hosts();
for (URLPatternSet::const_iterator i = permissions.begin();
i != permissions.end(); ++i) {
UpdateOriginPermissions(UpdatedExtensionPermissionsInfo::ADDED,
extension,
extension->GetActivePermissions()->explicit_hosts());
}
void ExtensionDispatcher::UpdateOriginPermissions(
UpdatedExtensionPermissionsInfo::Reason reason,
const Extension* extension,
const URLPatternSet& origins) {
for (URLPatternSet::const_iterator i = origins.begin();
i != origins.end(); ++i) {
const char* schemes[] = {
chrome::kHttpScheme,
chrome::kHttpsScheme,
......@@ -258,17 +268,20 @@ void ExtensionDispatcher::InitHostPermissions(const Extension* extension) {
};
for (size_t j = 0; j < arraysize(schemes); ++j) {
if (i->MatchesScheme(schemes[j])) {
WebSecurityPolicy::addOriginAccessWhitelistEntry(
extension->url(),
WebString::fromUTF8(schemes[j]),
WebString::fromUTF8(i->host()),
i->match_subdomains());
((reason == UpdatedExtensionPermissionsInfo::REMOVED) ?
WebSecurityPolicy::removeOriginAccessWhitelistEntry :
WebSecurityPolicy::addOriginAccessWhitelistEntry)(
extension->url(),
WebString::fromUTF8(schemes[j]),
WebString::fromUTF8(i->host()),
i->match_subdomains());
}
}
}
}
void ExtensionDispatcher::OnUpdatePermissions(
int reason_id,
const std::string& extension_id,
const ExtensionAPIPermissionSet& apis,
const URLPatternSet& explicit_hosts,
......@@ -277,8 +290,23 @@ void ExtensionDispatcher::OnUpdatePermissions(
if (!extension)
return;
extension->SetActivePermissions(
new ExtensionPermissionSet(apis, explicit_hosts, scriptable_hosts));
scoped_refptr<const ExtensionPermissionSet> delta =
new ExtensionPermissionSet(apis, explicit_hosts, scriptable_hosts);
scoped_refptr<const ExtensionPermissionSet> old_active =
extension->GetActivePermissions();
UpdatedExtensionPermissionsInfo::Reason reason =
static_cast<UpdatedExtensionPermissionsInfo::Reason>(reason_id);
const ExtensionPermissionSet* new_active = NULL;
if (reason == UpdatedExtensionPermissionsInfo::ADDED) {
new_active = ExtensionPermissionSet::CreateUnion(old_active, delta);
} else {
CHECK_EQ(UpdatedExtensionPermissionsInfo::REMOVED, reason);
new_active = ExtensionPermissionSet::CreateDifference(old_active, delta);
}
extension->SetActivePermissions(new_active);
UpdateOriginPermissions(reason, extension, explicit_hosts);
}
void ExtensionDispatcher::OnUpdateUserScripts(
......
......@@ -78,7 +78,8 @@ class ExtensionDispatcher : public RenderProcessObserver {
const std::vector<std::string>& page_actions);
void OnActivateApplication(const std::string& extension_id);
void OnActivateExtension(const std::string& extension_id);
void OnUpdatePermissions(const std::string& extension_id,
void OnUpdatePermissions(int reason_id,
const std::string& extension_id,
const ExtensionAPIPermissionSet& apis,
const URLPatternSet& explicit_hosts,
const URLPatternSet& scriptable_hosts);
......@@ -92,7 +93,10 @@ class ExtensionDispatcher : public RenderProcessObserver {
void RegisterExtension(v8::Extension* extension, bool restrict_to_extensions);
// Sets up the host permissions for |extension|.
void InitHostPermissions(const Extension* extension);
void InitOriginPermissions(const Extension* extension);
void UpdateOriginPermissions(UpdatedExtensionPermissionsInfo::Reason reason,
const Extension* extension,
const URLPatternSet& origins);
// True if this renderer is running extensions.
bool is_extension_process_;
......
......@@ -8,12 +8,14 @@
"experimental",
"management",
"permissions",
"http://example.org/"
"http://a.com/*"
],
"optional_permissions": [
"tabs",
"management",
"notifications",
"background"
"background",
"http://a.com/*",
"http://*.c.com/*"
]
}
......@@ -7,30 +7,58 @@ var pass = chrome.test.callbackPass;
var NO_TABS_PERMISSION =
"You do not have permission to use 'windows.getAll'.";
chrome.test.runTests([
function denyRequest() {
chrome.experimental.permissions.request(
{permissions: ['tabs']},
pass(function(granted) {
// They were not granted, and there should be no error.
assertFalse(granted);
assertTrue(chrome.extension.lastError === undefined);
// Make sure they weren't granted...
chrome.experimental.permissions.contains(
{permissions: ['tabs']},
pass(function(result) {
assertFalse(result);
}));
try {
chrome.windows.getAll({populate: true}, function() {
chrome.test.fail("Should not have tabs API permission.");
});
} catch (e) {
assertTrue(e.message.indexOf(NO_TABS_PERMISSION) == 0);
}
}));
chrome.test.getConfig(function(config) {
function doReq(domain, callback) {
var req = new XMLHttpRequest();
var url = domain + ":PORT/files/extensions/test_file.txt";
url = url.replace(/PORT/, config.testServer.port);
chrome.test.log("Requesting url: " + url);
req.open("GET", url, true);
req.onload = function() {
assertEq(200, req.status);
assertEq("Hello!", req.responseText);
callback(true);
};
req.onerror = function() {
chrome.test.log("status: " + req.status);
chrome.test.log("text: " + req.responseText);
callback(false);
};
req.send(null);
}
]);
chrome.test.runTests([
function denyRequest() {
chrome.experimental.permissions.request(
{permissions: ['tabs'], origins: ['http://*.c.com/*']},
pass(function(granted) {
// They were not granted, and there should be no error.
assertFalse(granted);
assertTrue(chrome.extension.lastError === undefined);
// Make sure they weren't granted...
chrome.experimental.permissions.contains(
{permissions: ['tabs'], origins:['http://*.c.com/*']},
pass(function(result) { assertFalse(result); }));
try {
chrome.windows.getAll({populate: true}, function() {
chrome.test.fail("Should not have tabs API permission.");
});
} catch (e) {
assertTrue(e.message.indexOf(NO_TABS_PERMISSION) == 0);
}
doReq('http://b.c.com/', pass(function(result) {
assertFalse(result);
}));
}));
}
]);
});
</script>
......@@ -3,6 +3,6 @@
"description": "permissions/optional_deny",
"version": "0.1",
"background_page": "background.html",
"permissions": ["experimental", "permissions", "http://example.org/"],
"optional_permissions": ["tabs", "management"]
"permissions": ["experimental", "permissions", "http://a.com/*"],
"optional_permissions": ["tabs", "management", "http://*.c.com/*"]
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment