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/service/cloud_print/cloud_print_auth.h"
6
7#include "base/bind.h"
8#include "base/metrics/histogram.h"
9#include "base/strings/string_util.h"
10#include "chrome/common/cloud_print/cloud_print_constants.h"
11#include "chrome/common/cloud_print/cloud_print_helpers.h"
12#include "chrome/service/cloud_print/cloud_print_token_store.h"
13#include "chrome/service/net/service_url_request_context.h"
14#include "chrome/service/service_process.h"
15#include "google_apis/gaia/gaia_urls.h"
16
17namespace cloud_print {
18
19namespace {
20
21enum CloudPrintAuthEvent {
22  AUTH_EVENT_ROBO_CREATE,
23  AUTH_EVENT_ROBO_SUCCEEDED,
24  AUTH_EVENT_ROBO_FAILED,
25  AUTH_EVENT_ROBO_JSON_ERROR,
26  AUTH_EVENT_ROBO_AUTH_ERROR,
27  AUTH_EVENT_AUTH_WITH_TOKEN,
28  AUTH_EVENT_AUTH_WITH_CODE,
29  AUTH_EVENT_TOKEN_RESPONSE,
30  AUTH_EVENT_REFRESH_REQUEST,
31  AUTH_EVENT_REFRESH_RESPONSE,
32  AUTH_EVENT_AUTH_ERROR,
33  AUTH_EVENT_NET_ERROR,
34  AUTH_EVENT_MAX
35};
36
37}  // namespace
38
39CloudPrintAuth::CloudPrintAuth(
40    Client* client,
41    const GURL& cloud_print_server_url,
42    const gaia::OAuthClientInfo& oauth_client_info,
43    const std::string& proxy_id)
44      : client_(client),
45        oauth_client_info_(oauth_client_info),
46        cloud_print_server_url_(cloud_print_server_url),
47        proxy_id_(proxy_id) {
48  DCHECK(client);
49}
50
51void CloudPrintAuth::AuthenticateWithToken(
52    const std::string& cloud_print_token) {
53  VLOG(1) << "CP_AUTH: Authenticating with token";
54
55  UMA_HISTOGRAM_ENUMERATION("CloudPrint.AuthEvent", AUTH_EVENT_ROBO_CREATE,
56                            AUTH_EVENT_MAX);
57
58  client_login_token_ = cloud_print_token;
59
60  // We need to get the credentials of the robot here.
61  GURL get_authcode_url = GetUrlForGetAuthCode(cloud_print_server_url_,
62                                               oauth_client_info_.client_id,
63                                               proxy_id_);
64  request_ = CloudPrintURLFetcher::Create();
65  request_->StartGetRequest(CloudPrintURLFetcher::REQUEST_AUTH_CODE,
66                            get_authcode_url, this,
67                            kCloudPrintAuthMaxRetryCount, std::string());
68}
69
70void CloudPrintAuth::AuthenticateWithRobotToken(
71    const std::string& robot_oauth_refresh_token,
72    const std::string& robot_email) {
73  VLOG(1) << "CP_AUTH: Authenticating with robot token";
74
75  UMA_HISTOGRAM_ENUMERATION("CloudPrint.AuthEvent", AUTH_EVENT_AUTH_WITH_TOKEN,
76                            AUTH_EVENT_MAX);
77
78  robot_email_ = robot_email;
79  refresh_token_ = robot_oauth_refresh_token;
80  RefreshAccessToken();
81}
82
83void CloudPrintAuth::AuthenticateWithRobotAuthCode(
84    const std::string& robot_oauth_auth_code,
85    const std::string& robot_email) {
86  VLOG(1) << "CP_AUTH: Authenticating with robot auth code";
87
88  UMA_HISTOGRAM_ENUMERATION("CloudPrint.AuthEvent", AUTH_EVENT_AUTH_WITH_CODE,
89                            AUTH_EVENT_MAX);
90
91  robot_email_ = robot_email;
92  // Now that we have an auth code we need to get the refresh and access tokens.
93  oauth_client_.reset(new gaia::GaiaOAuthClient(
94      g_service_process->GetServiceURLRequestContextGetter()));
95  oauth_client_->GetTokensFromAuthCode(oauth_client_info_,
96                                       robot_oauth_auth_code,
97                                       kCloudPrintAuthMaxRetryCount,
98                                       this);
99}
100
101void CloudPrintAuth::RefreshAccessToken() {
102  UMA_HISTOGRAM_ENUMERATION("CloudPrint.AuthEvent", AUTH_EVENT_REFRESH_REQUEST,
103                            AUTH_EVENT_MAX);
104  oauth_client_.reset(new gaia::GaiaOAuthClient(
105      g_service_process->GetServiceURLRequestContextGetter()));
106  std::vector<std::string> empty_scope_list;  // (Use scope from refresh token.)
107  oauth_client_->RefreshToken(oauth_client_info_,
108                              refresh_token_,
109                              empty_scope_list,
110                              kCloudPrintAuthMaxRetryCount,
111                              this);
112}
113
114void CloudPrintAuth::OnGetTokensResponse(const std::string& refresh_token,
115                                         const std::string& access_token,
116                                         int expires_in_seconds) {
117  UMA_HISTOGRAM_ENUMERATION("CloudPrint.AuthEvent", AUTH_EVENT_TOKEN_RESPONSE,
118                            AUTH_EVENT_MAX);
119  refresh_token_ = refresh_token;
120  // After saving the refresh token, this is just like having just refreshed
121  // the access token. Just call OnRefreshTokenResponse.
122  OnRefreshTokenResponse(access_token, expires_in_seconds);
123}
124
125void CloudPrintAuth::OnRefreshTokenResponse(const std::string& access_token,
126                                            int expires_in_seconds) {
127  UMA_HISTOGRAM_ENUMERATION("CloudPrint.AuthEvent", AUTH_EVENT_REFRESH_RESPONSE,
128                            AUTH_EVENT_MAX);
129  client_->OnAuthenticationComplete(access_token, refresh_token_,
130                                    robot_email_, user_email_);
131
132  // Schedule a task to refresh the access token again when it is about to
133  // expire.
134  DCHECK(expires_in_seconds > kTokenRefreshGracePeriodSecs);
135  base::TimeDelta refresh_delay = base::TimeDelta::FromSeconds(
136      expires_in_seconds - kTokenRefreshGracePeriodSecs);
137  base::MessageLoop::current()->PostDelayedTask(
138      FROM_HERE,
139      base::Bind(&CloudPrintAuth::RefreshAccessToken, this),
140      refresh_delay);
141}
142
143void CloudPrintAuth::OnOAuthError() {
144  UMA_HISTOGRAM_ENUMERATION("CloudPrint.AuthEvent", AUTH_EVENT_AUTH_ERROR,
145                            AUTH_EVENT_MAX);
146  // Notify client about authentication error.
147  client_->OnInvalidCredentials();
148}
149
150void CloudPrintAuth::OnNetworkError(int response_code) {
151  UMA_HISTOGRAM_ENUMERATION("CloudPrint.AuthEvent", AUTH_EVENT_NET_ERROR,
152                            AUTH_EVENT_MAX);
153  // Since we specify infinite retries on network errors, this should never
154  // be called.
155  NOTREACHED() <<
156      "OnNetworkError invoked when not expected, response code is " <<
157      response_code;
158}
159
160CloudPrintURLFetcher::ResponseAction CloudPrintAuth::HandleJSONData(
161    const net::URLFetcher* source,
162    const GURL& url,
163    base::DictionaryValue* json_data,
164    bool succeeded) {
165  if (!succeeded) {
166    VLOG(1) << "CP_AUTH: Creating robot account failed";
167    UMA_HISTOGRAM_ENUMERATION("CloudPrint.AuthEvent",
168                              AUTH_EVENT_ROBO_FAILED,
169                              AUTH_EVENT_MAX);
170    client_->OnInvalidCredentials();
171    return CloudPrintURLFetcher::STOP_PROCESSING;
172  }
173
174  std::string auth_code;
175  if (!json_data->GetString(kOAuthCodeValue, &auth_code)) {
176    VLOG(1) << "CP_AUTH: Creating robot account returned invalid json response";
177    UMA_HISTOGRAM_ENUMERATION("CloudPrint.AuthEvent",
178                              AUTH_EVENT_ROBO_JSON_ERROR,
179                              AUTH_EVENT_MAX);
180    client_->OnInvalidCredentials();
181    return CloudPrintURLFetcher::STOP_PROCESSING;
182  }
183
184  UMA_HISTOGRAM_ENUMERATION("CloudPrint.AuthEvent",
185                              AUTH_EVENT_ROBO_SUCCEEDED,
186                              AUTH_EVENT_MAX);
187
188  json_data->GetString(kXMPPJidValue, &robot_email_);
189  // Now that we have an auth code we need to get the refresh and access tokens.
190  oauth_client_.reset(new gaia::GaiaOAuthClient(
191      g_service_process->GetServiceURLRequestContextGetter()));
192  oauth_client_->GetTokensFromAuthCode(oauth_client_info_,
193                                       auth_code,
194                                       kCloudPrintAPIMaxRetryCount,
195                                       this);
196
197  return CloudPrintURLFetcher::STOP_PROCESSING;
198}
199
200CloudPrintURLFetcher::ResponseAction CloudPrintAuth::OnRequestAuthError() {
201  VLOG(1) << "CP_AUTH: Creating robot account authentication error";
202
203  UMA_HISTOGRAM_ENUMERATION("CloudPrint.AuthEvent",
204                            AUTH_EVENT_ROBO_AUTH_ERROR,
205                            AUTH_EVENT_MAX);
206
207  // Notify client about authentication error.
208  client_->OnInvalidCredentials();
209  return CloudPrintURLFetcher::STOP_PROCESSING;
210}
211
212std::string CloudPrintAuth::GetAuthHeader() {
213  DCHECK(!client_login_token_.empty());
214  std::string header;
215  header = "Authorization: GoogleLogin auth=";
216  header += client_login_token_;
217  return header;
218}
219
220CloudPrintAuth::~CloudPrintAuth() {}
221
222}  // namespace cloud_print
223