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