web_auth_flow.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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 "base/bind.h"
8#include "base/command_line.h"
9#include "base/location.h"
10#include "base/message_loop.h"
11#include "base/string_util.h"
12#include "base/stringprintf.h"
13#include "chrome/browser/profiles/profile.h"
14#include "chrome/browser/ui/browser.h"
15#include "chrome/browser/ui/browser_navigator.h"
16#include "chrome/common/chrome_switches.h"
17#include "content/public/browser/load_notification_details.h"
18#include "content/public/browser/navigation_controller.h"
19#include "content/public/browser/notification_details.h"
20#include "content/public/browser/notification_source.h"
21#include "content/public/browser/notification_types.h"
22#include "content/public/browser/render_view_host.h"
23#include "content/public/browser/resource_request_details.h"
24#include "content/public/browser/web_contents.h"
25#include "content/public/common/page_transition_types.h"
26#include "googleurl/src/gurl.h"
27#include "ipc/ipc_message.h"
28#include "ui/base/window_open_disposition.h"
29
30using content::LoadNotificationDetails;
31using content::NavigationController;
32using content::RenderViewHost;
33using content::ResourceRedirectDetails;
34using content::WebContents;
35using content::WebContentsObserver;
36
37namespace {
38
39static const char kChromeExtensionSchemeUrlPattern[] =
40    "chrome-extension://%s/";
41static const char kChromiumDomainRedirectUrlPattern[] =
42    "https://%s.chromiumapp.org/";
43
44}  // namespace
45
46namespace extensions {
47
48WebAuthFlow::WebAuthFlow(
49    Delegate* delegate,
50    Profile* profile,
51    const std::string& extension_id,
52    const GURL& provider_url,
53    Mode mode,
54    const gfx::Rect& initial_bounds,
55    chrome::HostDesktopType host_desktop_type)
56    : delegate_(delegate),
57      profile_(profile),
58      provider_url_(provider_url),
59      mode_(mode),
60      initial_bounds_(initial_bounds),
61      host_desktop_type_(host_desktop_type),
62      popup_shown_(false),
63      contents_(NULL) {
64  InitValidRedirectUrlPrefixes(extension_id);
65}
66
67WebAuthFlow::~WebAuthFlow() {
68  // Set the delegate to NULL to avoid reporting results twice.
69  delegate_ = NULL;
70
71  // Stop listening to notifications first since some of the code
72  // below may generate notifications.
73  registrar_.RemoveAll();
74
75  if (contents_) {
76    if (popup_shown_) {
77      contents_->Close();
78    } else {
79      contents_->Stop();
80      // Tell message loop to delete contents_ instead of deleting it
81      // directly since destructor can run in response to a callback from
82      // contents_.
83      MessageLoop::current()->DeleteSoon(FROM_HERE, contents_);
84    }
85  }
86}
87
88void WebAuthFlow::Start() {
89  contents_ = CreateWebContents();
90  WebContentsObserver::Observe(contents_);
91
92  NavigationController* controller = &(contents_->GetController());
93
94  // Register for appropriate notifications to intercept navigation to the
95  // redirect URLs.
96  registrar_.Add(
97      this,
98      content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT,
99      content::Source<WebContents>(contents_));
100
101  controller->LoadURL(
102      provider_url_,
103      content::Referrer(),
104      content::PAGE_TRANSITION_AUTO_TOPLEVEL,
105      std::string());
106}
107
108WebContents* WebAuthFlow::CreateWebContents() {
109  return WebContents::Create(WebContents::CreateParams(profile_));
110}
111
112void WebAuthFlow::ShowAuthFlowPopup() {
113  Browser::CreateParams browser_params(Browser::TYPE_POPUP, profile_,
114                                       host_desktop_type_);
115  browser_params.initial_bounds = initial_bounds_;
116  Browser* browser = new Browser(browser_params);
117  chrome::NavigateParams params(browser, contents_);
118  params.disposition = CURRENT_TAB;
119  params.window_action = chrome::NavigateParams::SHOW_WINDOW;
120  chrome::Navigate(&params);
121  // Observe method and WebContentsObserver::* methods will be called
122  // for varous navigation events. That is where we check for redirect
123  // to the right URL.
124  popup_shown_ = true;
125}
126
127bool WebAuthFlow::BeforeUrlLoaded(const GURL& url) {
128  if (IsValidRedirectUrl(url)) {
129    ReportResult(url);
130    return true;
131  }
132  return false;
133}
134
135void WebAuthFlow::AfterUrlLoaded() {
136  // Do nothing if a popup is already created.
137  if (popup_shown_)
138    return;
139
140  // Report results directly if not in interactive mode.
141  if (mode_ != WebAuthFlow::INTERACTIVE) {
142    ReportResult(GURL());
143    return;
144  }
145
146  // We are in interactive mode and window is not shown yet; show the window.
147  ShowAuthFlowPopup();
148}
149
150void WebAuthFlow::Observe(int type,
151                          const content::NotificationSource& source,
152                          const content::NotificationDetails& details) {
153  switch (type) {
154    case content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT: {
155      ResourceRedirectDetails* redirect_details =
156          content::Details<ResourceRedirectDetails>(details).ptr();
157      if (redirect_details != NULL)
158        BeforeUrlLoaded(redirect_details->new_url);
159    }
160    break;
161    default:
162      NOTREACHED() << "Got a notification that we did not register for: "
163                   << type;
164      break;
165  }
166}
167
168void WebAuthFlow::ProvisionalChangeToMainFrameUrl(
169    const GURL& url,
170    RenderViewHost* render_view_host) {
171  BeforeUrlLoaded(url);
172}
173
174void WebAuthFlow::DidStopLoading(RenderViewHost* render_view_host) {
175  AfterUrlLoaded();
176}
177
178void WebAuthFlow::WebContentsDestroyed(WebContents* web_contents) {
179  contents_ = NULL;
180  ReportResult(GURL());
181}
182
183void WebAuthFlow::ReportResult(const GURL& url) {
184  if (!delegate_)
185    return;
186
187  if (url.is_empty()) {
188    delegate_->OnAuthFlowFailure();
189  } else {
190    // TODO(munjal): Consider adding code to parse out access token
191    // from some common places (e.g. URL fragment) so the apps don't
192    // have to do that work.
193    delegate_->OnAuthFlowSuccess(url.spec());
194  }
195
196  // IMPORTANT: Do not access any members after calling the delegate
197  // since the delegate can destroy |this| in the callback and hence
198  // all data members are invalid after that.
199}
200
201bool WebAuthFlow::IsValidRedirectUrl(const GURL& url) const {
202  std::vector<std::string>::const_iterator iter;
203  for (iter = valid_prefixes_.begin(); iter != valid_prefixes_.end(); ++iter) {
204    if (StartsWithASCII(url.spec(), *iter, false)) {
205      return true;
206    }
207  }
208  return false;
209}
210
211void WebAuthFlow::InitValidRedirectUrlPrefixes(
212    const std::string& extension_id) {
213  valid_prefixes_.push_back(base::StringPrintf(
214      kChromeExtensionSchemeUrlPattern, extension_id.c_str()));
215  valid_prefixes_.push_back(base::StringPrintf(
216      kChromiumDomainRedirectUrlPattern, extension_id.c_str()));
217}
218
219}  // namespace extensions
220