| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "android_webview/browser/aw_permission_manager.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "android_webview/browser/aw_browser_permission_request_delegate.h" |
| #include "base/callback.h" |
| #include "base/containers/hash_tables.h" |
| #include "base/logging.h" |
| #include "content/public/browser/permission_controller.h" |
| #include "content/public/browser/permission_type.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/web_contents.h" |
| |
| using blink::mojom::PermissionStatus; |
| using content::PermissionType; |
| |
| using RequestPermissionsCallback = |
| base::OnceCallback<void(const std::vector<PermissionStatus>&)>; |
| |
| namespace android_webview { |
| |
| namespace { |
| |
| void PermissionRequestResponseCallbackWrapper( |
| const base::Callback<void(PermissionStatus)>& callback, |
| const std::vector<PermissionStatus>& vector) { |
| DCHECK_EQ(vector.size(), 1ul); |
| callback.Run(vector[0]); |
| } |
| |
| } // namespace |
| |
| class LastRequestResultCache { |
| public: |
| LastRequestResultCache() = default; |
| |
| void SetResult(PermissionType permission, |
| const GURL& requesting_origin, |
| const GURL& embedding_origin, |
| PermissionStatus status) { |
| DCHECK(status == PermissionStatus::GRANTED || |
| status == PermissionStatus::DENIED); |
| |
| // TODO(ddorwin): We should be denying empty origins at a higher level. |
| if (requesting_origin.is_empty() || embedding_origin.is_empty()) { |
| DLOG(WARNING) << "Not caching result because of empty origin."; |
| return; |
| } |
| |
| if (!requesting_origin.is_valid()) { |
| NOTREACHED() << requesting_origin.possibly_invalid_spec(); |
| return; |
| } |
| if (!embedding_origin.is_valid()) { |
| NOTREACHED() << embedding_origin.possibly_invalid_spec(); |
| return; |
| } |
| |
| if (permission != PermissionType::PROTECTED_MEDIA_IDENTIFIER) { |
| // Other permissions are not cached. |
| return; |
| } |
| |
| std::string key = GetCacheKey(requesting_origin, embedding_origin); |
| if (key.empty()) { |
| NOTREACHED(); |
| // Never store an empty key because it could inadvertently be used for |
| // another combination. |
| return; |
| } |
| pmi_result_cache_[key] = status; |
| } |
| |
| PermissionStatus GetResult(PermissionType permission, |
| const GURL& requesting_origin, |
| const GURL& embedding_origin) const { |
| // TODO(ddorwin): We should be denying empty origins at a higher level. |
| if (requesting_origin.is_empty() || embedding_origin.is_empty()) { |
| return PermissionStatus::ASK; |
| } |
| |
| DCHECK(requesting_origin.is_valid()) |
| << requesting_origin.possibly_invalid_spec(); |
| DCHECK(embedding_origin.is_valid()) |
| << embedding_origin.possibly_invalid_spec(); |
| |
| if (permission != PermissionType::PROTECTED_MEDIA_IDENTIFIER) { |
| NOTREACHED() << "Results are only cached for PROTECTED_MEDIA_IDENTIFIER"; |
| return PermissionStatus::ASK; |
| } |
| |
| std::string key = GetCacheKey(requesting_origin, embedding_origin); |
| StatusMap::const_iterator it = pmi_result_cache_.find(key); |
| if (it == pmi_result_cache_.end()) { |
| DLOG(WARNING) << "GetResult() called for uncached origins: " << key; |
| return PermissionStatus::ASK; |
| } |
| |
| DCHECK(!key.empty()); |
| return it->second; |
| } |
| |
| void ClearResult(PermissionType permission, |
| const GURL& requesting_origin, |
| const GURL& embedding_origin) { |
| // TODO(ddorwin): We should be denying empty origins at a higher level. |
| if (requesting_origin.is_empty() || embedding_origin.is_empty()) { |
| return; |
| } |
| |
| DCHECK(requesting_origin.is_valid()) |
| << requesting_origin.possibly_invalid_spec(); |
| DCHECK(embedding_origin.is_valid()) |
| << embedding_origin.possibly_invalid_spec(); |
| |
| |
| if (permission != PermissionType::PROTECTED_MEDIA_IDENTIFIER) { |
| // Other permissions are not cached, so nothing to clear. |
| return; |
| } |
| |
| std::string key = GetCacheKey(requesting_origin, embedding_origin); |
| pmi_result_cache_.erase(key); |
| } |
| |
| private: |
| // Returns a concatenation of the origins to be used as the index. |
| // Returns the empty string if either origin is invalid or empty. |
| static std::string GetCacheKey(const GURL& requesting_origin, |
| const GURL& embedding_origin) { |
| const std::string& requesting = requesting_origin.spec(); |
| const std::string& embedding = embedding_origin.spec(); |
| if (requesting.empty() || embedding.empty()) |
| return std::string(); |
| return requesting + "," + embedding; |
| } |
| |
| using StatusMap = base::hash_map<std::string, PermissionStatus>; |
| StatusMap pmi_result_cache_; |
| |
| DISALLOW_COPY_AND_ASSIGN(LastRequestResultCache); |
| }; |
| |
| class AwPermissionManager::PendingRequest { |
| public: |
| PendingRequest(const std::vector<PermissionType> permissions, |
| GURL requesting_origin, |
| GURL embedding_origin, |
| int render_process_id, |
| int render_frame_id, |
| RequestPermissionsCallback callback) |
| : permissions(permissions), |
| requesting_origin(requesting_origin), |
| embedding_origin(embedding_origin), |
| render_process_id(render_process_id), |
| render_frame_id(render_frame_id), |
| callback(std::move(callback)), |
| results(permissions.size(), PermissionStatus::DENIED), |
| cancelled_(false) { |
| for (size_t i = 0; i < permissions.size(); ++i) |
| permission_index_map_.insert(std::make_pair(permissions[i], i)); |
| } |
| |
| ~PendingRequest() = default; |
| |
| void SetPermissionStatus(PermissionType type, PermissionStatus status) { |
| auto result = permission_index_map_.find(type); |
| if (result == permission_index_map_.end()) { |
| NOTREACHED(); |
| return; |
| } |
| DCHECK(!IsCompleted()); |
| results[result->second] = status; |
| resolved_permissions_.insert(type); |
| } |
| |
| PermissionStatus GetPermissionStatus(PermissionType type) { |
| auto result = permission_index_map_.find(type); |
| if (result == permission_index_map_.end()) { |
| NOTREACHED(); |
| return PermissionStatus::DENIED; |
| } |
| return results[result->second]; |
| } |
| |
| bool HasPermissionType(PermissionType type) { |
| return permission_index_map_.find(type) != permission_index_map_.end(); |
| } |
| |
| bool IsCompleted() const { |
| return results.size() == resolved_permissions_.size(); |
| } |
| |
| bool IsCompleted(PermissionType type) const { |
| return resolved_permissions_.count(type) != 0; |
| } |
| |
| void Cancel() { cancelled_ = true; } |
| |
| bool IsCancelled() const { return cancelled_; } |
| |
| std::vector<PermissionType> permissions; |
| GURL requesting_origin; |
| GURL embedding_origin; |
| int render_process_id; |
| int render_frame_id; |
| RequestPermissionsCallback callback; |
| std::vector<PermissionStatus> results; |
| |
| private: |
| std::map<PermissionType, size_t> permission_index_map_; |
| std::set<PermissionType> resolved_permissions_; |
| bool cancelled_; |
| }; |
| |
| AwPermissionManager::AwPermissionManager() |
| : result_cache_(new LastRequestResultCache), weak_ptr_factory_(this) {} |
| |
| AwPermissionManager::~AwPermissionManager() { |
| CancelPermissionRequests(); |
| } |
| |
| int AwPermissionManager::RequestPermission( |
| PermissionType permission, |
| content::RenderFrameHost* render_frame_host, |
| const GURL& requesting_origin, |
| bool user_gesture, |
| const base::Callback<void(PermissionStatus)>& callback) { |
| return RequestPermissions( |
| std::vector<PermissionType>(1, permission), render_frame_host, |
| requesting_origin, user_gesture, |
| base::Bind(&PermissionRequestResponseCallbackWrapper, callback)); |
| } |
| |
| int AwPermissionManager::RequestPermissions( |
| const std::vector<PermissionType>& permissions, |
| content::RenderFrameHost* render_frame_host, |
| const GURL& requesting_origin, |
| bool user_gesture, |
| const base::Callback<void(const std::vector<PermissionStatus>&)>& |
| callback) { |
| if (permissions.empty()) { |
| callback.Run(std::vector<PermissionStatus>()); |
| return content::PermissionController::kNoPendingOperation; |
| } |
| |
| const GURL& embedding_origin = LastCommittedOrigin(render_frame_host); |
| |
| auto pending_request = std::make_unique<PendingRequest>( |
| permissions, requesting_origin, embedding_origin, |
| GetRenderProcessID(render_frame_host), |
| GetRenderFrameID(render_frame_host), callback); |
| std::vector<bool> should_delegate_requests = |
| std::vector<bool>(permissions.size(), true); |
| for (size_t i = 0; i < permissions.size(); ++i) { |
| for (PendingRequestsMap::Iterator<PendingRequest> it(&pending_requests_); |
| !it.IsAtEnd(); it.Advance()) { |
| if (it.GetCurrentValue()->HasPermissionType(permissions[i]) && |
| it.GetCurrentValue()->requesting_origin == requesting_origin) { |
| if (it.GetCurrentValue()->IsCompleted(permissions[i])) { |
| pending_request->SetPermissionStatus( |
| permissions[i], |
| it.GetCurrentValue()->GetPermissionStatus(permissions[i])); |
| } |
| should_delegate_requests[i] = false; |
| break; |
| } |
| } |
| } |
| |
| // Keep copy of pointer for performing further operations after ownership is |
| // transferred to pending_requests_ |
| PendingRequest* pending_request_raw = pending_request.get(); |
| int request_id = pending_requests_.Add(std::move(pending_request)); |
| |
| AwBrowserPermissionRequestDelegate* delegate = |
| GetDelegate(pending_request_raw->render_process_id, |
| pending_request_raw->render_frame_id); |
| |
| for (size_t i = 0; i < permissions.size(); ++i) { |
| if (!should_delegate_requests[i]) |
| continue; |
| |
| if (!delegate) { |
| DVLOG(0) << "Dropping permissions request for " |
| << static_cast<int>(permissions[i]); |
| pending_request_raw->SetPermissionStatus(permissions[i], |
| PermissionStatus::DENIED); |
| continue; |
| } |
| |
| switch (permissions[i]) { |
| case PermissionType::GEOLOCATION: |
| delegate->RequestGeolocationPermission( |
| pending_request_raw->requesting_origin, |
| base::BindOnce(&OnRequestResponse, weak_ptr_factory_.GetWeakPtr(), |
| request_id, permissions[i])); |
| break; |
| case PermissionType::PROTECTED_MEDIA_IDENTIFIER: |
| delegate->RequestProtectedMediaIdentifierPermission( |
| pending_request_raw->requesting_origin, |
| base::BindOnce(&OnRequestResponse, weak_ptr_factory_.GetWeakPtr(), |
| request_id, permissions[i])); |
| break; |
| case PermissionType::MIDI_SYSEX: |
| delegate->RequestMIDISysexPermission( |
| pending_request_raw->requesting_origin, |
| base::BindOnce(&OnRequestResponse, weak_ptr_factory_.GetWeakPtr(), |
| request_id, permissions[i])); |
| break; |
| case PermissionType::AUDIO_CAPTURE: |
| case PermissionType::VIDEO_CAPTURE: |
| case PermissionType::NOTIFICATIONS: |
| case PermissionType::DURABLE_STORAGE: |
| case PermissionType::BACKGROUND_SYNC: |
| case PermissionType::FLASH: |
| case PermissionType::ACCESSIBILITY_EVENTS: |
| case PermissionType::CLIPBOARD_READ: |
| case PermissionType::CLIPBOARD_WRITE: |
| case PermissionType::PAYMENT_HANDLER: |
| case PermissionType::BACKGROUND_FETCH: |
| NOTIMPLEMENTED() << "RequestPermissions is not implemented for " |
| << static_cast<int>(permissions[i]); |
| pending_request_raw->SetPermissionStatus(permissions[i], |
| PermissionStatus::DENIED); |
| break; |
| case PermissionType::MIDI: |
| case PermissionType::SENSORS: |
| // PermissionType::SENSORS requests are always granted so that access |
| // to device motion and device orientation data (and underlying |
| // sensors) works in the WebView. SensorProviderImpl::GetSensor() |
| // filters requests for other types of sensors. |
| pending_request_raw->SetPermissionStatus(permissions[i], |
| PermissionStatus::GRANTED); |
| break; |
| case PermissionType::NUM: |
| NOTREACHED() << "PermissionType::NUM was not expected here."; |
| pending_request_raw->SetPermissionStatus(permissions[i], |
| PermissionStatus::DENIED); |
| break; |
| } |
| } |
| |
| // If delegate resolve the permission synchronously, all requests could be |
| // already resolved here. |
| if (!pending_requests_.Lookup(request_id)) |
| return content::PermissionController::kNoPendingOperation; |
| |
| // If requests are resolved without calling delegate functions, e.g. |
| // PermissionType::MIDI is permitted within the previous for-loop, all |
| // requests could be already resolved, but still in the |pending_requests_| |
| // without invoking the callback. |
| if (pending_request_raw->IsCompleted()) { |
| std::vector<PermissionStatus> results = pending_request_raw->results; |
| pending_requests_.Remove(request_id); |
| callback.Run(results); |
| return content::PermissionController::kNoPendingOperation; |
| } |
| |
| return request_id; |
| } |
| |
| // static |
| void AwPermissionManager::OnRequestResponse( |
| const base::WeakPtr<AwPermissionManager>& manager, |
| int request_id, |
| PermissionType permission, |
| bool allowed) { |
| // All delegate functions should be cancelled when the manager runs |
| // destructor. Therefore |manager| should be always valid here. |
| DCHECK(manager); |
| |
| PermissionStatus status = |
| allowed ? PermissionStatus::GRANTED : PermissionStatus::DENIED; |
| PendingRequest* pending_request = |
| manager->pending_requests_.Lookup(request_id); |
| |
| manager->result_cache_->SetResult(permission, |
| pending_request->requesting_origin, |
| pending_request->embedding_origin, status); |
| |
| std::vector<int> complete_request_ids; |
| std::vector< |
| std::pair<RequestPermissionsCallback, std::vector<PermissionStatus>>> |
| complete_request_pairs; |
| for (PendingRequestsMap::Iterator<PendingRequest> it( |
| &manager->pending_requests_); |
| !it.IsAtEnd(); it.Advance()) { |
| if (!it.GetCurrentValue()->HasPermissionType(permission) || |
| it.GetCurrentValue()->requesting_origin != |
| pending_request->requesting_origin) { |
| continue; |
| } |
| it.GetCurrentValue()->SetPermissionStatus(permission, status); |
| if (it.GetCurrentValue()->IsCompleted()) { |
| complete_request_ids.push_back(it.GetCurrentKey()); |
| if (!it.GetCurrentValue()->IsCancelled()) { |
| complete_request_pairs.emplace_back( |
| std::move(it.GetCurrentValue()->callback), |
| std::move(it.GetCurrentValue()->results)); |
| } |
| } |
| } |
| for (auto id : complete_request_ids) |
| manager->pending_requests_.Remove(id); |
| for (auto& pair : complete_request_pairs) |
| std::move(pair.first).Run(pair.second); |
| } |
| |
| void AwPermissionManager::ResetPermission(PermissionType permission, |
| const GURL& requesting_origin, |
| const GURL& embedding_origin) { |
| result_cache_->ClearResult(permission, requesting_origin, embedding_origin); |
| } |
| |
| PermissionStatus AwPermissionManager::GetPermissionStatus( |
| PermissionType permission, |
| const GURL& requesting_origin, |
| const GURL& embedding_origin) { |
| // Method is called outside the Permissions API only for this permission. |
| if (permission == PermissionType::PROTECTED_MEDIA_IDENTIFIER) { |
| return result_cache_->GetResult(permission, requesting_origin, |
| embedding_origin); |
| } else if (permission == PermissionType::MIDI || |
| permission == PermissionType::SENSORS) { |
| return PermissionStatus::GRANTED; |
| } |
| |
| return PermissionStatus::DENIED; |
| } |
| |
| PermissionStatus AwPermissionManager::GetPermissionStatusForFrame( |
| PermissionType permission, |
| content::RenderFrameHost* render_frame_host, |
| const GURL& requesting_origin) { |
| return GetPermissionStatus( |
| permission, requesting_origin, |
| content::WebContents::FromRenderFrameHost(render_frame_host) |
| ->GetLastCommittedURL() |
| .GetOrigin()); |
| } |
| |
| int AwPermissionManager::SubscribePermissionStatusChange( |
| PermissionType permission, |
| content::RenderFrameHost* render_frame_host, |
| const GURL& requesting_origin, |
| const base::Callback<void(PermissionStatus)>& callback) { |
| return content::PermissionController::kNoPendingOperation; |
| } |
| |
| void AwPermissionManager::UnsubscribePermissionStatusChange( |
| int subscription_id) {} |
| |
| void AwPermissionManager::CancelPermissionRequest(int request_id) { |
| PendingRequest* pending_request = pending_requests_.Lookup(request_id); |
| if (!pending_request || pending_request->IsCancelled()) |
| return; |
| pending_request->Cancel(); |
| |
| const GURL& embedding_origin = pending_request->embedding_origin; |
| const GURL& requesting_origin = pending_request->requesting_origin; |
| for (auto permission : pending_request->permissions) |
| result_cache_->ClearResult(permission, requesting_origin, embedding_origin); |
| |
| AwBrowserPermissionRequestDelegate* delegate = GetDelegate( |
| pending_request->render_process_id, pending_request->render_frame_id); |
| |
| for (auto permission : pending_request->permissions) { |
| // If the permission was already resolved, we do not need to cancel it. |
| if (pending_request->IsCompleted(permission)) |
| continue; |
| |
| // If another pending_request waits for the same permission being resolved, |
| // we should not cancel the delegate's request. |
| bool should_not_cancel_ = false; |
| for (PendingRequestsMap::Iterator<PendingRequest> it(&pending_requests_); |
| !it.IsAtEnd(); it.Advance()) { |
| if (it.GetCurrentValue() != pending_request && |
| it.GetCurrentValue()->HasPermissionType(permission) && |
| it.GetCurrentValue()->requesting_origin == requesting_origin && |
| !it.GetCurrentValue()->IsCompleted(permission)) { |
| should_not_cancel_ = true; |
| break; |
| } |
| } |
| if (should_not_cancel_) |
| continue; |
| |
| switch (permission) { |
| case PermissionType::GEOLOCATION: |
| if (delegate) |
| delegate->CancelGeolocationPermissionRequests(requesting_origin); |
| break; |
| case PermissionType::PROTECTED_MEDIA_IDENTIFIER: |
| if (delegate) |
| delegate->CancelProtectedMediaIdentifierPermissionRequests( |
| requesting_origin); |
| break; |
| case PermissionType::MIDI_SYSEX: |
| if (delegate) |
| delegate->CancelMIDISysexPermissionRequests(requesting_origin); |
| break; |
| case PermissionType::NOTIFICATIONS: |
| case PermissionType::DURABLE_STORAGE: |
| case PermissionType::AUDIO_CAPTURE: |
| case PermissionType::VIDEO_CAPTURE: |
| case PermissionType::BACKGROUND_SYNC: |
| case PermissionType::FLASH: |
| case PermissionType::ACCESSIBILITY_EVENTS: |
| case PermissionType::CLIPBOARD_READ: |
| case PermissionType::CLIPBOARD_WRITE: |
| case PermissionType::PAYMENT_HANDLER: |
| case PermissionType::BACKGROUND_FETCH: |
| NOTIMPLEMENTED() << "CancelPermission not implemented for " |
| << static_cast<int>(permission); |
| break; |
| case PermissionType::MIDI: |
| case PermissionType::SENSORS: |
| // There is nothing to cancel so this is simply ignored. |
| break; |
| case PermissionType::NUM: |
| NOTREACHED() << "PermissionType::NUM was not expected here."; |
| break; |
| } |
| pending_request->SetPermissionStatus(permission, PermissionStatus::DENIED); |
| } |
| |
| // If there are still active requests, we should not remove request_id here, |
| // but just do not invoke a relevant callback when the request is resolved in |
| // OnRequestResponse(). |
| if (pending_request->IsCompleted()) |
| pending_requests_.Remove(request_id); |
| } |
| |
| void AwPermissionManager::CancelPermissionRequests() { |
| std::vector<int> request_ids; |
| for (PendingRequestsMap::Iterator<PendingRequest> it(&pending_requests_); |
| !it.IsAtEnd(); it.Advance()) { |
| request_ids.push_back(it.GetCurrentKey()); |
| } |
| for (auto request_id : request_ids) |
| CancelPermissionRequest(request_id); |
| DCHECK(pending_requests_.IsEmpty()); |
| } |
| |
| int AwPermissionManager::GetRenderProcessID( |
| content::RenderFrameHost* render_frame_host) { |
| return render_frame_host->GetProcess()->GetID(); |
| } |
| |
| int AwPermissionManager::GetRenderFrameID( |
| content::RenderFrameHost* render_frame_host) { |
| return render_frame_host->GetRoutingID(); |
| } |
| |
| GURL AwPermissionManager::LastCommittedOrigin( |
| content::RenderFrameHost* render_frame_host) { |
| return content::WebContents::FromRenderFrameHost(render_frame_host) |
| ->GetLastCommittedURL().GetOrigin(); |
| } |
| |
| AwBrowserPermissionRequestDelegate* AwPermissionManager::GetDelegate( |
| int render_process_id, int render_frame_id) { |
| return AwBrowserPermissionRequestDelegate::FromID(render_process_id, |
| render_frame_id); |
| } |
| |
| } // namespace android_webview |