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 |