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