diff options
14 files changed, 447 insertions, 5 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 1c01d52..0a766ff 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -4263,6 +4263,9 @@ Make sure you do not expose any sensitive information. <message name="IDS_EXTENSION_PROMPT_WARNING_DOWNLOADS_OPEN" desc="Permission string for access to downloads."> Open downloaded files </message> + <message name="IDS_EXTENSION_PROMPT_WARNING_IDENTITY_EMAIL" desc="Permission string for access to profile email address."> + View email addresses signed in to your profile + </message> <message name="IDS_EXTENSION_PROMPT_WARNING_WALLPAPER" desc="Permission string for access to wallpaper."> Change your wallpaper </message> diff --git a/chrome/browser/extensions/api/identity/identity_api.cc b/chrome/browser/extensions/api/identity/identity_api.cc index f60da81..491b65e 100644 --- a/chrome/browser/extensions/api/identity/identity_api.cc +++ b/chrome/browser/extensions/api/identity/identity_api.cc @@ -655,14 +655,20 @@ const base::Time& IdentityTokenCacheValue::expiration_time() const { IdentityAPI::IdentityAPI(Profile* profile) : profile_(profile), - error_(GoogleServiceAuthError::NONE) { - SigninGlobalError::GetForProfile(profile_)->AddProvider(this); - ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)->AddObserver(this); + error_(GoogleServiceAuthError::NONE), + initialized_(false) { } IdentityAPI::~IdentityAPI() { } +void IdentityAPI::Initialize() { + SigninGlobalError::GetForProfile(profile_)->AddProvider(this); + ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)->AddObserver(this); + + initialized_ = true; +} + IdentityMintRequestQueue* IdentityAPI::mint_queue() { return &mint_queue_; } @@ -714,9 +720,14 @@ void IdentityAPI::ReportAuthError(const GoogleServiceAuthError& error) { } void IdentityAPI::Shutdown() { + if (!initialized_) + return; + SigninGlobalError::GetForProfile(profile_)->RemoveProvider(this); ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)-> RemoveObserver(this); + + initialized_ = false; } static base::LazyInstance<ProfileKeyedAPIFactory<IdentityAPI> > diff --git a/chrome/browser/extensions/api/identity/identity_api.h b/chrome/browser/extensions/api/identity/identity_api.h index 5b790e1..e37c603 100644 --- a/chrome/browser/extensions/api/identity/identity_api.h +++ b/chrome/browser/extensions/api/identity/identity_api.h @@ -255,6 +255,7 @@ class IdentityAPI : public ProfileKeyedAPI, explicit IdentityAPI(Profile* profile); virtual ~IdentityAPI(); + void Initialize(); // Request serialization queue for getAuthToken. IdentityMintRequestQueue* mint_queue(); @@ -295,6 +296,7 @@ class IdentityAPI : public ProfileKeyedAPI, Profile* profile_; GoogleServiceAuthError error_; + bool initialized_; IdentityMintRequestQueue mint_queue_; CachedTokens token_cache_; }; diff --git a/chrome/browser/extensions/api/identity/identity_event_router.cc b/chrome/browser/extensions/api/identity/identity_event_router.cc new file mode 100644 index 0000000..06a9ac8 --- /dev/null +++ b/chrome/browser/extensions/api/identity/identity_event_router.cc @@ -0,0 +1,73 @@ +// Copyright 2013 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/identity_event_router.h" + +#include <set> + +#include "base/stl_util.h" +#include "base/values.h" +#include "chrome/browser/extensions/event_router.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_system.h" +#include "chrome/common/extensions/api/identity.h" + +namespace extensions { + +IdentityEventRouter::IdentityEventRouter(Profile* profile) + : profile_(profile) {} + +IdentityEventRouter::~IdentityEventRouter() {} + +void IdentityEventRouter::DispatchSignInEvent(const std::string& id, + const std::string& email, + bool is_signed_in) { + const EventListenerMap::ListenerList& listeners = + extensions::ExtensionSystem::Get(profile_)->event_router()->listeners() + .GetEventListenersByName(api::identity::OnSignInChanged::kEventName); + + ExtensionService* service = + ExtensionSystem::Get(profile_)->extension_service(); + EventRouter* event_router = ExtensionSystem::Get(profile_)->event_router(); + + api::identity::AccountInfo account_info; + account_info.id = id; + + api::identity::AccountInfo account_info_email; + account_info_email.id = id; + account_info_email.email = scoped_ptr<std::string>(new std::string(email)); + + std::set<std::string> already_dispatched; + + for (EventListenerMap::ListenerList::const_iterator it = listeners.begin(); + it != listeners.end(); + ++it) { + + const std::string extension_id = (*it)->extension_id; + const Extension* extension = service->extensions()->GetByID(extension_id); + + if (ContainsKey(already_dispatched, extension_id)) + continue; + + already_dispatched.insert(extension_id); + + // Add the email address to AccountInfo only for extensions that + // have APIPermission::kIdentityEmail. + scoped_ptr<base::ListValue> args; + if (extension->HasAPIPermission(APIPermission::kIdentityEmail)) { + args = api::identity::OnSignInChanged::Create(account_info_email, + is_signed_in); + } else { + args = api::identity::OnSignInChanged::Create(account_info, + is_signed_in); + } + + scoped_ptr<Event> event( + new Event(api::identity::OnSignInChanged::kEventName, args.Pass())); + event->restrict_to_profile = profile_; + event_router->DispatchEventToExtension(extension_id, event.Pass()); + } +} + +} // namespace extensions diff --git a/chrome/browser/extensions/api/identity/identity_event_router.h b/chrome/browser/extensions/api/identity/identity_event_router.h new file mode 100644 index 0000000..21bf2eb --- /dev/null +++ b/chrome/browser/extensions/api/identity/identity_event_router.h @@ -0,0 +1,35 @@ +// Copyright 2013 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. + +#ifndef CHROME_BROWSER_EXTENSIONS_API_IDENTITY_IDENTITY_EVENT_ROUTER_H_ +#define CHROME_BROWSER_EXTENSIONS_API_IDENTITY_IDENTITY_EVENT_ROUTER_H_ + +#include <string> + +#include "base/basictypes.h" + +class Profile; + +namespace extensions { + +class IdentityEventRouter { + public: + explicit IdentityEventRouter(Profile* profile); + ~IdentityEventRouter(); + + // Dispatch identity.onSignInChanged event, including email address + // for extensions with the identity.email permission. + void DispatchSignInEvent(const std::string& id, + const std::string& email, + bool is_signed_in); + + private: + Profile* profile_; + + DISALLOW_COPY_AND_ASSIGN(IdentityEventRouter); +}; + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_IDENTITY_IDENTITY_EVENT_ROUTER_H_ diff --git a/chrome/browser/extensions/api/identity/identity_event_router_unittest.cc b/chrome/browser/extensions/api/identity/identity_event_router_unittest.cc new file mode 100644 index 0000000..8b65c5c --- /dev/null +++ b/chrome/browser/extensions/api/identity/identity_event_router_unittest.cc @@ -0,0 +1,291 @@ +// Copyright 2013 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/identity_event_router.h" + +#include <map> +#include <string> +#include <vector> + +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/path_service.h" +#include "base/stl_util.h" +#include "base/strings/stringprintf.h" +#include "base/values.h" +#include "chrome/browser/extensions/event_router.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_system.h" +#include "chrome/browser/extensions/extension_system_factory.h" +#include "chrome/browser/extensions/test_extension_service.h" +#include "chrome/browser/extensions/test_extension_system.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/extensions/api/identity.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_builder.h" +#include "chrome/common/extensions/extension_set.h" +#include "chrome/common/extensions/permissions/permissions_data.h" +#include "chrome/common/extensions/value_builder.h" +#include "chrome/test/base/testing_profile.h" +#include "content/public/test/mock_render_process_host.h" +#include "content/public/test/test_browser_thread_bundle.h" +#include "extensions/common/permissions/api_permission.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +struct EventInfo { + std::string user_id; + std::string email; + bool is_signed_in; +}; + +class FakeEventRouter : public extensions::EventRouter { + public: + explicit FakeEventRouter(Profile* profile) : EventRouter(profile, NULL) {} + + virtual void DispatchEventToExtension( + const std::string& extension_id, + scoped_ptr<extensions::Event> event) OVERRIDE { + EventInfo event_info; + base::DictionaryValue* event_object = NULL; + EXPECT_TRUE(event->event_args->GetDictionary(0, &event_object)); + EXPECT_TRUE(event_object->GetString("id", &event_info.user_id)); + event_object->GetString("email", &event_info.email); + + EXPECT_TRUE(event->event_args->GetBoolean(1, &event_info.is_signed_in)); + + EXPECT_FALSE(ContainsKey(extension_id_to_event_, extension_id)); + extension_id_to_event_[extension_id] = event_info; + } + + size_t GetEventCount() { + return extension_id_to_event_.size(); + } + + bool ContainsExtensionId(const std::string extension_id) { + return ContainsKey(extension_id_to_event_, extension_id); + } + + const EventInfo& GetEventInfo(const std::string extension_id) { + return extension_id_to_event_[extension_id]; + } + + private: + std::map<std::string, EventInfo> extension_id_to_event_; + + DISALLOW_COPY_AND_ASSIGN(FakeEventRouter); +}; + +class FakeExtensionService : public TestExtensionService { + public: + FakeExtensionService() {} + virtual ~FakeExtensionService() {} + + virtual const ExtensionSet* extensions() const OVERRIDE { + return &extensions_; + } + + virtual void AddExtension(const extensions::Extension* extension) OVERRIDE { + extensions_.Insert(extension); + } + + private: + ExtensionSet extensions_; +}; + +class FakeExtensionSystem : public extensions::TestExtensionSystem { + public: + explicit FakeExtensionSystem(Profile* profile) + : extensions::TestExtensionSystem(profile) {} + + virtual extensions::EventRouter* event_router() OVERRIDE { + return fake_event_router(); + } + + virtual ExtensionService* extension_service() OVERRIDE { + ExtensionServiceInterface* as_interface = + static_cast<ExtensionServiceInterface*>(&fake_extension_service_); + return static_cast<ExtensionService*>(as_interface); + } + + FakeEventRouter* fake_event_router() { + if (!fake_event_router_) + fake_event_router_.reset(new FakeEventRouter(profile_)); + return fake_event_router_.get(); + } + + private: + FakeExtensionService fake_extension_service_; + scoped_ptr<FakeEventRouter> fake_event_router_; + + DISALLOW_COPY_AND_ASSIGN(FakeExtensionSystem); +}; + +BrowserContextKeyedService* BuildFakeExtensionSystem( + content::BrowserContext* profile) { + return new FakeExtensionSystem(static_cast<Profile*>(profile)); +} + +} // namespace + +namespace extensions { + +class IdentityEventRouterTest : public testing::Test { + public: + IdentityEventRouterTest() + : test_profile_(new TestingProfile()), + identity_event_router_(test_profile_.get()), + extension_counter_(0) {} + + virtual void SetUp() OVERRIDE { + fake_extension_system_ = static_cast<FakeExtensionSystem*>( + ExtensionSystemFactory::GetInstance()->SetTestingFactoryAndUse( + test_profile_.get(), &BuildFakeExtensionSystem)); + } + + FakeEventRouter* fake_event_router() { + return fake_extension_system_->fake_event_router(); + } + + Profile* profile() { + return test_profile_.get(); + } + + protected: + scoped_refptr<const Extension> CreateExtension(bool has_email_permission) { + ListBuilder permissions; + if (has_email_permission) + permissions.Append("identity.email"); + + std::string id = base::StringPrintf("id.%d", extension_counter_++); + scoped_refptr<const Extension> extension = ExtensionBuilder() + .SetID(id) + .SetManifest(DictionaryBuilder() + .Set("name", "Extension with ID " + id) + .Set("version", "1.0") + .Set("manifest_version", 2) + .Set("permissions", permissions)) + .Build(); + fake_extension_system_->extension_service()->AddExtension(extension.get()); + fake_event_router()->AddEventListener( + api::identity::OnSignInChanged::kEventName, NULL, extension->id()); + return extension; + } + + scoped_ptr<TestingProfile> test_profile_; + IdentityEventRouter identity_event_router_; + FakeExtensionSystem* fake_extension_system_; + content::TestBrowserThreadBundle thread_bundle_; + int extension_counter_; +}; + +TEST_F(IdentityEventRouterTest, SignInNoListeners) { + identity_event_router_.DispatchSignInEvent( + "test_user_id", "test_email", true); + EXPECT_EQ(0ul, fake_event_router()->GetEventCount()); +} + +TEST_F(IdentityEventRouterTest, SignInNoEmailListener) { + scoped_refptr<const Extension> ext = CreateExtension(false); + identity_event_router_.DispatchSignInEvent( + "test_user_id", "test_email", true); + EXPECT_EQ(1ul, fake_event_router()->GetEventCount()); + EXPECT_TRUE(fake_event_router()->ContainsExtensionId(ext->id())); + EXPECT_EQ("test_user_id", + fake_event_router()->GetEventInfo(ext->id()).user_id); + EXPECT_TRUE(fake_event_router()->GetEventInfo(ext->id()).email.empty()); + EXPECT_TRUE(fake_event_router()->GetEventInfo(ext->id()).is_signed_in); +} + +TEST_F(IdentityEventRouterTest, SignInWithEmailListener) { + scoped_refptr<const Extension> ext = CreateExtension(true); + identity_event_router_.DispatchSignInEvent( + "test_user_id", "test_email", true); + EXPECT_EQ(1ul, fake_event_router()->GetEventCount()); + EXPECT_TRUE(fake_event_router()->ContainsExtensionId(ext->id())); + EXPECT_EQ("test_user_id", + fake_event_router()->GetEventInfo(ext->id()).user_id); + EXPECT_EQ("test_email", fake_event_router()->GetEventInfo(ext->id()).email); + EXPECT_TRUE(fake_event_router()->GetEventInfo(ext->id()).is_signed_in); +} + +TEST_F(IdentityEventRouterTest, SignInMultipleListeners) { + typedef std::vector<scoped_refptr<const Extension> > ExtensionVector; + ExtensionVector with_email; + ExtensionVector no_email; + + for (int i = 0; i < 3; i++) + with_email.push_back(CreateExtension(true)); + + for (int i = 0; i < 2; i++) + no_email.push_back(CreateExtension(false)); + + identity_event_router_.DispatchSignInEvent( + "test_user_id", "test_email", true); + + EXPECT_EQ(with_email.size() + no_email.size(), + fake_event_router()->GetEventCount()); + + for (ExtensionVector::const_iterator it = with_email.begin(); + it != with_email.end(); + ++it) { + EXPECT_TRUE(fake_event_router()->ContainsExtensionId((*it)->id())); + EXPECT_EQ("test_user_id", + fake_event_router()->GetEventInfo((*it)->id()).user_id); + EXPECT_EQ("test_email", + fake_event_router()->GetEventInfo((*it)->id()).email); + EXPECT_TRUE(fake_event_router()->GetEventInfo((*it)->id()).is_signed_in); + } + + for (ExtensionVector::const_iterator it = no_email.begin(); + it != no_email.end(); + ++it) { + EXPECT_TRUE(fake_event_router()->ContainsExtensionId((*it)->id())); + EXPECT_EQ("test_user_id", + fake_event_router()->GetEventInfo((*it)->id()).user_id); + EXPECT_TRUE(fake_event_router()->GetEventInfo((*it)->id()).email.empty()); + EXPECT_TRUE(fake_event_router()->GetEventInfo((*it)->id()).is_signed_in); + } +} + +TEST_F(IdentityEventRouterTest, SignInWithTwoListenersOnOneExtension) { + scoped_refptr<const Extension> ext = CreateExtension(true); + + scoped_ptr<content::MockRenderProcessHost> fake_render_process( + new content::MockRenderProcessHost(profile())); + fake_event_router()->AddEventListener( + api::identity::OnSignInChanged::kEventName, + fake_render_process.get(), + ext->id()); + + identity_event_router_.DispatchSignInEvent( + "test_user_id", "test_email", true); + EXPECT_EQ(1ul, fake_event_router()->GetEventCount()); + EXPECT_TRUE(fake_event_router()->ContainsExtensionId(ext->id())); + EXPECT_EQ("test_user_id", + fake_event_router()->GetEventInfo(ext->id()).user_id); + EXPECT_EQ("test_email", fake_event_router()->GetEventInfo(ext->id()).email); + EXPECT_TRUE(fake_event_router()->GetEventInfo(ext->id()).is_signed_in); + + fake_event_router()->RemoveEventListener( + api::identity::OnSignInChanged::kEventName, + fake_render_process.get(), + ext->id()); +} + +TEST_F(IdentityEventRouterTest, SignOut) { + scoped_refptr<const Extension> ext = CreateExtension(false); + identity_event_router_.DispatchSignInEvent( + "test_user_id", "test_email", false); + EXPECT_EQ(1ul, fake_event_router()->GetEventCount()); + EXPECT_TRUE(fake_event_router()->ContainsExtensionId(ext->id())); + EXPECT_EQ("test_user_id", + fake_event_router()->GetEventInfo(ext->id()).user_id); + EXPECT_TRUE(fake_event_router()->GetEventInfo(ext->id()).email.empty()); + EXPECT_FALSE(fake_event_router()->GetEventInfo(ext->id()).is_signed_in); +} + +} // namespace extensions diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi index 9f2d058..51dbee7 100644 --- a/chrome/chrome_browser_extensions.gypi +++ b/chrome/chrome_browser_extensions.gypi @@ -284,6 +284,8 @@ 'browser/extensions/api/identity/gaia_web_auth_flow.h', 'browser/extensions/api/identity/identity_api.cc', 'browser/extensions/api/identity/identity_api.h', + 'browser/extensions/api/identity/identity_event_router.cc', + 'browser/extensions/api/identity/identity_event_router.h', 'browser/extensions/api/identity/identity_mint_queue.cc', 'browser/extensions/api/identity/identity_mint_queue.h', 'browser/extensions/api/identity/identity_signin_flow.cc', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index d9f91ea..083c7e4 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -809,6 +809,7 @@ 'browser/extensions/api/file_system/file_system_api_unittest.cc', 'browser/extensions/api/identity/experimental_web_auth_flow_unittest.cc', 'browser/extensions/api/identity/gaia_web_auth_flow_unittest.cc', + 'browser/extensions/api/identity/identity_event_router_unittest.cc', 'browser/extensions/api/identity/identity_mint_queue_unittest.cc', 'browser/extensions/api/idle/idle_api_unittest.cc', 'browser/extensions/api/log_private/syslog_parser_unittest.cc', diff --git a/chrome/common/extensions/api/_api_features.json b/chrome/common/extensions/api/_api_features.json index b6e72e7..9026c20 100644 --- a/chrome/common/extensions/api/_api_features.json +++ b/chrome/common/extensions/api/_api_features.json @@ -309,6 +309,11 @@ "dependencies": ["permission:identity"], "contexts": ["blessed_extension"] }, + "identity.onSignInChanged": { + "channel": "dev", + "dependencies": ["permission:identity"], + "contexts": ["blessed_extension"] + }, "identityPrivate": { "dependencies": ["permission:identityPrivate"], "contexts": ["blessed_extension"] diff --git a/chrome/common/extensions/api/_permission_features.json b/chrome/common/extensions/api/_permission_features.json index ce62073..922176a 100644 --- a/chrome/common/extensions/api/_permission_features.json +++ b/chrome/common/extensions/api/_permission_features.json @@ -360,6 +360,10 @@ "channel": "stable", "extension_types": ["extension", "platform_app"] }, + "identity.email": { + "channel": "dev", + "extension_types": ["extension", "platform_app"] + }, "identityPrivate": { "channel": "stable", "extension_types": [ diff --git a/chrome/common/extensions/api/identity.idl b/chrome/common/extensions/api/identity.idl index cba84e4..cefd50c 100644 --- a/chrome/common/extensions/api/identity.idl +++ b/chrome/common/extensions/api/identity.idl @@ -39,6 +39,11 @@ namespace identity { boolean? interactive; }; + dictionary AccountInfo { + DOMString id; + DOMString? email; + }; + callback GetAuthTokenCallback = void (optional DOMString token); callback InvalidateAuthTokenCallback = void (); callback LaunchWebAuthFlowCallback = void (optional DOMString responseUrl); @@ -85,6 +90,10 @@ namespace identity { // |callback| : Called with the URL redirected back to your application. static void launchWebAuthFlow(WebAuthFlowDetails details, LaunchWebAuthFlowCallback callback); - } - ; + }; + + interface Events { + // Fired when signin state changes for an account on the user's profile. + static void onSignInChanged(AccountInfo account, boolean signedIn); + }; }; diff --git a/chrome/common/extensions/permissions/chrome_api_permissions.cc b/chrome/common/extensions/permissions/chrome_api_permissions.cc index 28790c2..1950a9946 100644 --- a/chrome/common/extensions/permissions/chrome_api_permissions.cc +++ b/chrome/common/extensions/permissions/chrome_api_permissions.cc @@ -64,6 +64,10 @@ std::vector<APIPermissionInfo*> ChromeAPIPermissions::GetAllPermissions() PermissionMessage::kDownloadsOpen }, { APIPermission::kDownloadsShelf, "downloads.shelf" }, { APIPermission::kIdentity, "identity" }, + { APIPermission::kIdentityEmail, "identity.email", + APIPermissionInfo::kFlagNone, + IDS_EXTENSION_PROMPT_WARNING_IDENTITY_EMAIL, + PermissionMessage::kIdentityEmail }, { APIPermission::kExperimental, "experimental", APIPermissionInfo::kFlagCannotBeOptional }, // NOTE(kalman): this is provided by a manifest property but needs to diff --git a/extensions/common/permissions/api_permission.h b/extensions/common/permissions/api_permission.h index 7e09d14..7dd42c0 100644 --- a/extensions/common/permissions/api_permission.h +++ b/extensions/common/permissions/api_permission.h @@ -91,6 +91,7 @@ class APIPermission { kGeolocation, kHistory, kIdentity, + kIdentityEmail, kIdentityPrivate, kIdltest, kIdle, diff --git a/extensions/common/permissions/permission_message.h b/extensions/common/permissions/permission_message.h index 19c5799..3b745b2 100644 --- a/extensions/common/permissions/permission_message.h +++ b/extensions/common/permissions/permission_message.h @@ -74,6 +74,7 @@ class PermissionMessage { kSignedInDevices, kWallpaper, kNetworkState, + kIdentityEmail, kEnumBoundary, }; COMPILE_ASSERT(PermissionMessage::kNone > PermissionMessage::kUnknown, |