Skip to content

Commit

Permalink
Merge pull request #35 from ExodusMovement/mzndako/fix/ios-whitelist
Browse files Browse the repository at this point in the history
fix: enforce webview camera permission whitelist for ios
  • Loading branch information
mzndako authored Feb 19, 2025
2 parents e4e1540 + e69cae0 commit 148c1e2
Show file tree
Hide file tree
Showing 7 changed files with 53 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
protected @Nullable String mDownloadingMessage = null;
protected @Nullable String mLackPermissionToDownloadMessage = null;

private static Set<String> cameraPermissionOriginWhitelist;

public RNCWebViewManager() {
cameraPermissionOriginWhitelist = new HashSet<>();
cameraPermissionOriginWhitelist.add("https://alchemy.veriff.com/");
cameraPermissionOriginWhitelist.add("https://magic.veriff.me/");

mWebViewConfig = new WebViewConfig() {
public void configWebView(WebView webView) {
}
Expand Down Expand Up @@ -232,6 +226,16 @@ private String getLackPermissionToDownloadMessage() {
return mDownloadingMessage == null ? DEFAULT_LACK_PERMISSION_TO_DOWNLOAD_MESSAGE : mLackPermissionToDownloadMessage;
}

@ReactProp(name = "cameraPermissionWhitelist")
public void setCameraPermissionWhitelist(RNCWebView webView, ReadableArray whitelist) {
Set<String> whitelistSet = new HashSet<>();
for (int i = 0; i < whitelist.size(); i++) {
whitelistSet.add(whitelist.getString(i));
}

mWebChromeClient.setCameraPermissionWhitelist(whitelistSet);
}

@ReactProp(name = "javaScriptEnabled")
public void setJavaScriptEnabled(WebView view, boolean enabled) {
view.getSettings().setJavaScriptEnabled(enabled);
Expand Down Expand Up @@ -998,12 +1002,17 @@ protected static class RNCWebChromeClient extends WebChromeClient implements Lif

// True if protected media should be allowed, false otherwise
protected boolean mAllowsProtectedMedia = false;
private Set<String> cameraPermissionWhitelist = new HashSet<>();

public RNCWebChromeClient(ReactContext reactContext, WebView webView) {
this.mReactContext = reactContext;
this.mWebView = webView;
}

public void setCameraPermissionWhitelist(Set<String> whitelist) {
this.cameraPermissionWhitelist = whitelist;
}

@Override
public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {

Expand Down Expand Up @@ -1065,11 +1074,21 @@ public void onPermissionRequest(final PermissionRequest request) {
grantedPermissions = new ArrayList<>();

ArrayList<String> requestedAndroidPermissions = new ArrayList<>();
String originUrl = request.getOrigin().toString();
String originHost;

try {
URL url = new URL(originUrl);
originHost = url.getHost();
} catch (MalformedURLException e) {
request.deny();
return;
}

for (String requestedResource : request.getResources()) {
String androidPermission = null;

if (cameraPermissionOriginWhitelist.contains(request.getOrigin().toString())) {
if (this.cameraPermissionWhitelist.contains(originHost)) {
if (requestedResource.equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) {
androidPermission = Manifest.permission.CAMERA;
} else if (requestedResource.equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) {
Expand Down
7 changes: 7 additions & 0 deletions apple/RNCWebView.m
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
static NSString *const MessageHandlerName = @"ReactNativeWebView";
static NSURLCredential* clientAuthenticationCredential;
static NSDictionary* customCertificatesForHost;
static NSSet *cameraPermissionWhitelist;

NSString *const CUSTOM_SELECTOR = @"_CUSTOM_SELECTOR_";

Expand Down Expand Up @@ -70,6 +71,7 @@ @interface RNCWebView () <WKUIDelegate, WKNavigationDelegate, WKScriptMessageHan
@property (nonatomic, strong) WKUserScript *postMessageScript;
@property (nonatomic, strong) WKUserScript *atStartScript;
@property (nonatomic, strong) WKUserScript *atEndScript;
@property (nonatomic, strong) NSArray<NSString *> *cameraPermissionWhitelist;
@end

@implementation RNCWebView
Expand Down Expand Up @@ -1054,6 +1056,11 @@ - (void) webView:(WKWebView *)webView
initiatedByFrame:(WKFrameInfo *)frame
type:(WKMediaCaptureType)type
decisionHandler:(void (^)(WKPermissionDecision decision))decisionHandler {
if (![self.cameraPermissionWhitelist containsObject:origin.host]) {
decisionHandler(WKPermissionDecisionDeny);
return;
}

if (_mediaCapturePermissionGrantType == RNCWebViewPermissionGrantType_GrantIfSameHost_ElsePrompt || _mediaCapturePermissionGrantType == RNCWebViewPermissionGrantType_GrantIfSameHost_ElseDeny) {
if ([origin.host isEqualToString:webView.URL.host]) {
decisionHandler(WKPermissionDecisionGrant);
Expand Down
1 change: 1 addition & 0 deletions apple/RNCWebViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ - (RCTUIView *)view
RCT_EXPORT_VIEW_PROPERTY(cacheEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(allowsLinkPreview, BOOL)
RCT_EXPORT_VIEW_PROPERTY(basicAuthCredential, NSDictionary)
RCT_EXPORT_VIEW_PROPERTY(cameraPermissionWhitelist, NSArray)

#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
RCT_EXPORT_VIEW_PROPERTY(contentInsetAdjustmentBehavior, UIScrollViewContentInsetAdjustmentBehavior)
Expand Down
3 changes: 3 additions & 0 deletions src/WebView.android.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
defaultRenderLoading,
useWebWiewLogic,
versionPasses,
removeHttpsFromOrigins,
} from './WebViewShared';
import {
AndroidWebViewProps,
Expand Down Expand Up @@ -67,6 +68,7 @@ const WebViewComponent = forwardRef<{}, AndroidWebViewProps>((props, ref) => {
scalesPageToFit = true,
saveFormDataDisabled = false,
cacheEnabled = true,
cameraPermissionWhitelist = [],
androidHardwareAccelerationDisabled = false,
androidLayerType = "none",
originWhitelist = defaultOriginWhitelist,
Expand Down Expand Up @@ -207,6 +209,7 @@ const WebViewComponent = forwardRef<{}, AndroidWebViewProps>((props, ref) => {
const webView = <NativeWebView
key="webViewKey"
{...otherProps}
cameraPermissionWhitelist={removeHttpsFromOrigins(cameraPermissionWhitelist)}
messagingEnabled={typeof onMessageProp === 'function'}
messagingModuleName={messagingModuleName}

Expand Down
3 changes: 3 additions & 0 deletions src/WebView.ios.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
defaultRenderLoading,
useWebWiewLogic,
versionPasses,
removeHttpsFromOrigins,
} from './WebViewShared';
import {
IOSWebViewProps,
Expand Down Expand Up @@ -72,6 +73,7 @@ const WebViewComponent = forwardRef<{}, IOSWebViewProps>((props, ref) => {
const {
javaScriptEnabled = true,
cacheEnabled = true,
cameraPermissionWhitelist = [],
originWhitelist = defaultOriginWhitelist,
deeplinkWhitelist = defaultDeeplinkWhitelist,
textInteractionEnabled= true,
Expand Down Expand Up @@ -190,6 +192,7 @@ const WebViewComponent = forwardRef<{}, IOSWebViewProps>((props, ref) => {
enableApplePay={enableApplePay}
javaScriptEnabled={javaScriptEnabled}
cacheEnabled={cacheEnabled}
cameraPermissionWhitelist={removeHttpsFromOrigins(cameraPermissionWhitelist)}
dataDetectorTypes={dataDetectorTypes}
useSharedProcessPool={useSharedProcessPool}
textInteractionEnabled={textInteractionEnabled}
Expand Down
4 changes: 4 additions & 0 deletions src/WebViewShared.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -316,3 +316,7 @@ export const versionPasses = (version: string | undefined, minimum: string | und
}
return true // equals
}

export const removeHttpsFromOrigins = (whitelist: string[]) => {
return whitelist.map((origin) => origin.replace('https://', ''))
}
10 changes: 9 additions & 1 deletion src/WebViewTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ export interface BasicAuthCredential {

export interface CommonNativeWebViewProps extends ViewProps {
cacheEnabled?: boolean;
cameraPermissionWhitelist?: string[];
incognito?: boolean;
injectedJavaScript?: string;
injectedJavaScriptBeforeContentLoaded?: string;
Expand Down Expand Up @@ -787,6 +788,13 @@ export interface WebViewSharedProps extends ViewProps {
*/
javaScriptEnabled?: boolean;


/**
* Defines a list of domain origins that can access camera.
*/

readonly cameraPermissionWhitelist?: string[];

/**
* Stylesheet object to set the style of the container view.
*/
Expand Down Expand Up @@ -889,7 +897,7 @@ export interface WebViewSharedProps extends ViewProps {
* List of protocol schemes to allow being deep linked to. This requires
* an exact match. The default behavior is to only allow "https:".
*/
readonly deeplinkWhitelist?: string[];
readonly deeplinkWhitelist?: string[];

/**
* Function that allows custom handling of any web view requests. Return
Expand Down

0 comments on commit 148c1e2

Please sign in to comment.