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