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/base64.h"
8#include "base/debug/trace_event.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/profiles/profile.h"
16#include "chrome/common/extensions/api/identity_private.h"
17#include "chrome/common/extensions/extension_constants.h"
18#include "content/public/browser/navigation_details.h"
19#include "content/public/browser/navigation_entry.h"
20#include "content/public/browser/notification_details.h"
21#include "content/public/browser/notification_service.h"
22#include "content/public/browser/notification_source.h"
23#include "content/public/browser/notification_types.h"
24#include "content/public/browser/render_frame_host.h"
25#include "content/public/browser/resource_request_details.h"
26#include "content/public/browser/web_contents.h"
27#include "crypto/random.h"
28#include "extensions/browser/app_window/app_window.h"
29#include "extensions/browser/event_router.h"
30#include "extensions/browser/extension_system.h"
31#include "extensions/browser/guest_view/guest_view_base.h"
32#include "grit/browser_resources.h"
33#include "url/gurl.h"
34
35using content::RenderViewHost;
36using content::ResourceRedirectDetails;
37using content::WebContents;
38using content::WebContentsObserver;
39
40namespace extensions {
41
42namespace identity_private = api::identity_private;
43
44WebAuthFlow::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
56WebAuthFlow::~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 (!app_window_key_.empty()) {
65    AppWindowRegistry::Get(profile_)->RemoveObserver(this);
66
67    if (app_window_ && app_window_->web_contents())
68      app_window_->web_contents()->Close();
69  }
70}
71
72void WebAuthFlow::Start() {
73  AppWindowRegistry::Get(profile_)->AddObserver(this);
74
75  // Attach a random ID string to the window so we can recoginize it
76  // in OnAppWindowAdded.
77  std::string random_bytes;
78  crypto::RandBytes(WriteInto(&random_bytes, 33), 32);
79  base::Base64Encode(random_bytes, &app_window_key_);
80
81  // identityPrivate.onWebFlowRequest(app_window_key, provider_url_, mode_)
82  scoped_ptr<base::ListValue> args(new base::ListValue());
83  args->AppendString(app_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
107void WebAuthFlow::DetachDelegateAndDelete() {
108  delegate_ = NULL;
109  base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
110}
111
112void WebAuthFlow::OnAppWindowAdded(AppWindow* app_window) {
113  if (app_window->window_key() == app_window_key_ &&
114      app_window->extension_id() == extension_misc::kIdentityApiUiAppId) {
115    app_window_ = app_window;
116    WebContentsObserver::Observe(app_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
125void WebAuthFlow::OnAppWindowRemoved(AppWindow* app_window) {
126  if (app_window->window_key() == app_window_key_ &&
127      app_window->extension_id() == extension_misc::kIdentityApiUiAppId) {
128    app_window_ = NULL;
129    registrar_.RemoveAll();
130
131    if (delegate_)
132      delegate_->OnAuthFlowFailure(WebAuthFlow::WINDOW_CLOSED);
133  }
134}
135
136void WebAuthFlow::BeforeUrlLoaded(const GURL& url) {
137  if (delegate_ && embedded_window_created_)
138    delegate_->OnAuthFlowURLChange(url);
139}
140
141void WebAuthFlow::AfterUrlLoaded() {
142  if (delegate_ && embedded_window_created_ && mode_ == WebAuthFlow::SILENT)
143    delegate_->OnAuthFlowFailure(WebAuthFlow::INTERACTION_REQUIRED);
144}
145
146void WebAuthFlow::Observe(int type,
147                          const content::NotificationSource& source,
148                          const content::NotificationDetails& details) {
149  DCHECK(app_window_);
150
151  if (!delegate_)
152    return;
153
154  if (!embedded_window_created_) {
155    DCHECK(type == content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED);
156
157    RenderViewHost* render_view(
158        content::Details<RenderViewHost>(details).ptr());
159    WebContents* web_contents = WebContents::FromRenderViewHost(render_view);
160    GuestViewBase* guest = GuestViewBase::FromWebContents(web_contents);
161    WebContents* embedder = guest ? guest->embedder_web_contents() : NULL;
162    if (web_contents &&
163        (embedder == WebContentsObserver::web_contents())) {
164      // Switch from watching the app window to the guest inside it.
165      embedded_window_created_ = true;
166      WebContentsObserver::Observe(web_contents);
167
168      registrar_.RemoveAll();
169      registrar_.Add(this,
170                     content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT,
171                     content::Source<WebContents>(web_contents));
172      registrar_.Add(this,
173                     content::NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED,
174                     content::Source<WebContents>(web_contents));
175    }
176  } else {
177    // embedded_window_created_
178    switch (type) {
179      case content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT: {
180        ResourceRedirectDetails* redirect_details =
181            content::Details<ResourceRedirectDetails>(details).ptr();
182        if (redirect_details != NULL)
183          BeforeUrlLoaded(redirect_details->new_url);
184        break;
185      }
186      case content::NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED: {
187        std::pair<content::NavigationEntry*, bool>* title =
188            content::Details<std::pair<content::NavigationEntry*, bool> >(
189                details).ptr();
190
191        if (title->first) {
192          delegate_->OnAuthFlowTitleChange(
193              base::UTF16ToUTF8(title->first->GetTitle()));
194        }
195        break;
196      }
197      default:
198        NOTREACHED()
199            << "Got a notification that we did not register for: " << type;
200        break;
201    }
202  }
203}
204
205void WebAuthFlow::RenderProcessGone(base::TerminationStatus status) {
206  if (delegate_)
207    delegate_->OnAuthFlowFailure(WebAuthFlow::WINDOW_CLOSED);
208}
209
210void WebAuthFlow::DidStartProvisionalLoadForFrame(
211    content::RenderFrameHost* render_frame_host,
212    const GURL& validated_url,
213    bool is_error_page,
214    bool is_iframe_srcdoc) {
215  if (!render_frame_host->GetParent())
216    BeforeUrlLoaded(validated_url);
217}
218
219void WebAuthFlow::DidFailProvisionalLoad(
220    content::RenderFrameHost* render_frame_host,
221    const GURL& validated_url,
222    int error_code,
223    const base::string16& error_description) {
224  TRACE_EVENT_ASYNC_STEP_PAST1("identity",
225                               "WebAuthFlow",
226                               this,
227                               "DidFailProvisionalLoad",
228                               "error_code",
229                               error_code);
230  if (delegate_)
231    delegate_->OnAuthFlowFailure(LOAD_FAILED);
232}
233
234void WebAuthFlow::DidStopLoading(RenderViewHost* render_view_host) {
235  AfterUrlLoaded();
236}
237
238void WebAuthFlow::DidNavigateMainFrame(
239    const content::LoadCommittedDetails& details,
240    const content::FrameNavigateParams& params) {
241  if (delegate_ && details.http_status_code >= 400)
242    delegate_->OnAuthFlowFailure(LOAD_FAILED);
243}
244
245}  // namespace extensions
246