1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "extensions/browser/guest_view/web_view/web_view_permission_helper.h"
6
7#include "content/public/browser/render_process_host.h"
8#include "content/public/browser/render_view_host.h"
9#include "content/public/browser/user_metrics.h"
10#include "extensions/browser/api/extensions_api_client.h"
11#include "extensions/browser/guest_view/web_view/web_view_constants.h"
12#include "extensions/browser/guest_view/web_view/web_view_guest.h"
13#include "extensions/browser/guest_view/web_view/web_view_permission_helper_delegate.h"
14#include "extensions/browser/guest_view/web_view/web_view_permission_types.h"
15
16using content::BrowserPluginGuestDelegate;
17using content::RenderViewHost;
18
19namespace extensions {
20
21namespace {
22static std::string PermissionTypeToString(WebViewPermissionType type) {
23  switch (type) {
24    case WEB_VIEW_PERMISSION_TYPE_DOWNLOAD:
25      return webview::kPermissionTypeDownload;
26    case WEB_VIEW_PERMISSION_TYPE_FILESYSTEM:
27      return webview::kPermissionTypeFileSystem;
28    case WEB_VIEW_PERMISSION_TYPE_GEOLOCATION:
29      return webview::kPermissionTypeGeolocation;
30    case WEB_VIEW_PERMISSION_TYPE_JAVASCRIPT_DIALOG:
31      return webview::kPermissionTypeDialog;
32    case WEB_VIEW_PERMISSION_TYPE_LOAD_PLUGIN:
33      return webview::kPermissionTypeLoadPlugin;
34    case WEB_VIEW_PERMISSION_TYPE_MEDIA:
35      return webview::kPermissionTypeMedia;
36    case WEB_VIEW_PERMISSION_TYPE_NEW_WINDOW:
37      return webview::kPermissionTypeNewWindow;
38    case WEB_VIEW_PERMISSION_TYPE_POINTER_LOCK:
39      return webview::kPermissionTypePointerLock;
40    default:
41      NOTREACHED();
42      return std::string();
43  }
44}
45
46// static
47void RecordUserInitiatedUMA(
48    const WebViewPermissionHelper::PermissionResponseInfo& info,
49    bool allow) {
50  if (allow) {
51    // Note that |allow| == true means the embedder explicitly allowed the
52    // request. For some requests they might still fail. An example of such
53    // scenario would be: an embedder allows geolocation request but doesn't
54    // have geolocation access on its own.
55    switch (info.permission_type) {
56      case WEB_VIEW_PERMISSION_TYPE_DOWNLOAD:
57        content::RecordAction(
58            UserMetricsAction("WebView.PermissionAllow.Download"));
59        break;
60      case WEB_VIEW_PERMISSION_TYPE_FILESYSTEM:
61        content::RecordAction(
62            UserMetricsAction("WebView.PermissionAllow.FileSystem"));
63        break;
64      case WEB_VIEW_PERMISSION_TYPE_GEOLOCATION:
65        content::RecordAction(
66            UserMetricsAction("WebView.PermissionAllow.Geolocation"));
67        break;
68      case WEB_VIEW_PERMISSION_TYPE_JAVASCRIPT_DIALOG:
69        content::RecordAction(
70            UserMetricsAction("WebView.PermissionAllow.JSDialog"));
71        break;
72      case WEB_VIEW_PERMISSION_TYPE_LOAD_PLUGIN:
73        content::RecordAction(
74            UserMetricsAction("WebView.Guest.PermissionAllow.PluginLoad"));
75      case WEB_VIEW_PERMISSION_TYPE_MEDIA:
76        content::RecordAction(
77            UserMetricsAction("WebView.PermissionAllow.Media"));
78        break;
79      case WEB_VIEW_PERMISSION_TYPE_NEW_WINDOW:
80        content::RecordAction(
81            UserMetricsAction("BrowserPlugin.PermissionAllow.NewWindow"));
82        break;
83      case WEB_VIEW_PERMISSION_TYPE_POINTER_LOCK:
84        content::RecordAction(
85            UserMetricsAction("WebView.PermissionAllow.PointerLock"));
86        break;
87      default:
88        break;
89    }
90  } else {
91    switch (info.permission_type) {
92      case WEB_VIEW_PERMISSION_TYPE_DOWNLOAD:
93        content::RecordAction(
94            UserMetricsAction("WebView.PermissionDeny.Download"));
95        break;
96      case WEB_VIEW_PERMISSION_TYPE_FILESYSTEM:
97        content::RecordAction(
98            UserMetricsAction("WebView.PermissionDeny.FileSystem"));
99        break;
100      case WEB_VIEW_PERMISSION_TYPE_GEOLOCATION:
101        content::RecordAction(
102            UserMetricsAction("WebView.PermissionDeny.Geolocation"));
103        break;
104      case WEB_VIEW_PERMISSION_TYPE_JAVASCRIPT_DIALOG:
105        content::RecordAction(
106            UserMetricsAction("WebView.PermissionDeny.JSDialog"));
107        break;
108      case WEB_VIEW_PERMISSION_TYPE_LOAD_PLUGIN:
109        content::RecordAction(
110            UserMetricsAction("WebView.Guest.PermissionDeny.PluginLoad"));
111        break;
112      case WEB_VIEW_PERMISSION_TYPE_MEDIA:
113        content::RecordAction(
114            UserMetricsAction("WebView.PermissionDeny.Media"));
115        break;
116      case WEB_VIEW_PERMISSION_TYPE_NEW_WINDOW:
117        content::RecordAction(
118            UserMetricsAction("BrowserPlugin.PermissionDeny.NewWindow"));
119        break;
120      case WEB_VIEW_PERMISSION_TYPE_POINTER_LOCK:
121        content::RecordAction(
122            UserMetricsAction("WebView.PermissionDeny.PointerLock"));
123        break;
124      default:
125        break;
126    }
127  }
128}
129
130} // namespace
131
132WebViewPermissionHelper::WebViewPermissionHelper(WebViewGuest* web_view_guest)
133    : content::WebContentsObserver(web_view_guest->web_contents()),
134      next_permission_request_id_(guestview::kInstanceIDNone),
135      web_view_guest_(web_view_guest),
136      weak_factory_(this) {
137      web_view_permission_helper_delegate_.reset(
138          ExtensionsAPIClient::Get()->CreateWebViewPermissionHelperDelegate(
139              this));
140}
141
142WebViewPermissionHelper::~WebViewPermissionHelper() {
143}
144
145// static
146WebViewPermissionHelper* WebViewPermissionHelper::FromFrameID(
147    int render_process_id,
148    int render_frame_id) {
149  WebViewGuest* web_view_guest = WebViewGuest::FromFrameID(
150      render_process_id, render_frame_id);
151  if (!web_view_guest) {
152    return NULL;
153  }
154  return web_view_guest->web_view_permission_helper_.get();
155}
156
157// static
158WebViewPermissionHelper* WebViewPermissionHelper::FromWebContents(
159      content::WebContents* web_contents) {
160  WebViewGuest* web_view_guest = WebViewGuest::FromWebContents(web_contents);
161  if (!web_view_guest)
162      return NULL;
163  return web_view_guest->web_view_permission_helper_.get();
164}
165
166#if defined(ENABLE_PLUGINS)
167bool WebViewPermissionHelper::OnMessageReceived(
168    const IPC::Message& message,
169    content::RenderFrameHost* render_frame_host) {
170  return web_view_permission_helper_delegate_->OnMessageReceived(
171      message, render_frame_host);
172}
173
174bool WebViewPermissionHelper::OnMessageReceived(const IPC::Message& message) {
175  return web_view_permission_helper_delegate_->OnMessageReceived(message);
176}
177#endif  // defined(ENABLE_PLUGINS)
178
179void WebViewPermissionHelper::RequestMediaAccessPermission(
180    content::WebContents* source,
181    const content::MediaStreamRequest& request,
182    const content::MediaResponseCallback& callback) {
183  base::DictionaryValue request_info;
184  request_info.SetString(guestview::kUrl, request.security_origin.spec());
185  RequestPermission(
186      WEB_VIEW_PERMISSION_TYPE_MEDIA,
187      request_info,
188      base::Bind(&WebViewPermissionHelper::OnMediaPermissionResponse,
189                 weak_factory_.GetWeakPtr(),
190                 request,
191                 callback),
192      false /* allowed_by_default */);
193}
194
195bool WebViewPermissionHelper::CheckMediaAccessPermission(
196    content::WebContents* source,
197    const GURL& security_origin,
198    content::MediaStreamType type) {
199  if (!web_view_guest()->attached() ||
200      !web_view_guest()->embedder_web_contents()->GetDelegate()) {
201    return false;
202  }
203  return web_view_guest()
204      ->embedder_web_contents()
205      ->GetDelegate()
206      ->CheckMediaAccessPermission(
207          web_view_guest()->embedder_web_contents(), security_origin, type);
208}
209
210void WebViewPermissionHelper::OnMediaPermissionResponse(
211    const content::MediaStreamRequest& request,
212    const content::MediaResponseCallback& callback,
213    bool allow,
214    const std::string& user_input) {
215  if (!allow) {
216    callback.Run(content::MediaStreamDevices(),
217                 content::MEDIA_DEVICE_PERMISSION_DENIED,
218                 scoped_ptr<content::MediaStreamUI>());
219    return;
220  }
221  if (!web_view_guest()->attached() ||
222      !web_view_guest()->embedder_web_contents()->GetDelegate()) {
223    callback.Run(content::MediaStreamDevices(),
224                 content::MEDIA_DEVICE_INVALID_STATE,
225                 scoped_ptr<content::MediaStreamUI>());
226    return;
227  }
228
229  web_view_guest()
230      ->embedder_web_contents()
231      ->GetDelegate()
232      ->RequestMediaAccessPermission(
233          web_view_guest()->embedder_web_contents(), request, callback);
234}
235
236void WebViewPermissionHelper::CanDownload(
237    content::RenderViewHost* render_view_host,
238    const GURL& url,
239    const std::string& request_method,
240    const base::Callback<void(bool)>& callback) {
241  web_view_permission_helper_delegate_->CanDownload(
242      render_view_host, url, request_method, callback);
243}
244
245void WebViewPermissionHelper::RequestPointerLockPermission(
246    bool user_gesture,
247    bool last_unlocked_by_target,
248    const base::Callback<void(bool)>& callback) {
249  web_view_permission_helper_delegate_->RequestPointerLockPermission(
250      user_gesture, last_unlocked_by_target, callback);
251}
252
253void WebViewPermissionHelper::RequestGeolocationPermission(
254    int bridge_id,
255    const GURL& requesting_frame,
256    bool user_gesture,
257    const base::Callback<void(bool)>& callback) {
258  web_view_permission_helper_delegate_->RequestGeolocationPermission(
259      bridge_id, requesting_frame, user_gesture, callback);
260}
261
262void WebViewPermissionHelper::CancelGeolocationPermissionRequest(
263    int bridge_id) {
264  web_view_permission_helper_delegate_->CancelGeolocationPermissionRequest(
265      bridge_id);
266}
267
268void WebViewPermissionHelper::RequestFileSystemPermission(
269    const GURL& url,
270    bool allowed_by_default,
271    const base::Callback<void(bool)>& callback) {
272  web_view_permission_helper_delegate_->RequestFileSystemPermission(
273      url, allowed_by_default, callback);
274}
275
276void WebViewPermissionHelper::FileSystemAccessedAsync(int render_process_id,
277                                                      int render_frame_id,
278                                                      int request_id,
279                                                      const GURL& url,
280                                                      bool blocked_by_policy) {
281  web_view_permission_helper_delegate_->FileSystemAccessedAsync(
282      render_process_id, render_frame_id, request_id, url, blocked_by_policy);
283}
284
285void WebViewPermissionHelper::FileSystemAccessedSync(int render_process_id,
286                                                     int render_frame_id,
287                                                     const GURL& url,
288                                                     bool blocked_by_policy,
289                                                     IPC::Message* reply_msg) {
290  web_view_permission_helper_delegate_->FileSystemAccessedSync(
291      render_process_id, render_frame_id, url, blocked_by_policy, reply_msg);
292}
293
294int WebViewPermissionHelper::RequestPermission(
295    WebViewPermissionType permission_type,
296    const base::DictionaryValue& request_info,
297    const PermissionResponseCallback& callback,
298    bool allowed_by_default) {
299  // If there are too many pending permission requests then reject this request.
300  if (pending_permission_requests_.size() >=
301      webview::kMaxOutstandingPermissionRequests) {
302    // Let the stack unwind before we deny the permission request so that
303    // objects held by the permission request are not destroyed immediately
304    // after creation. This is to allow those same objects to be accessed again
305    // in the same scope without fear of use after freeing.
306    base::MessageLoop::current()->PostTask(
307        FROM_HERE,
308        base::Bind(&PermissionResponseCallback::Run,
309                   base::Owned(new PermissionResponseCallback(callback)),
310                   allowed_by_default,
311                   std::string()));
312    return webview::kInvalidPermissionRequestID;
313  }
314
315  int request_id = next_permission_request_id_++;
316  pending_permission_requests_[request_id] =
317      PermissionResponseInfo(callback, permission_type, allowed_by_default);
318  scoped_ptr<base::DictionaryValue> args(request_info.DeepCopy());
319  args->SetInteger(webview::kRequestId, request_id);
320  switch (permission_type) {
321    case WEB_VIEW_PERMISSION_TYPE_NEW_WINDOW: {
322      web_view_guest_->DispatchEventToEmbedder(
323          new GuestViewBase::Event(webview::kEventNewWindow, args.Pass()));
324      break;
325    }
326    case WEB_VIEW_PERMISSION_TYPE_JAVASCRIPT_DIALOG: {
327      web_view_guest_->DispatchEventToEmbedder(
328          new GuestViewBase::Event(webview::kEventDialog, args.Pass()));
329      break;
330    }
331    default: {
332      args->SetString(webview::kPermission,
333                      PermissionTypeToString(permission_type));
334      web_view_guest_->DispatchEventToEmbedder(new GuestViewBase::Event(
335          webview::kEventPermissionRequest,
336          args.Pass()));
337      break;
338    }
339  }
340  return request_id;
341}
342
343WebViewPermissionHelper::SetPermissionResult
344WebViewPermissionHelper::SetPermission(
345    int request_id,
346    PermissionResponseAction action,
347    const std::string& user_input) {
348  RequestMap::iterator request_itr =
349      pending_permission_requests_.find(request_id);
350
351  if (request_itr == pending_permission_requests_.end())
352    return SET_PERMISSION_INVALID;
353
354  const PermissionResponseInfo& info = request_itr->second;
355  bool allow = (action == ALLOW) ||
356      ((action == DEFAULT) && info.allowed_by_default);
357
358  info.callback.Run(allow, user_input);
359
360  // Only record user initiated (i.e. non-default) actions.
361  if (action != DEFAULT)
362    RecordUserInitiatedUMA(info, allow);
363
364  pending_permission_requests_.erase(request_itr);
365
366  return allow ? SET_PERMISSION_ALLOWED : SET_PERMISSION_DENIED;
367}
368
369void WebViewPermissionHelper::CancelPendingPermissionRequest(int request_id) {
370  RequestMap::iterator request_itr =
371      pending_permission_requests_.find(request_id);
372
373  if (request_itr == pending_permission_requests_.end())
374    return;
375
376  pending_permission_requests_.erase(request_itr);
377}
378
379WebViewPermissionHelper::PermissionResponseInfo::PermissionResponseInfo()
380    : permission_type(WEB_VIEW_PERMISSION_TYPE_UNKNOWN),
381      allowed_by_default(false) {
382}
383
384WebViewPermissionHelper::PermissionResponseInfo::PermissionResponseInfo(
385    const PermissionResponseCallback& callback,
386    WebViewPermissionType permission_type,
387    bool allowed_by_default)
388    : callback(callback),
389      permission_type(permission_type),
390      allowed_by_default(allowed_by_default) {
391}
392
393WebViewPermissionHelper::PermissionResponseInfo::~PermissionResponseInfo() {
394}
395
396}  // namespace extensions
397