Bug 1197451 - Add clipboardWrite permission r=billm

MozReview-Commit-ID: 6d1mQSVWRPe

--HG--
extra : rebase_source : 2f19bab5c9d6db25c60b2b19c06c7027384a04ca
This commit is contained in:
Rob Wu 2016-07-08 17:19:17 -07:00
parent aa5cceb394
commit 9069fff35f
12 changed files with 216 additions and 3 deletions

View File

@ -586,6 +586,22 @@ BasePrincipal::GetUnknownAppId(bool* aUnknownAppId)
return NS_OK;
}
bool
BasePrincipal::AddonHasPermission(const nsAString& aPerm)
{
if (mOriginAttributes.mAddonId.IsEmpty()) {
return false;
}
nsCOMPtr<nsIAddonPolicyService> aps =
do_GetService("@mozilla.org/addons/policy-service;1");
NS_ENSURE_TRUE(aps, false);
bool retval = false;
nsresult rv = aps->AddonHasPermission(mOriginAttributes.mAddonId, aPerm, &retval);
NS_ENSURE_SUCCESS(rv, false);
return retval;
}
already_AddRefed<BasePrincipal>
BasePrincipal::CreateCodebasePrincipal(nsIURI* aURI, const PrincipalOriginAttributes& aAttrs)
{

View File

@ -266,6 +266,8 @@ public:
NS_IMETHOD GetUserContextId(uint32_t* aUserContextId) final;
NS_IMETHOD GetPrivateBrowsingId(uint32_t* aPrivateBrowsingId) final;
virtual bool AddonHasPermission(const nsAString& aPerm);
virtual bool IsOnCSSUnprefixingWhitelist() override { return false; }
virtual bool IsCodebasePrincipal() const { return false; };

View File

@ -40,6 +40,11 @@ interface nsIAddonPolicyService : nsISupports
*/
ACString getGeneratedBackgroundPageUrl(in ACString aAddonId);
/**
* Returns true if the addon was granted the |aPerm| API permission.
*/
boolean addonHasPermission(in AString aAddonId, in AString aPerm);
/**
* Returns true if unprivileged code associated with the given addon may load
* data from |aURI|.

View File

@ -786,6 +786,17 @@ nsExpandedPrincipal::GetBaseDomain(nsACString& aBaseDomain)
return NS_ERROR_NOT_AVAILABLE;
}
bool
nsExpandedPrincipal::AddonHasPermission(const nsAString& aPerm)
{
for (size_t i = 0; i < mPrincipals.Length(); ++i) {
if (BasePrincipal::Cast(mPrincipals[i])->AddonHasPermission(aPerm)) {
return true;
}
}
return false;
}
bool
nsExpandedPrincipal::IsOnCSSUnprefixingWhitelist()
{

View File

@ -79,6 +79,7 @@ public:
NS_IMETHOD GetDomain(nsIURI** aDomain) override;
NS_IMETHOD SetDomain(nsIURI* aDomain) override;
NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override;
virtual bool AddonHasPermission(const nsAString& aPerm) override;
virtual bool IsOnCSSUnprefixingWhitelist() override;
virtual void GetScriptLocation(nsACString &aStr) override;
nsresult GetOriginInternal(nsACString& aOrigin) override;

View File

@ -98,6 +98,7 @@
#include "nsHostObjectProtocolHandler.h"
#include "nsHtml5Module.h"
#include "nsHtml5StringParser.h"
#include "nsIAddonPolicyService.h"
#include "nsIAsyncVerifyRedirectCallback.h"
#include "nsICategoryManager.h"
#include "nsIChannelEventSink.h"
@ -6759,9 +6760,12 @@ nsContentUtils::IsRequestFullScreenAllowed()
bool
nsContentUtils::IsCutCopyAllowed()
{
return (!IsCutCopyRestricted() &&
EventStateManager::IsHandlingUserInput()) ||
IsCallerChrome();
if ((!IsCutCopyRestricted() && EventStateManager::IsHandlingUserInput()) ||
IsCallerChrome()) {
return true;
}
return BasePrincipal::Cast(SubjectPrincipal())->AddonHasPermission(NS_LITERAL_STRING("clipboardWrite"));
}
/* static */

View File

@ -804,6 +804,10 @@ BrowserExtensionContent.prototype = {
localize(...args) {
return this.localeData.localize(...args);
},
hasPermission(perm) {
return this.permissions.has(perm);
},
};
ExtensionManager = {

View File

@ -166,6 +166,7 @@ var Service = {
handler.setSubstitution(uuid, uri);
this.uuidMap.set(uuid, extension);
this.aps.setAddonHasPermissionCallback(extension.id, extension.hasPermission.bind(extension));
this.aps.setAddonLoadURICallback(extension.id, this.checkAddonMayLoad.bind(this, extension));
this.aps.setAddonLocalizeCallback(extension.id, extension.localize.bind(extension));
this.aps.setAddonCSP(extension.id, extension.manifest.content_security_policy);
@ -176,6 +177,7 @@ var Service = {
shutdownExtension(uuid) {
let extension = this.uuidMap.get(uuid);
this.uuidMap.delete(uuid);
this.aps.setAddonHasPermissionCallback(extension.id, null);
this.aps.setAddonLoadURICallback(extension.id, null);
this.aps.setAddonLocalizeCallback(extension.id, null);
this.aps.setAddonCSP(extension.id, null);

View File

@ -187,6 +187,7 @@
"type": "string",
"enum": [
"alarms",
"clipboardWrite",
"idle",
"notifications",
"storage"

View File

@ -35,6 +35,8 @@ support-files =
file_ext_test_api_injection.js
file_permission_xhr.html
[test_clipboard.html]
# skip-if = # disabled test case with_permission_allow_copy, see inline comment.
[test_ext_inIncognitoContext_window.html]
skip-if = os == 'android' # Android does not currently support windows.
[test_ext_geturl.html]

View File

@ -0,0 +1,140 @@
<!DOCTYPE HTML>
<html>
<head>
<title>clipboard permission test</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/SpawnTask.js"></script>
<script src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
<script src="head.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
</head>
<body>
<script>
"use strict";
function doCopy(txt) {
let field = document.createElement("textarea");
document.body.appendChild(field);
field.value = txt;
field.select();
return document.execCommand("copy");
}
add_task(function* no_permission_deny_copy() {
function backgroundScript() {
browser.test.assertEq(false, doCopy("whatever"),
"copy should be denied without permission");
browser.test.sendMessage("ready");
}
let extensionData = {
background: `${doCopy};(${backgroundScript})();`,
};
let extension = ExtensionTestUtils.loadExtension(extensionData);
yield extension.startup();
yield extension.awaitMessage("ready");
yield extension.unload();
});
/** Selecting text in a bg page is not possible, skip test until it's fixed.
add_task(function* with_permission_allow_copy() {
function backgroundScript() {
browser.test.onMessage.addListener(txt => {
browser.test.assertEq(true, doCopy(txt),
"copy should be allowed with permission");
});
browser.test.sendMessage("ready");
}
let extensionData = {
background: `${doCopy};(${backgroundScript})();`,
manifest: {
permissions: [
"clipboardWrite",
],
},
};
let extension = ExtensionTestUtils.loadExtension(extensionData);
yield extension.startup();
yield extension.awaitMessage("ready");
const DUMMY_STR = "dummy string to copy";
yield new Promise(resolve => {
SimpleTest.waitForClipboard(DUMMY_STR, () => {
extension.sendMessage(DUMMY_STR);
}, resolve, resolve);
});
yield extension.unload();
}); */
add_task(function* content_script_no_permission_deny_copy() {
function contentScript() {
browser.test.assertEq(false, doCopy("whatever"),
"copy should be denied without permission");
browser.test.sendMessage("ready");
}
let extensionData = {
manifest: {
content_scripts: [{
js: ["contentscript.js"],
matches: ["http://mochi.test/*/file_sample.html"],
}],
},
files: {
"contentscript.js": `${doCopy};(${contentScript})();`,
},
};
let extension = ExtensionTestUtils.loadExtension(extensionData);
yield extension.startup();
let win = window.open("file_sample.html");
yield extension.awaitMessage("ready");
win.close();
yield extension.unload();
});
add_task(function* content_script_with_permission_allow_copy() {
function contentScript() {
browser.test.onMessage.addListener(txt => {
browser.test.assertEq(true, doCopy(txt),
"copy should be allowed with permission");
});
browser.test.sendMessage("ready");
}
let extensionData = {
manifest: {
content_scripts: [{
js: ["contentscript.js"],
matches: ["http://mochi.test/*/file_sample.html"],
}],
permissions: [
"clipboardWrite",
],
},
files: {
"contentscript.js": `${doCopy};(${contentScript})();`,
},
};
let extension = ExtensionTestUtils.loadExtension(extensionData);
yield extension.startup();
let win = window.open("file_sample.html");
yield extension.awaitMessage("ready");
const DUMMY_STR = "dummy string to copy in content script";
yield new Promise(resolve => {
SimpleTest.waitForClipboard(DUMMY_STR, () => {
extension.sendMessage(DUMMY_STR);
}, resolve, resolve);
});
win.close();
yield extension.unload();
});
</script>
</body>
</html>

View File

@ -29,6 +29,7 @@ function AddonPolicyService()
this.wrappedJSObject = this;
this.cspStrings = new Map();
this.backgroundPageUrlCallbacks = new Map();
this.checkHasPermissionCallbacks = new Map();
this.mayLoadURICallbacks = new Map();
this.localizeCallbacks = new Map();
@ -66,6 +67,17 @@ AddonPolicyService.prototype = {
return cb && cb(aAddonId) || '';
},
/*
* Invokes a callback (if any) associated with the addon to determine whether
* the addon is granted the |aPerm| API permission.
*
* @see nsIAddonPolicyService.addonHasPermission
*/
addonHasPermission(aAddonId, aPerm) {
let cb = this.checkHasPermissionCallbacks.get(aAddonId);
return cb ? cb(aPerm) : false;
},
/*
* Invokes a callback (if any) associated with the addon to determine whether
* unprivileged code running within the addon is allowed to perform loads from
@ -119,6 +131,19 @@ AddonPolicyService.prototype = {
return cb(aURI);
},
/*
* Sets the callbacks used in addonHasPermission above. Not accessible over
* XPCOM - callers should use .wrappedJSObject on the service to call it
* directly.
*/
setAddonHasPermissionCallback(aAddonId, aCallback) {
if (aCallback) {
this.checkHasPermissionCallbacks.set(aAddonId, aCallback);
} else {
this.checkHasPermissionCallbacks.delete(aAddonId);
}
},
/*
* Sets the callbacks used in addonMayLoadURI above. Not accessible over
* XPCOM - callers should use .wrappedJSObject on the service to call it