Home | History | Annotate | Download | only in identity
      1 // Copyright (c) 2012 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 "chrome/browser/extensions/api/identity/web_auth_flow.h"
      6 
      7 #include "apps/shell_window.h"
      8 #include "base/base64.h"
      9 #include "base/location.h"
     10 #include "base/message_loop/message_loop.h"
     11 #include "base/strings/string_util.h"
     12 #include "base/strings/utf_string_conversions.h"
     13 #include "chrome/browser/extensions/component_loader.h"
     14 #include "chrome/browser/extensions/extension_service.h"
     15 #include "chrome/browser/extensions/extension_system.h"
     16 #include "chrome/browser/extensions/extension_system.h"
     17 #include "chrome/browser/profiles/profile.h"
     18 #include "chrome/common/extensions/api/identity_private.h"
     19 #include "chrome/common/extensions/extension_constants.h"
     20 #include "content/public/browser/navigation_details.h"
     21 #include "content/public/browser/navigation_entry.h"
     22 #include "content/public/browser/notification_details.h"
     23 #include "content/public/browser/notification_service.h"
     24 #include "content/public/browser/notification_source.h"
     25 #include "content/public/browser/notification_types.h"
     26 #include "content/public/browser/render_view_host.h"
     27 #include "content/public/browser/resource_request_details.h"
     28 #include "content/public/browser/web_contents.h"
     29 #include "crypto/random.h"
     30 #include "extensions/browser/event_router.h"
     31 #include "grit/browser_resources.h"
     32 #include "url/gurl.h"
     33 
     34 using apps::ShellWindow;
     35 using content::RenderViewHost;
     36 using content::ResourceRedirectDetails;
     37 using content::WebContents;
     38 using content::WebContentsObserver;
     39 
     40 namespace extensions {
     41 
     42 namespace identity_private = api::identity_private;
     43 
     44 WebAuthFlow::WebAuthFlow(
     45     Delegate* delegate,
     46     Profile* profile,
     47     const GURL& provider_url,
     48     Mode mode)
     49     : delegate_(delegate),
     50       profile_(profile),
     51       provider_url_(provider_url),
     52       mode_(mode),
     53       embedded_window_created_(false) {
     54 }
     55 
     56 WebAuthFlow::~WebAuthFlow() {
     57   DCHECK(delegate_ == NULL);
     58 
     59   // Stop listening to notifications first since some of the code
     60   // below may generate notifications.
     61   registrar_.RemoveAll();
     62   WebContentsObserver::Observe(NULL);
     63 
     64   if (!shell_window_key_.empty()) {
     65     apps::ShellWindowRegistry::Get(profile_)->RemoveObserver(this);
     66 
     67     if (shell_window_ && shell_window_->web_contents())
     68       shell_window_->web_contents()->Close();
     69   }
     70 }
     71 
     72 void WebAuthFlow::Start() {
     73   apps::ShellWindowRegistry::Get(profile_)->AddObserver(this);
     74 
     75   // Attach a random ID string to the window so we can recoginize it
     76   // in OnShellWindowAdded.
     77   std::string random_bytes;
     78   crypto::RandBytes(WriteInto(&random_bytes, 33), 32);
     79   base::Base64Encode(random_bytes, &shell_window_key_);
     80 
     81   // identityPrivate.onWebFlowRequest(shell_window_key, provider_url_, mode_)
     82   scoped_ptr<base::ListValue> args(new base::ListValue());
     83   args->AppendString(shell_window_key_);
     84   args->AppendString(provider_url_.spec());
     85   if (mode_ == WebAuthFlow::INTERACTIVE)
     86     args->AppendString("interactive");
     87   else
     88     args->AppendString("silent");
     89 
     90   scoped_ptr<Event> event(
     91       new Event(identity_private::OnWebFlowRequest::kEventName, args.Pass()));
     92   event->restrict_to_browser_context = profile_;
     93   ExtensionSystem* system = ExtensionSystem::Get(profile_);
     94 
     95   extensions::ComponentLoader* component_loader =
     96       system->extension_service()->component_loader();
     97   if (!component_loader->Exists(extension_misc::kIdentityApiUiAppId)) {
     98     component_loader->Add(
     99         IDR_IDENTITY_API_SCOPE_APPROVAL_MANIFEST,
    100         base::FilePath(FILE_PATH_LITERAL("identity_scope_approval_dialog")));
    101   }
    102 
    103   system->event_router()->DispatchEventWithLazyListener(
    104       extension_misc::kIdentityApiUiAppId, event.Pass());
    105 }
    106 
    107 void WebAuthFlow::DetachDelegateAndDelete() {
    108   delegate_ = NULL;
    109   base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
    110 }
    111 
    112 void WebAuthFlow::OnShellWindowAdded(ShellWindow* shell_window) {
    113   if (shell_window->window_key() == shell_window_key_ &&
    114       shell_window->extension()->id() == extension_misc::kIdentityApiUiAppId) {
    115     shell_window_ = shell_window;
    116     WebContentsObserver::Observe(shell_window->web_contents());
    117 
    118     registrar_.Add(
    119         this,
    120         content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED,
    121         content::NotificationService::AllBrowserContextsAndSources());
    122   }
    123 }
    124 
    125 void WebAuthFlow::OnShellWindowIconChanged(ShellWindow* shell_window) {}
    126 
    127 void WebAuthFlow::OnShellWindowRemoved(ShellWindow* shell_window) {
    128   if (shell_window->window_key() == shell_window_key_ &&
    129       shell_window->extension()->id() == extension_misc::kIdentityApiUiAppId) {
    130     shell_window_ = NULL;
    131     registrar_.RemoveAll();
    132 
    133     if (delegate_)
    134       delegate_->OnAuthFlowFailure(WebAuthFlow::WINDOW_CLOSED);
    135   }
    136 }
    137 
    138 void WebAuthFlow::BeforeUrlLoaded(const GURL& url) {
    139   if (delegate_ && embedded_window_created_)
    140     delegate_->OnAuthFlowURLChange(url);
    141 }
    142 
    143 void WebAuthFlow::AfterUrlLoaded() {
    144   if (delegate_ && embedded_window_created_ && mode_ == WebAuthFlow::SILENT)
    145     delegate_->OnAuthFlowFailure(WebAuthFlow::INTERACTION_REQUIRED);
    146 }
    147 
    148 void WebAuthFlow::Observe(int type,
    149                           const content::NotificationSource& source,
    150                           const content::NotificationDetails& details) {
    151   DCHECK(shell_window_);
    152 
    153   if (!delegate_)
    154     return;
    155 
    156   if (!embedded_window_created_) {
    157     DCHECK(type == content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED);
    158 
    159     RenderViewHost* render_view(
    160         content::Details<RenderViewHost>(details).ptr());
    161     WebContents* web_contents = WebContents::FromRenderViewHost(render_view);
    162 
    163     if (web_contents &&
    164         (web_contents->GetEmbedderWebContents() ==
    165          WebContentsObserver::web_contents())) {
    166       // Switch from watching the shell window to the guest inside it.
    167       embedded_window_created_ = true;
    168       WebContentsObserver::Observe(web_contents);
    169 
    170       registrar_.RemoveAll();
    171       registrar_.Add(this,
    172                      content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT,
    173                      content::Source<WebContents>(web_contents));
    174       registrar_.Add(this,
    175                      content::NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED,
    176                      content::Source<WebContents>(web_contents));
    177     }
    178   } else {
    179     // embedded_window_created_
    180     switch (type) {
    181       case content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT: {
    182         ResourceRedirectDetails* redirect_details =
    183             content::Details<ResourceRedirectDetails>(details).ptr();
    184         if (redirect_details != NULL)
    185           BeforeUrlLoaded(redirect_details->new_url);
    186         break;
    187       }
    188       case content::NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED: {
    189         std::pair<content::NavigationEntry*, bool>* title =
    190             content::Details<std::pair<content::NavigationEntry*, bool> >(
    191                 details).ptr();
    192 
    193         if (title->first) {
    194           delegate_->OnAuthFlowTitleChange(
    195               UTF16ToUTF8(title->first->GetTitle()));
    196         }
    197         break;
    198       }
    199       default:
    200         NOTREACHED()
    201             << "Got a notification that we did not register for: " << type;
    202         break;
    203     }
    204   }
    205 }
    206 
    207 void WebAuthFlow::RenderProcessGone(base::TerminationStatus status) {
    208   if (delegate_)
    209     delegate_->OnAuthFlowFailure(WebAuthFlow::WINDOW_CLOSED);
    210 }
    211 
    212 void WebAuthFlow::DidStartProvisionalLoadForFrame(
    213     int64 frame_id,
    214     int64 parent_frame_id,
    215     bool is_main_frame,
    216     const GURL& validated_url,
    217     bool is_error_page,
    218     bool is_iframe_srcdoc,
    219     RenderViewHost* render_view_host) {
    220   if (is_main_frame)
    221     BeforeUrlLoaded(validated_url);
    222 }
    223 
    224 void WebAuthFlow::DidFailProvisionalLoad(
    225     int64 frame_id,
    226     const base::string16& frame_unique_name,
    227     bool is_main_frame,
    228     const GURL& validated_url,
    229     int error_code,
    230     const base::string16& error_description,
    231     RenderViewHost* render_view_host) {
    232   if (delegate_)
    233     delegate_->OnAuthFlowFailure(LOAD_FAILED);
    234 }
    235 
    236 void WebAuthFlow::DidStopLoading(RenderViewHost* render_view_host) {
    237   AfterUrlLoaded();
    238 }
    239 
    240 void WebAuthFlow::DidNavigateMainFrame(
    241     const content::LoadCommittedDetails& details,
    242     const content::FrameNavigateParams& params) {
    243   if (delegate_ && details.http_status_code >= 400)
    244     delegate_->OnAuthFlowFailure(LOAD_FAILED);
    245 }
    246 
    247 }  // namespace extensions
    248