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/event_router.h" 15 #include "chrome/browser/extensions/extension_service.h" 16 #include "chrome/browser/extensions/extension_system.h" 17 #include "chrome/browser/extensions/extension_system.h" 18 #include "chrome/browser/profiles/profile.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 "grit/browser_resources.h" 31 #include "url/gurl.h" 32 33 using apps::ShellWindow; 34 using content::RenderViewHost; 35 using content::ResourceRedirectDetails; 36 using content::WebContents; 37 using content::WebContentsObserver; 38 39 namespace extensions { 40 41 WebAuthFlow::WebAuthFlow( 42 Delegate* delegate, 43 Profile* profile, 44 const GURL& provider_url, 45 Mode mode) 46 : delegate_(delegate), 47 profile_(profile), 48 provider_url_(provider_url), 49 mode_(mode), 50 embedded_window_created_(false) { 51 } 52 53 WebAuthFlow::~WebAuthFlow() { 54 DCHECK(delegate_ == NULL); 55 56 // Stop listening to notifications first since some of the code 57 // below may generate notifications. 58 registrar_.RemoveAll(); 59 WebContentsObserver::Observe(NULL); 60 61 if (!shell_window_key_.empty()) { 62 apps::ShellWindowRegistry::Get(profile_)->RemoveObserver(this); 63 64 if (shell_window_ && shell_window_->web_contents()) 65 shell_window_->web_contents()->Close(); 66 } 67 } 68 69 void WebAuthFlow::Start() { 70 apps::ShellWindowRegistry::Get(profile_)->AddObserver(this); 71 72 // Attach a random ID string to the window so we can recoginize it 73 // in OnShellWindowAdded. 74 std::string random_bytes; 75 crypto::RandBytes(WriteInto(&random_bytes, 33), 32); 76 bool success = base::Base64Encode(random_bytes, &shell_window_key_); 77 DCHECK(success); 78 79 // identityPrivate.onWebFlowRequest(shell_window_key, provider_url_, mode_) 80 scoped_ptr<base::ListValue> args(new base::ListValue()); 81 args->AppendString(shell_window_key_); 82 args->AppendString(provider_url_.spec()); 83 if (mode_ == WebAuthFlow::INTERACTIVE) 84 args->AppendString("interactive"); 85 else 86 args->AppendString("silent"); 87 88 scoped_ptr<Event> event( 89 new Event("identityPrivate.onWebFlowRequest", args.Pass())); 90 event->restrict_to_profile = profile_; 91 ExtensionSystem* system = ExtensionSystem::Get(profile_); 92 93 extensions::ComponentLoader* component_loader = 94 system->extension_service()->component_loader(); 95 if (!component_loader->Exists(extension_misc::kIdentityApiUiAppId)) { 96 component_loader->Add( 97 IDR_IDENTITY_API_SCOPE_APPROVAL_MANIFEST, 98 base::FilePath(FILE_PATH_LITERAL("identity_scope_approval_dialog"))); 99 } 100 101 system->event_router()->DispatchEventWithLazyListener( 102 extension_misc::kIdentityApiUiAppId, event.Pass()); 103 } 104 105 void WebAuthFlow::DetachDelegateAndDelete() { 106 delegate_ = NULL; 107 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); 108 } 109 110 void WebAuthFlow::OnShellWindowAdded(ShellWindow* shell_window) { 111 if (shell_window->window_key() == shell_window_key_ && 112 shell_window->extension()->id() == extension_misc::kIdentityApiUiAppId) { 113 shell_window_ = shell_window; 114 WebContentsObserver::Observe(shell_window->web_contents()); 115 116 registrar_.Add( 117 this, 118 content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED, 119 content::NotificationService::AllBrowserContextsAndSources()); 120 } 121 } 122 123 void WebAuthFlow::OnShellWindowIconChanged(ShellWindow* shell_window) {} 124 125 void WebAuthFlow::OnShellWindowRemoved(ShellWindow* shell_window) { 126 if (shell_window->window_key() == shell_window_key_ && 127 shell_window->extension()->id() == extension_misc::kIdentityApiUiAppId) { 128 shell_window_ = NULL; 129 registrar_.RemoveAll(); 130 131 if (delegate_) 132 delegate_->OnAuthFlowFailure(WebAuthFlow::WINDOW_CLOSED); 133 } 134 } 135 136 void WebAuthFlow::BeforeUrlLoaded(const GURL& url) { 137 if (delegate_ && embedded_window_created_) 138 delegate_->OnAuthFlowURLChange(url); 139 } 140 141 void WebAuthFlow::AfterUrlLoaded() { 142 if (delegate_ && embedded_window_created_ && mode_ == WebAuthFlow::SILENT) 143 delegate_->OnAuthFlowFailure(WebAuthFlow::INTERACTION_REQUIRED); 144 } 145 146 void WebAuthFlow::Observe(int type, 147 const content::NotificationSource& source, 148 const content::NotificationDetails& details) { 149 DCHECK(shell_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 161 if (web_contents && 162 (web_contents->GetEmbedderWebContents() == 163 WebContentsObserver::web_contents())) { 164 // Switch from watching the shell 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 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 205 void WebAuthFlow::RenderProcessGone(base::TerminationStatus status) { 206 if (delegate_) 207 delegate_->OnAuthFlowFailure(WebAuthFlow::WINDOW_CLOSED); 208 } 209 210 void WebAuthFlow::DidStartProvisionalLoadForFrame( 211 int64 frame_id, 212 int64 parent_frame_id, 213 bool is_main_frame, 214 const GURL& validated_url, 215 bool is_error_page, 216 bool is_iframe_srcdoc, 217 RenderViewHost* render_view_host) { 218 if (is_main_frame) 219 BeforeUrlLoaded(validated_url); 220 } 221 222 void WebAuthFlow::DidFailProvisionalLoad(int64 frame_id, 223 bool is_main_frame, 224 const GURL& validated_url, 225 int error_code, 226 const string16& error_description, 227 RenderViewHost* render_view_host) { 228 if (delegate_) 229 delegate_->OnAuthFlowFailure(LOAD_FAILED); 230 } 231 232 void WebAuthFlow::DidStopLoading(RenderViewHost* render_view_host) { 233 AfterUrlLoaded(); 234 } 235 236 void WebAuthFlow::DidNavigateMainFrame( 237 const content::LoadCommittedDetails& details, 238 const content::FrameNavigateParams& params) { 239 if (delegate_ && details.http_status_code >= 400) 240 delegate_->OnAuthFlowFailure(LOAD_FAILED); 241 } 242 243 } // namespace extensions 244