| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/extensions/api/identity/web_auth_flow.h" | 5 #include "chrome/browser/extensions/api/identity/web_auth_flow.h" |
| 6 | 6 |
| 7 #include "base/base64.h" |
| 7 #include "base/location.h" | 8 #include "base/location.h" |
| 8 #include "base/message_loop.h" | 9 #include "base/message_loop.h" |
| 10 #include "base/string_util.h" |
| 9 #include "base/utf_string_conversions.h" | 11 #include "base/utf_string_conversions.h" |
| 12 #include "chrome/browser/extensions/component_loader.h" |
| 13 #include "chrome/browser/extensions/event_router.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" |
| 10 #include "chrome/browser/profiles/profile.h" | 17 #include "chrome/browser/profiles/profile.h" |
| 11 #include "chrome/browser/ui/browser.h" | 18 #include "chrome/browser/ui/extensions/shell_window.h" |
| 12 #include "chrome/browser/ui/browser_navigator.h" | 19 #include "chrome/common/extensions/extension_constants.h" |
| 13 #include "content/public/browser/load_notification_details.h" | 20 #include "content/public/browser/navigation_details.h" |
| 14 #include "content/public/browser/navigation_controller.h" | |
| 15 #include "content/public/browser/navigation_entry.h" | 21 #include "content/public/browser/navigation_entry.h" |
| 16 #include "content/public/browser/notification_details.h" | 22 #include "content/public/browser/notification_details.h" |
| 17 #include "content/public/browser/notification_source.h" | 23 #include "content/public/browser/notification_source.h" |
| 18 #include "content/public/browser/notification_types.h" | 24 #include "content/public/browser/notification_types.h" |
| 25 #include "content/public/browser/render_view_host.h" |
| 19 #include "content/public/browser/resource_request_details.h" | 26 #include "content/public/browser/resource_request_details.h" |
| 20 #include "content/public/browser/web_contents.h" | 27 #include "content/public/browser/web_contents.h" |
| 21 #include "content/public/common/page_transition_types.h" | 28 #include "crypto/random.h" |
| 22 #include "googleurl/src/gurl.h" | 29 #include "googleurl/src/gurl.h" |
| 23 #include "ui/base/window_open_disposition.h" | 30 #include "grit/browser_resources.h" |
| 24 | 31 |
| 25 using content::LoadNotificationDetails; | |
| 26 using content::NavigationController; | |
| 27 using content::RenderViewHost; | 32 using content::RenderViewHost; |
| 28 using content::ResourceRedirectDetails; | 33 using content::ResourceRedirectDetails; |
| 29 using content::WebContents; | 34 using content::WebContents; |
| 30 using content::WebContentsObserver; | 35 using content::WebContentsObserver; |
| 31 | 36 |
| 32 namespace extensions { | 37 namespace extensions { |
| 33 | 38 |
| 34 WebAuthFlow::WebAuthFlow( | 39 WebAuthFlow::WebAuthFlow( |
| 35 Delegate* delegate, | 40 Delegate* delegate, |
| 36 Profile* profile, | 41 Profile* profile, |
| 37 const GURL& provider_url, | 42 const GURL& provider_url, |
| 38 Mode mode, | 43 Mode mode) |
| 39 const gfx::Rect& initial_bounds, | |
| 40 chrome::HostDesktopType host_desktop_type) | |
| 41 : delegate_(delegate), | 44 : delegate_(delegate), |
| 42 profile_(profile), | 45 profile_(profile), |
| 43 provider_url_(provider_url), | 46 provider_url_(provider_url), |
| 44 mode_(mode), | 47 mode_(mode), |
| 45 initial_bounds_(initial_bounds), | 48 embedded_window_created_(false) { |
| 46 host_desktop_type_(host_desktop_type), | |
| 47 popup_shown_(false), | |
| 48 contents_(NULL) { | |
| 49 } | 49 } |
| 50 | 50 |
| 51 WebAuthFlow::~WebAuthFlow() { | 51 WebAuthFlow::~WebAuthFlow() { |
| 52 DCHECK(delegate_ == NULL); | 52 DCHECK(delegate_ == NULL); |
| 53 | 53 |
| 54 // Stop listening to notifications first since some of the code | 54 // Stop listening to notifications first since some of the code |
| 55 // below may generate notifications. | 55 // below may generate notifications. |
| 56 registrar_.RemoveAll(); | 56 registrar_.RemoveAll(); |
| 57 WebContentsObserver::Observe(NULL); | 57 WebContentsObserver::Observe(NULL); |
| 58 | 58 |
| 59 if (contents_) { | 59 if (!shell_window_key_.empty()) { |
| 60 // The popup owns the contents if it was displayed. | 60 ShellWindowRegistry::Get(profile_)->RemoveObserver(this); |
| 61 if (popup_shown_) | 61 |
| 62 contents_->Close(); | 62 if (shell_window_ && shell_window_->web_contents()) |
| 63 else | 63 shell_window_->web_contents()->Close(); |
| 64 delete contents_; | |
| 65 } | 64 } |
| 66 } | 65 } |
| 67 | 66 |
| 68 void WebAuthFlow::Start() { | 67 void WebAuthFlow::Start() { |
| 69 contents_ = CreateWebContents(); | 68 ShellWindowRegistry::Get(profile_)->AddObserver(this); |
| 70 WebContentsObserver::Observe(contents_); | |
| 71 | 69 |
| 72 NavigationController* controller = &(contents_->GetController()); | 70 // Attach a random ID string to the window so we can recoginize it |
| 71 // in OnShellWindowAdded. |
| 72 std::string random_bytes; |
| 73 crypto::RandBytes(WriteInto(&random_bytes, 33), 32); |
| 74 std::string key; |
| 75 bool success = base::Base64Encode(random_bytes, &shell_window_key_); |
| 76 DCHECK(success); |
| 73 | 77 |
| 74 // Register for appropriate notifications to intercept navigation to the | 78 // identityPrivate.onWebFlowRequest(shell_window_key, provider_url_, mode_) |
| 75 // redirect URLs. | 79 scoped_ptr<ListValue> args(new ListValue()); |
| 76 registrar_.Add( | 80 args->AppendString(shell_window_key_); |
| 77 this, | 81 args->AppendString(provider_url_.spec()); |
| 78 content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT, | 82 if (mode_ == WebAuthFlow::INTERACTIVE) |
| 79 content::Source<WebContents>(contents_)); | 83 args->AppendString("interactive"); |
| 80 registrar_.Add( | 84 else |
| 81 this, | 85 args->AppendString("silent"); |
| 82 content::NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED, | |
| 83 content::Source<WebContents>(contents_)); | |
| 84 | 86 |
| 85 controller->LoadURL( | 87 scoped_ptr<Event> event( |
| 86 provider_url_, | 88 new Event("identityPrivate.onWebFlowRequest", args.Pass())); |
| 87 content::Referrer(), | 89 event->restrict_to_profile = profile_; |
| 88 content::PAGE_TRANSITION_AUTO_TOPLEVEL, | 90 ExtensionSystem* system = ExtensionSystem::Get(profile_); |
| 89 std::string()); | 91 |
| 92 extensions::ComponentLoader* component_loader = |
| 93 system->extension_service()->component_loader(); |
| 94 if (!component_loader->Exists(extension_misc::kIdentityApiUiAppId)) { |
| 95 component_loader->Add( |
| 96 IDR_IDENTITY_API_SCOPE_APPROVAL_MANIFEST, |
| 97 base::FilePath(FILE_PATH_LITERAL("identity_scope_approval_dialog"))); |
| 98 } |
| 99 |
| 100 system->event_router()->AddLazyEventListener( |
| 101 "identityPrivate.onWebFlowRequest", extension_misc::kIdentityApiUiAppId); |
| 102 system->event_router()->DispatchEventToExtension( |
| 103 extension_misc::kIdentityApiUiAppId, event.Pass()); |
| 104 system->event_router()->RemoveLazyEventListener( |
| 105 "identityPrivate.onWebFlowRequest", extension_misc::kIdentityApiUiAppId); |
| 90 } | 106 } |
| 91 | 107 |
| 92 void WebAuthFlow::DetachDelegateAndDelete() { | 108 void WebAuthFlow::DetachDelegateAndDelete() { |
| 93 delegate_ = NULL; | 109 delegate_ = NULL; |
| 94 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); | 110 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); |
| 95 } | 111 } |
| 96 | 112 |
| 97 WebContents* WebAuthFlow::CreateWebContents() { | 113 void WebAuthFlow::OnShellWindowAdded(ShellWindow* shell_window) { |
| 98 return WebContents::Create(WebContents::CreateParams(profile_)); | 114 if (shell_window->window_key() == shell_window_key_ && |
| 115 shell_window->extension()->id() == extension_misc::kIdentityApiUiAppId) { |
| 116 shell_window_ = shell_window; |
| 117 WebContentsObserver::Observe(shell_window->web_contents()); |
| 118 |
| 119 registrar_.Add( |
| 120 this, |
| 121 content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED, |
| 122 content::NotificationService::AllBrowserContextsAndSources()); |
| 123 } |
| 99 } | 124 } |
| 100 | 125 |
| 101 void WebAuthFlow::ShowAuthFlowPopup() { | 126 void WebAuthFlow::OnShellWindowRemoved(ShellWindow* shell_window) { |
| 102 Browser::CreateParams browser_params(Browser::TYPE_POPUP, profile_, | 127 if (shell_window->window_key() == shell_window_key_ && |
| 103 host_desktop_type_); | 128 shell_window->extension()->id() == extension_misc::kIdentityApiUiAppId) { |
| 104 browser_params.initial_bounds = initial_bounds_; | 129 shell_window_ = NULL; |
| 105 Browser* browser = new Browser(browser_params); | 130 registrar_.RemoveAll(); |
| 106 chrome::NavigateParams params(browser, contents_); | 131 |
| 107 params.disposition = CURRENT_TAB; | 132 if (delegate_) |
| 108 params.window_action = chrome::NavigateParams::SHOW_WINDOW; | 133 delegate_->OnAuthFlowFailure(WebAuthFlow::WINDOW_CLOSED); |
| 109 chrome::Navigate(¶ms); | 134 } |
| 110 // Observe method and WebContentsObserver::* methods will be called | |
| 111 // for varous navigation events. That is where we check for redirect | |
| 112 // to the right URL. | |
| 113 popup_shown_ = true; | |
| 114 } | 135 } |
| 115 | 136 |
| 116 void WebAuthFlow::BeforeUrlLoaded(const GURL& url) { | 137 void WebAuthFlow::BeforeUrlLoaded(const GURL& url) { |
| 117 if (delegate_) | 138 if (delegate_ && embedded_window_created_) |
| 118 delegate_->OnAuthFlowURLChange(url); | 139 delegate_->OnAuthFlowURLChange(url); |
| 119 } | 140 } |
| 120 | 141 |
| 121 void WebAuthFlow::AfterUrlLoaded() { | 142 void WebAuthFlow::AfterUrlLoaded() { |
| 122 // Do nothing if a popup is already created. | 143 if (delegate_ && embedded_window_created_ && mode_ == WebAuthFlow::SILENT) |
| 123 if (popup_shown_) | 144 delegate_->OnAuthFlowFailure(WebAuthFlow::INTERACTION_REQUIRED); |
| 124 return; | |
| 125 | |
| 126 // Report results directly if not in interactive mode. | |
| 127 if (mode_ != WebAuthFlow::INTERACTIVE) { | |
| 128 if (delegate_) | |
| 129 delegate_->OnAuthFlowFailure(WebAuthFlow::INTERACTION_REQUIRED); | |
| 130 return; | |
| 131 } | |
| 132 | |
| 133 // We are in interactive mode and window is not shown yet; show the window. | |
| 134 ShowAuthFlowPopup(); | |
| 135 } | 145 } |
| 136 | 146 |
| 137 void WebAuthFlow::Observe(int type, | 147 void WebAuthFlow::Observe(int type, |
| 138 const content::NotificationSource& source, | 148 const content::NotificationSource& source, |
| 139 const content::NotificationDetails& details) { | 149 const content::NotificationDetails& details) { |
| 140 switch (type) { | 150 DCHECK(shell_window_); |
| 141 case content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT: { | 151 |
| 142 ResourceRedirectDetails* redirect_details = | 152 if (!delegate_) |
| 143 content::Details<ResourceRedirectDetails>(details).ptr(); | 153 return; |
| 144 if (redirect_details != NULL) | 154 |
| 145 BeforeUrlLoaded(redirect_details->new_url); | 155 if (!embedded_window_created_) { |
| 156 DCHECK(type == content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED); |
| 157 |
| 158 RenderViewHost* render_view( |
| 159 content::Details<RenderViewHost>(details).ptr()); |
| 160 WebContents* web_contents = WebContents::FromRenderViewHost(render_view); |
| 161 |
| 162 if (web_contents && |
| 163 (web_contents->GetEmbedderWebContents() == |
| 164 WebContentsObserver::web_contents())) { |
| 165 // Switch from watching the shell window to the guest inside it. |
| 166 embedded_window_created_ = true; |
| 167 WebContentsObserver::Observe(web_contents); |
| 168 |
| 169 registrar_.RemoveAll(); |
| 170 registrar_.Add(this, |
| 171 content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT, |
| 172 content::Source<WebContents>(web_contents)); |
| 173 registrar_.Add(this, |
| 174 content::NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED, |
| 175 content::Source<WebContents>(web_contents)); |
| 146 } | 176 } |
| 147 break; | 177 } else { |
| 148 case content::NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED: { | 178 // embedded_window_created_ |
| 149 std::pair<content::NavigationEntry*, bool>* title = | 179 switch (type) { |
| 150 content::Details< | 180 case content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT: { |
| 151 std::pair<content::NavigationEntry*, bool> >(details).ptr(); | 181 ResourceRedirectDetails* redirect_details = |
| 182 content::Details<ResourceRedirectDetails>(details).ptr(); |
| 183 if (redirect_details != NULL) |
| 184 BeforeUrlLoaded(redirect_details->new_url); |
| 185 break; |
| 186 } |
| 187 case content::NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED: { |
| 188 std::pair<content::NavigationEntry*, bool>* title = |
| 189 content::Details<std::pair<content::NavigationEntry*, bool> >( |
| 190 details).ptr(); |
| 152 | 191 |
| 153 if (title->first) | 192 if (title->first) { |
| 154 delegate_->OnAuthFlowTitleChange(UTF16ToUTF8(title->first->GetTitle())); | 193 delegate_->OnAuthFlowTitleChange( |
| 194 UTF16ToUTF8(title->first->GetTitle())); |
| 195 } |
| 196 break; |
| 197 } |
| 198 default: |
| 199 NOTREACHED() |
| 200 << "Got a notification that we did not register for: " << type; |
| 201 break; |
| 155 } | 202 } |
| 156 break; | |
| 157 default: | |
| 158 NOTREACHED() << "Got a notification that we did not register for: " | |
| 159 << type; | |
| 160 break; | |
| 161 } | 203 } |
| 162 } | 204 } |
| 163 | 205 |
| 164 void WebAuthFlow::ProvisionalChangeToMainFrameUrl( | 206 void WebAuthFlow::RenderViewGone(base::TerminationStatus status) { |
| 165 const GURL& url, | 207 if (delegate_) |
| 208 delegate_->OnAuthFlowFailure(WebAuthFlow::WINDOW_CLOSED); |
| 209 } |
| 210 |
| 211 void WebAuthFlow::DidStartProvisionalLoadForFrame( |
| 212 int64 frame_id, |
| 213 int64 parent_frame_id, |
| 214 bool is_main_frame, |
| 215 const GURL& validated_url, |
| 216 bool is_error_page, |
| 217 bool is_iframe_srcdoc, |
| 166 RenderViewHost* render_view_host) { | 218 RenderViewHost* render_view_host) { |
| 167 BeforeUrlLoaded(url); | 219 if (is_main_frame) |
| 220 BeforeUrlLoaded(validated_url); |
| 221 } |
| 222 |
| 223 void WebAuthFlow::DidFailProvisionalLoad(int64 frame_id, |
| 224 bool is_main_frame, |
| 225 const GURL& validated_url, |
| 226 int error_code, |
| 227 const string16& error_description, |
| 228 RenderViewHost* render_view_host) { |
| 229 if (delegate_) |
| 230 delegate_->OnAuthFlowFailure(LOAD_FAILED); |
| 168 } | 231 } |
| 169 | 232 |
| 170 void WebAuthFlow::DidStopLoading(RenderViewHost* render_view_host) { | 233 void WebAuthFlow::DidStopLoading(RenderViewHost* render_view_host) { |
| 171 AfterUrlLoaded(); | 234 AfterUrlLoaded(); |
| 172 } | 235 } |
| 173 | 236 |
| 174 void WebAuthFlow::WebContentsDestroyed(WebContents* web_contents) { | 237 void WebAuthFlow::DidNavigateMainFrame( |
| 175 contents_ = NULL; | 238 const content::LoadCommittedDetails& details, |
| 176 if (delegate_) | 239 const content::FrameNavigateParams& params) { |
| 177 delegate_->OnAuthFlowFailure(WebAuthFlow::WINDOW_CLOSED); | 240 if (delegate_ && details.http_status_code >= 400) |
| 241 delegate_->OnAuthFlowFailure(LOAD_FAILED); |
| 178 } | 242 } |
| 179 | 243 |
| 180 } // namespace extensions | 244 } // namespace extensions |
| OLD | NEW |