|  | // Copyright (c) 2012 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 "chrome/browser/extensions/api/identity/web_auth_flow.h" | 
|  |  | 
|  | #include <memory> | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/base64.h" | 
|  | #include "base/location.h" | 
|  | #include "base/single_thread_task_runner.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "base/strings/utf_string_conversions.h" | 
|  | #include "base/threading/thread_task_runner_handle.h" | 
|  | #include "base/trace_event/trace_event.h" | 
|  | #include "chrome/browser/extensions/component_loader.h" | 
|  | #include "chrome/browser/extensions/extension_service.h" | 
|  | #include "chrome/browser/profiles/profile.h" | 
|  | #include "chrome/common/extensions/api/identity_private.h" | 
|  | #include "chrome/common/extensions/extension_constants.h" | 
|  | #include "chrome/grit/browser_resources.h" | 
|  | #include "components/guest_view/browser/guest_view_base.h" | 
|  | #include "content/public/browser/navigation_details.h" | 
|  | #include "content/public/browser/navigation_entry.h" | 
|  | #include "content/public/browser/navigation_handle.h" | 
|  | #include "content/public/browser/notification_details.h" | 
|  | #include "content/public/browser/notification_service.h" | 
|  | #include "content/public/browser/notification_source.h" | 
|  | #include "content/public/browser/notification_types.h" | 
|  | #include "content/public/browser/render_frame_host.h" | 
|  | #include "content/public/browser/web_contents.h" | 
|  | #include "crypto/random.h" | 
|  | #include "extensions/browser/app_window/app_window.h" | 
|  | #include "extensions/browser/event_router.h" | 
|  | #include "extensions/browser/extension_system.h" | 
|  | #include "net/http/http_response_headers.h" | 
|  | #include "url/gurl.h" | 
|  | #include "url/url_constants.h" | 
|  |  | 
|  | using content::RenderViewHost; | 
|  | using content::WebContents; | 
|  | using content::WebContentsObserver; | 
|  | using guest_view::GuestViewBase; | 
|  |  | 
|  | namespace extensions { | 
|  |  | 
|  | namespace identity_private = api::identity_private; | 
|  |  | 
|  | WebAuthFlow::WebAuthFlow( | 
|  | Delegate* delegate, | 
|  | Profile* profile, | 
|  | const GURL& provider_url, | 
|  | Mode mode) | 
|  | : delegate_(delegate), | 
|  | profile_(profile), | 
|  | provider_url_(provider_url), | 
|  | mode_(mode), | 
|  | embedded_window_created_(false) { | 
|  | TRACE_EVENT_ASYNC_BEGIN0("identity", "WebAuthFlow", this); | 
|  | } | 
|  |  | 
|  | WebAuthFlow::~WebAuthFlow() { | 
|  | DCHECK(delegate_ == NULL); | 
|  |  | 
|  | // Stop listening to notifications first since some of the code | 
|  | // below may generate notifications. | 
|  | registrar_.RemoveAll(); | 
|  | WebContentsObserver::Observe(nullptr); | 
|  |  | 
|  | if (!app_window_key_.empty()) { | 
|  | AppWindowRegistry::Get(profile_)->RemoveObserver(this); | 
|  |  | 
|  | if (app_window_ && app_window_->web_contents()) | 
|  | app_window_->web_contents()->Close(); | 
|  | } | 
|  | TRACE_EVENT_ASYNC_END0("identity", "WebAuthFlow", this); | 
|  | } | 
|  |  | 
|  | void WebAuthFlow::Start() { | 
|  | AppWindowRegistry::Get(profile_)->AddObserver(this); | 
|  |  | 
|  | // Attach a random ID string to the window so we can recognize it | 
|  | // in OnAppWindowAdded. | 
|  | std::string random_bytes; | 
|  | crypto::RandBytes(base::WriteInto(&random_bytes, 33), 32); | 
|  | base::Base64Encode(random_bytes, &app_window_key_); | 
|  |  | 
|  | // identityPrivate.onWebFlowRequest(app_window_key, provider_url_, mode_) | 
|  | std::unique_ptr<base::ListValue> args(new base::ListValue()); | 
|  | args->AppendString(app_window_key_); | 
|  | args->AppendString(provider_url_.spec()); | 
|  | if (mode_ == WebAuthFlow::INTERACTIVE) | 
|  | args->AppendString("interactive"); | 
|  | else | 
|  | args->AppendString("silent"); | 
|  |  | 
|  | auto event = | 
|  | std::make_unique<Event>(events::IDENTITY_PRIVATE_ON_WEB_FLOW_REQUEST, | 
|  | identity_private::OnWebFlowRequest::kEventName, | 
|  | std::move(args), profile_); | 
|  | ExtensionSystem* system = ExtensionSystem::Get(profile_); | 
|  |  | 
|  | extensions::ComponentLoader* component_loader = | 
|  | system->extension_service()->component_loader(); | 
|  | if (!component_loader->Exists(extension_misc::kIdentityApiUiAppId)) { | 
|  | component_loader->Add( | 
|  | IDR_IDENTITY_API_SCOPE_APPROVAL_MANIFEST, | 
|  | base::FilePath(FILE_PATH_LITERAL("identity_scope_approval_dialog"))); | 
|  | } | 
|  |  | 
|  | EventRouter::Get(profile_)->DispatchEventWithLazyListener( | 
|  | extension_misc::kIdentityApiUiAppId, std::move(event)); | 
|  | } | 
|  |  | 
|  | void WebAuthFlow::DetachDelegateAndDelete() { | 
|  | delegate_ = NULL; | 
|  | base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this); | 
|  | } | 
|  |  | 
|  | void WebAuthFlow::OnAppWindowAdded(AppWindow* app_window) { | 
|  | if (app_window->window_key() == app_window_key_ && | 
|  | app_window->extension_id() == extension_misc::kIdentityApiUiAppId) { | 
|  | app_window_ = app_window; | 
|  | WebContentsObserver::Observe(app_window->web_contents()); | 
|  |  | 
|  | registrar_.Add( | 
|  | this, | 
|  | content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED, | 
|  | content::NotificationService::AllBrowserContextsAndSources()); | 
|  | } | 
|  | } | 
|  |  | 
|  | void WebAuthFlow::OnAppWindowRemoved(AppWindow* app_window) { | 
|  | if (app_window->window_key() == app_window_key_ && | 
|  | app_window->extension_id() == extension_misc::kIdentityApiUiAppId) { | 
|  | app_window_ = NULL; | 
|  | registrar_.RemoveAll(); | 
|  | WebContentsObserver::Observe(nullptr); | 
|  |  | 
|  | if (delegate_) | 
|  | delegate_->OnAuthFlowFailure(WebAuthFlow::WINDOW_CLOSED); | 
|  | } | 
|  | } | 
|  |  | 
|  | void WebAuthFlow::BeforeUrlLoaded(const GURL& url) { | 
|  | if (delegate_ && embedded_window_created_) | 
|  | delegate_->OnAuthFlowURLChange(url); | 
|  | } | 
|  |  | 
|  | void WebAuthFlow::AfterUrlLoaded() { | 
|  | if (delegate_ && embedded_window_created_ && mode_ == WebAuthFlow::SILENT) | 
|  | delegate_->OnAuthFlowFailure(WebAuthFlow::INTERACTION_REQUIRED); | 
|  | } | 
|  |  | 
|  | void WebAuthFlow::Observe(int type, | 
|  | const content::NotificationSource& source, | 
|  | const content::NotificationDetails& details) { | 
|  | DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED, type); | 
|  | DCHECK(app_window_); | 
|  |  | 
|  | if (!delegate_ || embedded_window_created_) | 
|  | return; | 
|  |  | 
|  | RenderViewHost* render_view(content::Details<RenderViewHost>(details).ptr()); | 
|  | WebContents* web_contents = WebContents::FromRenderViewHost(render_view); | 
|  | GuestViewBase* guest = GuestViewBase::FromWebContents(web_contents); | 
|  | WebContents* owner = guest ? guest->owner_web_contents() : nullptr; | 
|  | if (!web_contents || owner != WebContentsObserver::web_contents()) | 
|  | return; | 
|  |  | 
|  | // Switch from watching the app window to the guest inside it. | 
|  | embedded_window_created_ = true; | 
|  | WebContentsObserver::Observe(web_contents); | 
|  |  | 
|  | registrar_.RemoveAll(); | 
|  | } | 
|  |  | 
|  | void WebAuthFlow::RenderProcessGone(base::TerminationStatus status) { | 
|  | if (delegate_) | 
|  | delegate_->OnAuthFlowFailure(WebAuthFlow::WINDOW_CLOSED); | 
|  | } | 
|  |  | 
|  | void WebAuthFlow::TitleWasSet(content::NavigationEntry* entry) { | 
|  | if (delegate_) | 
|  | delegate_->OnAuthFlowTitleChange(base::UTF16ToUTF8(entry->GetTitle())); | 
|  | } | 
|  |  | 
|  | void WebAuthFlow::DidStopLoading() { | 
|  | AfterUrlLoaded(); | 
|  | } | 
|  |  | 
|  | void WebAuthFlow::DidStartNavigation( | 
|  | content::NavigationHandle* navigation_handle) { | 
|  | if (navigation_handle->IsInMainFrame()) | 
|  | BeforeUrlLoaded(navigation_handle->GetURL()); | 
|  | } | 
|  |  | 
|  | void WebAuthFlow::DidRedirectNavigation( | 
|  | content::NavigationHandle* navigation_handle) { | 
|  | BeforeUrlLoaded(navigation_handle->GetURL()); | 
|  | } | 
|  |  | 
|  | void WebAuthFlow::DidFinishNavigation( | 
|  | content::NavigationHandle* navigation_handle) { | 
|  | bool failed = false; | 
|  |  | 
|  | if (navigation_handle->GetNetErrorCode() != net::OK) { | 
|  | if (navigation_handle->GetURL().spec() == url::kAboutBlankURL) { | 
|  | // As part of the OAUth 2.0 protocol with GAIA, at the end of the web | 
|  | // authorization flow, GAIA redirects to a custom scheme URL of type | 
|  | // |com.googleusercontent.apps.123:/<extension_id>|, where | 
|  | // |com.googleusercontent.apps.123| is the reverse DNS notation of the | 
|  | // client ID of the extension that started the web sign-in flow. (The | 
|  | // intent of this weird URL scheme was to make sure it couldn't be loaded | 
|  | // anywhere at all as this makes it much harder to pull off a cross-site | 
|  | // attack that could leak the returned oauth token to a malicious script | 
|  | // or site.) | 
|  | // | 
|  | // This URL is not an accessible URL from within a Guest WebView, so | 
|  | // during its load of this URL, Chrome changes it to |about:blank| and | 
|  | // then the Identity Scope Approval Dialog extension fails to load it. | 
|  | // Failing to load |about:blank| must not be treated as a failure of | 
|  | // the web auth flow. | 
|  | DCHECK_EQ(net::ERR_UNKNOWN_URL_SCHEME, | 
|  | navigation_handle->GetNetErrorCode()); | 
|  | } else { | 
|  | failed = true; | 
|  | TRACE_EVENT_ASYNC_STEP_PAST1("identity", "WebAuthFlow", this, | 
|  | "DidFinishNavigationFailure", "error_code", | 
|  | navigation_handle->GetNetErrorCode()); | 
|  | } | 
|  | } else if (navigation_handle->IsInMainFrame() && | 
|  | navigation_handle->GetResponseHeaders() && | 
|  | navigation_handle->GetResponseHeaders()->response_code() >= 400) { | 
|  | failed = true; | 
|  | TRACE_EVENT_ASYNC_STEP_PAST1( | 
|  | "identity", "WebAuthFlow", this, "DidFinishNavigationFailure", | 
|  | "response_code", | 
|  | navigation_handle->GetResponseHeaders()->response_code()); | 
|  | } | 
|  |  | 
|  | if (failed && delegate_) | 
|  | delegate_->OnAuthFlowFailure(LOAD_FAILED); | 
|  | } | 
|  |  | 
|  | }  // namespace extensions |