text: // 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
  