-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Javascript Binding - Add ability to limit access to JavaScript Bound objects to specific origins #5085
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Javascript Binding - Add ability to limit access to JavaScript Bound objects to specific origins #5085
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,7 @@ | |
#include "Wrapper\Browser.h" | ||
#include "..\CefSharp.Core.Runtime\Internals\Messaging\Messages.h" | ||
#include "..\CefSharp.Core.Runtime\Internals\Serialization\Primitives.h" | ||
#include <include/cef_parser.h> | ||
|
||
using namespace System; | ||
using namespace System::Diagnostics; | ||
|
@@ -100,6 +101,16 @@ namespace CefSharp | |
} | ||
|
||
_jsBindingApiEnabled = extraInfo->GetBool("JavascriptBindingApiEnabled"); | ||
_jsBindingApiHasAllowOrigins = extraInfo->GetBool("JavascriptBindingApiHasAllowOrigins"); | ||
|
||
if (_jsBindingApiHasAllowOrigins) | ||
{ | ||
auto allowOrigins = extraInfo->GetList("JavascriptBindingApiAllowOrigins"); | ||
if (allowOrigins.get() && allowOrigins->IsValid()) | ||
{ | ||
_jsBindingApiAllowOrigins = allowOrigins->Copy(); | ||
} | ||
} | ||
|
||
if (extraInfo->HasKey("JsBindingPropertyName") || extraInfo->HasKey("JsBindingPropertyNameCamelCase")) | ||
{ | ||
|
@@ -149,50 +160,88 @@ namespace CefSharp | |
|
||
if (_jsBindingApiEnabled) | ||
{ | ||
//TODO: Look at adding some sort of javascript mapping layer to reduce the code duplication | ||
auto global = context->GetGlobal(); | ||
auto browserWrapper = FindBrowserWrapper(browser->GetIdentifier()); | ||
auto processId = System::Diagnostics::Process::GetCurrentProcess()->Id; | ||
|
||
//TODO: JSB: Split functions into their own classes | ||
//Browser wrapper is only used for BindObjectAsync | ||
auto bindObjAsyncFunction = CefV8Value::CreateFunction(kBindObjectAsync, new BindObjectAsyncHandler(_registerBoundObjectRegistry, _javascriptObjects, browserWrapper)); | ||
auto unBindObjFunction = CefV8Value::CreateFunction(kDeleteBoundObject, new RegisterBoundObjectHandler(_javascriptObjects)); | ||
auto removeObjectFromCacheFunction = CefV8Value::CreateFunction(kRemoveObjectFromCache, new RegisterBoundObjectHandler(_javascriptObjects)); | ||
auto isObjectCachedFunction = CefV8Value::CreateFunction(kIsObjectCached, new RegisterBoundObjectHandler(_javascriptObjects)); | ||
auto postMessageFunction = CefV8Value::CreateFunction(kPostMessage, new JavascriptPostMessageHandler(rootObject == nullptr ? nullptr : rootObject->CallbackRegistry)); | ||
auto promiseHandlerFunction = CefV8Value::CreateFunction(kSendEvalScriptResponse, new JavascriptPromiseHandler()); | ||
|
||
//By default We'll support both CefSharp and cefSharp, for those who prefer the JS style | ||
auto createCefSharpObj = !_jsBindingPropertyName.empty(); | ||
auto createCefSharpObjCamelCase = !_jsBindingPropertyNameCamelCase.empty(); | ||
|
||
if (createCefSharpObj) | ||
auto createObjects = true; | ||
|
||
if (_jsBindingApiHasAllowOrigins) | ||
{ | ||
auto cefSharpObj = CefV8Value::CreateObject(nullptr, nullptr); | ||
cefSharpObj->SetValue(kBindObjectAsync, bindObjAsyncFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
cefSharpObj->SetValue(kDeleteBoundObject, unBindObjFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
cefSharpObj->SetValue(kRemoveObjectFromCache, removeObjectFromCacheFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
cefSharpObj->SetValue(kIsObjectCached, isObjectCachedFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
cefSharpObj->SetValue(kPostMessage, postMessageFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
cefSharpObj->SetValue(kSendEvalScriptResponse, promiseHandlerFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
cefSharpObj->SetValue(kRenderProcessId, CefV8Value::CreateInt(processId), CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
|
||
global->SetValue(_jsBindingPropertyName, cefSharpObj, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_READONLY); | ||
createObjects = false; | ||
|
||
auto frameUrl = frame->GetURL(); | ||
|
||
CefURLParts frameUrlParts; | ||
|
||
if (CefParseURL(frameUrl, frameUrlParts)) | ||
{ | ||
auto frameUrlOrigin = CefString(frameUrlParts.origin.str, frameUrlParts.origin.length); | ||
auto clrframeUrlOrigin = StringUtils::ToClr(frameUrlOrigin); | ||
|
||
auto size = static_cast<int>(_jsBindingApiAllowOrigins->GetSize()); | ||
|
||
for (int i = 0; i < size; i++) | ||
{ | ||
auto origin = _jsBindingApiAllowOrigins->GetString(i); | ||
|
||
auto clrOrigin = StringUtils::ToClr(origin); | ||
|
||
auto originEqual = String::Compare(clrframeUrlOrigin, clrOrigin, StringComparison::InvariantCultureIgnoreCase); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to find a more efficient string comparison, ideally compare the native strings |
||
|
||
if (originEqual == 0) | ||
{ | ||
createObjects = true; | ||
|
||
break; | ||
} | ||
} | ||
} | ||
} | ||
|
||
if (createCefSharpObjCamelCase) | ||
if (createObjects) | ||
{ | ||
auto cefSharpObjCamelCase = CefV8Value::CreateObject(nullptr, nullptr); | ||
cefSharpObjCamelCase->SetValue(kBindObjectAsyncCamelCase, bindObjAsyncFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
cefSharpObjCamelCase->SetValue(kDeleteBoundObjectCamelCase, unBindObjFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
cefSharpObjCamelCase->SetValue(kRemoveObjectFromCacheCamelCase, removeObjectFromCacheFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
cefSharpObjCamelCase->SetValue(kIsObjectCachedCamelCase, isObjectCachedFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
cefSharpObjCamelCase->SetValue(kPostMessageCamelCase, postMessageFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
cefSharpObjCamelCase->SetValue(kSendEvalScriptResponseCamelCase, promiseHandlerFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
cefSharpObjCamelCase->SetValue(kRenderProcessIdCamelCase, CefV8Value::CreateInt(processId), CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
|
||
global->SetValue(_jsBindingPropertyNameCamelCase, cefSharpObjCamelCase, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_READONLY); | ||
//TODO: Look at adding some sort of javascript mapping layer to reduce the code duplication | ||
auto global = context->GetGlobal(); | ||
auto browserWrapper = FindBrowserWrapper(browser->GetIdentifier()); | ||
auto processId = System::Diagnostics::Process::GetCurrentProcess()->Id; | ||
|
||
//TODO: JSB: Split functions into their own classes | ||
//Browser wrapper is only used for BindObjectAsync | ||
auto bindObjAsyncFunction = CefV8Value::CreateFunction(kBindObjectAsync, new BindObjectAsyncHandler(_registerBoundObjectRegistry, _javascriptObjects, browserWrapper)); | ||
auto unBindObjFunction = CefV8Value::CreateFunction(kDeleteBoundObject, new RegisterBoundObjectHandler(_javascriptObjects)); | ||
auto removeObjectFromCacheFunction = CefV8Value::CreateFunction(kRemoveObjectFromCache, new RegisterBoundObjectHandler(_javascriptObjects)); | ||
auto isObjectCachedFunction = CefV8Value::CreateFunction(kIsObjectCached, new RegisterBoundObjectHandler(_javascriptObjects)); | ||
auto postMessageFunction = CefV8Value::CreateFunction(kPostMessage, new JavascriptPostMessageHandler(rootObject == nullptr ? nullptr : rootObject->CallbackRegistry)); | ||
auto promiseHandlerFunction = CefV8Value::CreateFunction(kSendEvalScriptResponse, new JavascriptPromiseHandler()); | ||
|
||
//By default We'll support both CefSharp and cefSharp, for those who prefer the JS style | ||
auto createCefSharpObj = !_jsBindingPropertyName.empty(); | ||
auto createCefSharpObjCamelCase = !_jsBindingPropertyNameCamelCase.empty(); | ||
|
||
if (createCefSharpObj) | ||
{ | ||
auto cefSharpObj = CefV8Value::CreateObject(nullptr, nullptr); | ||
cefSharpObj->SetValue(kBindObjectAsync, bindObjAsyncFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
cefSharpObj->SetValue(kDeleteBoundObject, unBindObjFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
cefSharpObj->SetValue(kRemoveObjectFromCache, removeObjectFromCacheFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
cefSharpObj->SetValue(kIsObjectCached, isObjectCachedFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
cefSharpObj->SetValue(kPostMessage, postMessageFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
cefSharpObj->SetValue(kSendEvalScriptResponse, promiseHandlerFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
cefSharpObj->SetValue(kRenderProcessId, CefV8Value::CreateInt(processId), CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
|
||
global->SetValue(_jsBindingPropertyName, cefSharpObj, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_READONLY); | ||
} | ||
|
||
if (createCefSharpObjCamelCase) | ||
{ | ||
auto cefSharpObjCamelCase = CefV8Value::CreateObject(nullptr, nullptr); | ||
cefSharpObjCamelCase->SetValue(kBindObjectAsyncCamelCase, bindObjAsyncFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
cefSharpObjCamelCase->SetValue(kDeleteBoundObjectCamelCase, unBindObjFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
cefSharpObjCamelCase->SetValue(kRemoveObjectFromCacheCamelCase, removeObjectFromCacheFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
cefSharpObjCamelCase->SetValue(kIsObjectCachedCamelCase, isObjectCachedFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
cefSharpObjCamelCase->SetValue(kPostMessageCamelCase, postMessageFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
cefSharpObjCamelCase->SetValue(kSendEvalScriptResponseCamelCase, promiseHandlerFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
cefSharpObjCamelCase->SetValue(kRenderProcessIdCamelCase, CefV8Value::CreateInt(processId), CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); | ||
|
||
global->SetValue(_jsBindingPropertyNameCamelCase, cefSharpObjCamelCase, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_READONLY); | ||
} | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,8 @@ namespace CefSharp | |
bool _focusedNodeChangedEnabled; | ||
bool _legacyBindingEnabled; | ||
bool _jsBindingApiEnabled = true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Storing these on a per browser basis would provide greater flexibility, we could create a class that has a lookup by browser id. |
||
bool _jsBindingApiHasAllowOrigins = false; | ||
CefRefPtr<CefListValue> _jsBindingApiAllowOrigins; | ||
|
||
// The property names used to call bound objects | ||
CefString _jsBindingPropertyName; | ||
|
@@ -69,6 +71,8 @@ namespace CefSharp | |
} | ||
delete _onBrowserCreated; | ||
delete _onBrowserDestroyed; | ||
|
||
_jsBindingApiAllowOrigins = nullptr; | ||
} | ||
|
||
CefBrowserWrapper^ FindBrowserWrapper(int browserId); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
// | ||
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. | ||
|
||
using System.Collections.Generic; | ||
using CefSharp.Internals; | ||
|
||
namespace CefSharp.JavascriptBinding | ||
|
@@ -15,6 +16,7 @@ public class JavascriptBindingSettings : FreezableBase | |
private bool legacyBindingEnabled; | ||
private string jsBindingGlobalObjectName; | ||
private bool jsBindingApiEnabled = true; | ||
private string[] javascriptBindingApiAllowOrigins; | ||
|
||
/// <summary> | ||
/// The Javascript methods that CefSharp provides in relation to JavaScript Binding are | ||
|
@@ -33,6 +35,24 @@ public bool JavascriptBindingApiEnabled | |
} | ||
} | ||
|
||
/// <summary> | ||
/// When <see cref="JavascriptBindingApiEnabled"/> is set to true, set a collection | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Needs review |
||
/// of origins to limit which origins CefSharp will create it's global (window) object. | ||
/// </summary> | ||
/// <remarks> | ||
/// If you wish to create the CefSharp object for a limited set of origins then set this property | ||
/// </remarks> | ||
public string[] JavascriptBindingApiAllowOrigins | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
{ | ||
get { return javascriptBindingApiAllowOrigins; } | ||
set | ||
{ | ||
ThrowIfFrozen(); | ||
|
||
javascriptBindingApiAllowOrigins = value; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// The Javascript methods that CefSharp provides in relation to JavaScript Binding are | ||
/// created using a Global (window) Object. Settings this property allows you to customise | ||
|
@@ -95,5 +115,17 @@ public bool AlwaysInterceptAsynchronously | |
alwaysInterceptAsynchronously = value; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// HasJavascriptBindingApiAllowOrigins | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Needs review. |
||
/// </summary> | ||
/// <returns>bool true if <see cref="JavascriptBindingApiAllowOrigins"/> is non empty collection.</returns> | ||
public bool HasJavascriptBindingApiAllowOrigins() | ||
{ | ||
if (javascriptBindingApiAllowOrigins == null) | ||
return false; | ||
|
||
return javascriptBindingApiAllowOrigins.Length > 0; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extract this into a helper method would probably make this a lot cleaner