blob: eb6106857521331fa196c6dca32a6be74615b590 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/api/identity/web_auth_flow.h"
#include "apps/shell_window.h"
#include "base/base64.h"
#include "base/location.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/extensions/component_loader.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_system.h"
#include "chrome/browser/extensions/extension_system.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/extensions/api/identity_private.h"
#include "chrome/common/extensions/extension_constants.h"
#include "content/public/browser/navigation_details.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/resource_request_details.h"
#include "content/public/browser/web_contents.h"
#include "crypto/random.h"
#include "extensions/browser/event_router.h"
#include "grit/browser_resources.h"
#include "url/gurl.h"
using apps::ShellWindow;
using content::RenderViewHost;
using content::ResourceRedirectDetails;
using content::WebContents;
using content::WebContentsObserver;
namespace extensions {
namespace identity_private = api::identity_private;
WebAuthFlow::WebAuthFlow(
Delegate* delegate,
Profile* profile,
const GURL& provider_url,
Mode mode)
: delegate_(delegate),
profile_(profile),
provider_url_(provider_url),
mode_(mode),
embedded_window_created_(false) {
}
WebAuthFlow::~WebAuthFlow() {
DCHECK(delegate_ == NULL);
// Stop listening to notifications first since some of the code
// below may generate notifications.
registrar_.RemoveAll();
WebContentsObserver::Observe(NULL);
if (!shell_window_key_.empty()) {
apps::ShellWindowRegistry::Get(profile_)->RemoveObserver(this);
if (shell_window_ && shell_window_->web_contents())
shell_window_->web_contents()->Close();
}
}
void WebAuthFlow::Start() {
apps::ShellWindowRegistry::Get(profile_)->AddObserver(this);
// Attach a random ID string to the window so we can recoginize it
// in OnShellWindowAdded.
std::string random_bytes;
crypto::RandBytes(WriteInto(&random_bytes, 33), 32);
bool success = base::Base64Encode(random_bytes, &shell_window_key_);
DCHECK(success);
// identityPrivate.onWebFlowRequest(shell_window_key, provider_url_, mode_)
scoped_ptr<base::ListValue> args(new base::ListValue());
args->AppendString(shell_window_key_);
args->AppendString(provider_url_.spec());
if (mode_ == WebAuthFlow::INTERACTIVE)
args->AppendString("interactive");
else
args->AppendString("silent");
scoped_ptr<Event> event(
new Event(identity_private::OnWebFlowRequest::kEventName, args.Pass()));
event->restrict_to_browser_context = profile_;
ExtensionSystem* system = ExtensionSystem::Get(profile_);
extensions::ComponentLoader* component_loader =
system->extension_service()->component_loader();
if (!component_loader->Exists(extension_misc::kIdentityApiUiAppId)) {
component_loader->Add(
IDR_IDENTITY_API_SCOPE_APPROVAL_MANIFEST,
base::FilePath(FILE_PATH_LITERAL("identity_scope_approval_dialog")));
}
system->event_router()->DispatchEventWithLazyListener(
extension_misc::kIdentityApiUiAppId, event.Pass());
}
void WebAuthFlow::DetachDelegateAndDelete() {
delegate_ = NULL;
base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
}
void WebAuthFlow::OnShellWindowAdded(ShellWindow* shell_window) {
if (shell_window->window_key() == shell_window_key_ &&
shell_window->extension()->id() == extension_misc::kIdentityApiUiAppId) {
shell_window_ = shell_window;
WebContentsObserver::Observe(shell_window->web_contents());
registrar_.Add(
this,
content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED,
content::NotificationService::AllBrowserContextsAndSources());
}
}
void WebAuthFlow::OnShellWindowIconChanged(ShellWindow* shell_window) {}
void WebAuthFlow::OnShellWindowRemoved(ShellWindow* shell_window) {
if (shell_window->window_key() == shell_window_key_ &&
shell_window->extension()->id() == extension_misc::kIdentityApiUiAppId) {
shell_window_ = NULL;
registrar_.RemoveAll();
if (delegate_)
delegate_->OnAuthFlowFailure(WebAuthFlow::WINDOW_CLOSED);
}
}
void WebAuthFlow::BeforeUrlLoaded(const GURL& url) {
if (delegate_ && embedded_window_created_)
delegate_->OnAuthFlowURLChange(url);
}
void WebAuthFlow::AfterUrlLoaded() {
if (delegate_ && embedded_window_created_ && mode_ == WebAuthFlow::SILENT)
delegate_->OnAuthFlowFailure(WebAuthFlow::INTERACTION_REQUIRED);
}
void WebAuthFlow::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
DCHECK(shell_window_);
if (!delegate_)
return;
if (!embedded_window_created_) {
DCHECK(type == content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED);
RenderViewHost* render_view(
content::Details<RenderViewHost>(details).ptr());
WebContents* web_contents = WebContents::FromRenderViewHost(render_view);
if (web_contents &&
(web_contents->GetEmbedderWebContents() ==
WebContentsObserver::web_contents())) {
// Switch from watching the shell window to the guest inside it.
embedded_window_created_ = true;
WebContentsObserver::Observe(web_contents);
registrar_.RemoveAll();
registrar_.Add(this,
content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT,
content::Source<WebContents>(web_contents));
registrar_.Add(this,
content::NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED,
content::Source<WebContents>(web_contents));
}
} else {
// embedded_window_created_
switch (type) {
case content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT: {
ResourceRedirectDetails* redirect_details =
content::Details<ResourceRedirectDetails>(details).ptr();
if (redirect_details != NULL)
BeforeUrlLoaded(redirect_details->new_url);
break;
}
case content::NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED: {
std::pair<content::NavigationEntry*, bool>* title =
content::Details<std::pair<content::NavigationEntry*, bool> >(
details).ptr();
if (title->first) {
delegate_->OnAuthFlowTitleChange(
UTF16ToUTF8(title->first->GetTitle()));
}
break;
}
default:
NOTREACHED()
<< "Got a notification that we did not register for: " << type;
break;
}
}
}
void WebAuthFlow::RenderProcessGone(base::TerminationStatus status) {
if (delegate_)
delegate_->OnAuthFlowFailure(WebAuthFlow::WINDOW_CLOSED);
}
void WebAuthFlow::DidStartProvisionalLoadForFrame(
int64 frame_id,
int64 parent_frame_id,
bool is_main_frame,
const GURL& validated_url,
bool is_error_page,
bool is_iframe_srcdoc,
RenderViewHost* render_view_host) {
if (is_main_frame)
BeforeUrlLoaded(validated_url);
}
void WebAuthFlow::DidFailProvisionalLoad(int64 frame_id,
const string16& frame_unique_name,
bool is_main_frame,
const GURL& validated_url,
int error_code,
const string16& error_description,
RenderViewHost* render_view_host) {
if (delegate_)
delegate_->OnAuthFlowFailure(LOAD_FAILED);
}
void WebAuthFlow::DidStopLoading(RenderViewHost* render_view_host) {
AfterUrlLoaded();
}
void WebAuthFlow::DidNavigateMainFrame(
const content::LoadCommittedDetails& details,
const content::FrameNavigateParams& params) {
if (delegate_ && details.http_status_code >= 400)
delegate_->OnAuthFlowFailure(LOAD_FAILED);
}
} // namespace extensions