identity_api.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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
3// found in the LICENSE file.
4
5#include "chrome/browser/extensions/api/identity/identity_api.h"
6
7#include "base/lazy_instance.h"
8#include "base/values.h"
9#include "chrome/browser/extensions/extension_function_dispatcher.h"
10#include "chrome/browser/extensions/extension_install_prompt.h"
11#include "chrome/browser/extensions/extension_service.h"
12#include "chrome/browser/extensions/permissions_updater.h"
13#include "chrome/browser/profiles/profile.h"
14#include "chrome/browser/signin/token_service.h"
15#include "chrome/browser/signin/token_service_factory.h"
16#include "chrome/browser/ui/browser.h"
17#include "chrome/browser/ui/browser_navigator.h"
18#include "chrome/browser/ui/webui/signin/login_ui_service.h"
19#include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
20#include "chrome/browser/ui/webui/sync_promo/sync_promo_ui.h"
21#include "chrome/common/extensions/api/experimental_identity.h"
22#include "chrome/common/extensions/api/identity/oauth2_manifest_handler.h"
23#include "chrome/common/extensions/extension.h"
24#include "chrome/common/extensions/extension_manifest_constants.h"
25#include "chrome/common/extensions/manifest_handler.h"
26#include "chrome/common/url_constants.h"
27#include "content/public/common/page_transition_types.h"
28#include "googleurl/src/gurl.h"
29#include "ui/base/window_open_disposition.h"
30
31namespace extensions {
32
33namespace identity_constants {
34const char kInvalidClientId[] = "Invalid OAuth2 Client ID.";
35const char kInvalidScopes[] = "Invalid OAuth2 scopes.";
36const char kAuthFailure[] = "OAuth2 request failed: ";
37const char kNoGrant[] = "OAuth2 not granted or revoked.";
38const char kUserRejected[] = "The user did not approve access.";
39const char kUserNotSignedIn[] = "The user is not signed in.";
40const char kInvalidRedirect[] = "Did not redirect to the right URL.";
41}  // namespace identity_constants
42
43namespace GetAuthToken = api::experimental_identity::GetAuthToken;
44namespace LaunchWebAuthFlow = api::experimental_identity::LaunchWebAuthFlow;
45namespace identity = api::experimental_identity;
46
47IdentityGetAuthTokenFunction::IdentityGetAuthTokenFunction()
48    : interactive_(false) {}
49IdentityGetAuthTokenFunction::~IdentityGetAuthTokenFunction() {}
50
51bool IdentityGetAuthTokenFunction::RunImpl() {
52  scoped_ptr<GetAuthToken::Params> params(GetAuthToken::Params::Create(*args_));
53  EXTENSION_FUNCTION_VALIDATE(params.get());
54  if (params->details.get() && params->details->interactive.get())
55    interactive_ = *params->details->interactive;
56
57  const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
58
59  // Check that the necessary information is present in the manfist.
60  if (oauth2_info.client_id.empty()) {
61    error_ = identity_constants::kInvalidClientId;
62    return false;
63  }
64
65  if (oauth2_info.scopes.size() == 0) {
66    error_ = identity_constants::kInvalidScopes;
67    return false;
68  }
69
70  // Balanced in OnIssueAdviceSuccess|OnMintTokenSuccess|OnMintTokenFailure|
71  // InstallUIAbort|OnLoginUIClosed.
72  AddRef();
73
74  if (!HasLoginToken()) {
75    if (StartLogin()) {
76      return true;
77    } else {
78      Release();
79      return false;
80    }
81  }
82
83  if (StartFlow(OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE)) {
84    return true;
85  } else {
86    Release();
87    return false;
88  }
89}
90
91void IdentityGetAuthTokenFunction::OnMintTokenSuccess(
92    const std::string& access_token) {
93  SetResult(Value::CreateStringValue(access_token));
94  SendResponse(true);
95  Release();  // Balanced in RunImpl.
96}
97
98void IdentityGetAuthTokenFunction::OnMintTokenFailure(
99    const GoogleServiceAuthError& error) {
100  error_ = std::string(identity_constants::kAuthFailure) + error.ToString();
101  SendResponse(false);
102  Release();  // Balanced in RunImpl.
103}
104
105void IdentityGetAuthTokenFunction::OnIssueAdviceSuccess(
106    const IssueAdviceInfo& issue_advice) {
107  // Existing grant was revoked and we used NO_FORCE, so we got info back
108  // instead.
109  if (interactive_) {
110    install_ui_.reset(new ExtensionInstallPrompt(GetAssociatedWebContents()));
111    ShowOAuthApprovalDialog(issue_advice);
112  } else {
113    error_ = identity_constants::kNoGrant;
114    SendResponse(false);
115    Release();  // Balanced in RunImpl.
116  }
117}
118
119void IdentityGetAuthTokenFunction::OnLoginUIClosed(
120    LoginUIService::LoginUI* ui) {
121  StopObservingLoginService();
122  if (!StartFlow(OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE)) {
123    SendResponse(false);
124    Release();
125  }
126}
127
128void IdentityGetAuthTokenFunction::InstallUIProceed() {
129  DCHECK(install_ui_->record_oauth2_grant());
130  // The user has accepted the scopes, so we may now force (recording a grant
131  // and receiving a token).
132  bool success = StartFlow(OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE);
133  DCHECK(success);
134}
135
136void IdentityGetAuthTokenFunction::InstallUIAbort(bool user_initiated) {
137  error_ = identity_constants::kUserRejected;
138  SendResponse(false);
139  Release();  // Balanced in RunImpl.
140}
141
142bool IdentityGetAuthTokenFunction::StartFlow(OAuth2MintTokenFlow::Mode mode) {
143  if (!HasLoginToken()) {
144    error_ = identity_constants::kUserNotSignedIn;
145    return false;
146  }
147
148  flow_.reset(CreateMintTokenFlow(mode));
149  flow_->Start();
150  return true;
151}
152
153bool IdentityGetAuthTokenFunction::StartLogin() {
154  if (!interactive_) {
155    error_ = identity_constants::kUserNotSignedIn;
156    return false;
157  }
158
159  ShowLoginPopup();
160  return true;
161}
162
163void IdentityGetAuthTokenFunction::StartObservingLoginService() {
164  LoginUIService* login_ui_service =
165      LoginUIServiceFactory::GetForProfile(profile());
166  login_ui_service->AddObserver(this);
167}
168
169void IdentityGetAuthTokenFunction::StopObservingLoginService() {
170  LoginUIService* login_ui_service =
171      LoginUIServiceFactory::GetForProfile(profile());
172  login_ui_service->RemoveObserver(this);
173}
174
175void IdentityGetAuthTokenFunction::ShowLoginPopup() {
176  StartObservingLoginService();
177
178  LoginUIService* login_ui_service =
179      LoginUIServiceFactory::GetForProfile(profile());
180  login_ui_service->ShowLoginPopup();
181}
182
183void IdentityGetAuthTokenFunction::ShowOAuthApprovalDialog(
184    const IssueAdviceInfo& issue_advice) {
185  install_ui_->ConfirmIssueAdvice(this, GetExtension(), issue_advice);
186}
187
188OAuth2MintTokenFlow* IdentityGetAuthTokenFunction::CreateMintTokenFlow(
189    OAuth2MintTokenFlow::Mode mode) {
190  const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
191  TokenService* token_service = TokenServiceFactory::GetForProfile(profile());
192  return new OAuth2MintTokenFlow(
193      profile()->GetRequestContext(),
194      this,
195      OAuth2MintTokenFlow::Parameters(
196          token_service->GetOAuth2LoginRefreshToken(),
197          GetExtension()->id(),
198          oauth2_info.client_id,
199          oauth2_info.scopes,
200          mode));
201}
202
203bool IdentityGetAuthTokenFunction::HasLoginToken() const {
204  TokenService* token_service = TokenServiceFactory::GetForProfile(profile());
205  return token_service->HasOAuthLoginToken();
206}
207
208IdentityLaunchWebAuthFlowFunction::IdentityLaunchWebAuthFlowFunction() {}
209IdentityLaunchWebAuthFlowFunction::~IdentityLaunchWebAuthFlowFunction() {}
210
211bool IdentityLaunchWebAuthFlowFunction::RunImpl() {
212  scoped_ptr<LaunchWebAuthFlow::Params> params(
213      LaunchWebAuthFlow::Params::Create(*args_));
214  EXTENSION_FUNCTION_VALIDATE(params.get());
215  const identity::WebAuthFlowDetails& details = params->details;
216
217  GURL auth_url(details.url);
218  WebAuthFlow::Mode mode =
219      details.interactive && *details.interactive ?
220      WebAuthFlow::INTERACTIVE : WebAuthFlow::SILENT;
221
222  // The bounds attributes are optional, but using 0 when they're not available
223  // does the right thing.
224  gfx::Rect initial_bounds;
225  if (details.width)
226    initial_bounds.set_width(*details.width);
227  if (details.height)
228    initial_bounds.set_height(*details.height);
229  if (details.left)
230    initial_bounds.set_x(*details.left);
231  if (details.top)
232    initial_bounds.set_y(*details.top);
233
234  AddRef();  // Balanced in OnAuthFlowSuccess/Failure.
235
236  Browser* current_browser = this->GetCurrentBrowser();
237  chrome::HostDesktopType host_desktop_type = current_browser ?
238      current_browser->host_desktop_type() : chrome::GetActiveDesktop();
239  auth_flow_.reset(new WebAuthFlow(
240      this, profile(), GetExtension()->id(), auth_url, mode, initial_bounds,
241      host_desktop_type));
242  auth_flow_->Start();
243  return true;
244}
245
246void IdentityLaunchWebAuthFlowFunction::OnAuthFlowSuccess(
247    const std::string& redirect_url) {
248  SetResult(Value::CreateStringValue(redirect_url));
249  SendResponse(true);
250  Release();  // Balanced in RunImpl.
251}
252
253void IdentityLaunchWebAuthFlowFunction::OnAuthFlowFailure() {
254  error_ = identity_constants::kInvalidRedirect;
255  SendResponse(false);
256  Release();  // Balanced in RunImpl.
257}
258
259IdentityAPI::IdentityAPI(Profile* profile) {
260  (new OAuth2ManifestHandler)->Register();
261}
262
263IdentityAPI::~IdentityAPI() {
264}
265
266static base::LazyInstance<ProfileKeyedAPIFactory<IdentityAPI> >
267    g_factory = LAZY_INSTANCE_INITIALIZER;
268
269// static
270ProfileKeyedAPIFactory<IdentityAPI>* IdentityAPI::GetFactoryInstance() {
271  return &g_factory.Get();
272}
273
274}  // namespace extensions
275