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/google_apis/auth_service.h"
6
7#include <string>
8#include <vector>
9
10#include "base/bind.h"
11#include "base/location.h"
12#include "base/message_loop/message_loop_proxy.h"
13#include "base/metrics/histogram.h"
14#include "chrome/browser/google_apis/auth_service_observer.h"
15#include "google_apis/gaia/google_service_auth_error.h"
16
17namespace google_apis {
18
19namespace {
20
21// Used for success ratio histograms. 0 for failure, 1 for success,
22// 2 for no connection (likely offline).
23const int kSuccessRatioHistogramFailure = 0;
24const int kSuccessRatioHistogramSuccess = 1;
25const int kSuccessRatioHistogramNoConnection = 2;
26const int kSuccessRatioHistogramTemporaryFailure = 3;
27const int kSuccessRatioHistogramMaxValue = 4;  // The max value is exclusive.
28
29// OAuth2 authorization token retrieval request.
30class AuthRequest : public OAuth2TokenService::Consumer {
31 public:
32  AuthRequest(OAuth2TokenService* oauth2_token_service,
33              net::URLRequestContextGetter* url_request_context_getter,
34              const AuthStatusCallback& callback,
35              const std::vector<std::string>& scopes);
36  virtual ~AuthRequest();
37
38 private:
39  // Overridden from OAuth2TokenService::Consumer:
40  virtual void OnGetTokenSuccess(const OAuth2TokenService::Request* request,
41                                 const std::string& access_token,
42                                 const base::Time& expiration_time) OVERRIDE;
43  virtual void OnGetTokenFailure(const OAuth2TokenService::Request* request,
44                                 const GoogleServiceAuthError& error) OVERRIDE;
45
46  AuthStatusCallback callback_;
47  scoped_ptr<OAuth2TokenService::Request> request_;
48  base::ThreadChecker thread_checker_;
49
50  DISALLOW_COPY_AND_ASSIGN(AuthRequest);
51};
52
53AuthRequest::AuthRequest(
54    OAuth2TokenService* oauth2_token_service,
55    net::URLRequestContextGetter* url_request_context_getter,
56    const AuthStatusCallback& callback,
57    const std::vector<std::string>& scopes)
58    : callback_(callback) {
59  DCHECK(!callback_.is_null());
60  request_ = oauth2_token_service->
61      StartRequestWithContext(
62          url_request_context_getter,
63          OAuth2TokenService::ScopeSet(scopes.begin(), scopes.end()),
64          this);
65}
66
67AuthRequest::~AuthRequest() {}
68
69// Callback for OAuth2AccessTokenFetcher on success. |access_token| is the token
70// used to start fetching user data.
71void AuthRequest::OnGetTokenSuccess(const OAuth2TokenService::Request* request,
72                                    const std::string& access_token,
73                                    const base::Time& expiration_time) {
74  DCHECK(thread_checker_.CalledOnValidThread());
75
76  UMA_HISTOGRAM_ENUMERATION("GData.AuthSuccess",
77                            kSuccessRatioHistogramSuccess,
78                            kSuccessRatioHistogramMaxValue);
79
80  callback_.Run(HTTP_SUCCESS, access_token);
81  delete this;
82}
83
84// Callback for OAuth2AccessTokenFetcher on failure.
85void AuthRequest::OnGetTokenFailure(const OAuth2TokenService::Request* request,
86                                    const GoogleServiceAuthError& error) {
87  DCHECK(thread_checker_.CalledOnValidThread());
88
89  LOG(WARNING) << "AuthRequest: token request using refresh token failed: "
90               << error.ToString();
91
92  // There are many ways to fail, but if the failure is due to connection,
93  // it's likely that the device is off-line. We treat the error differently
94  // so that the file manager works while off-line.
95  if (error.state() == GoogleServiceAuthError::CONNECTION_FAILED) {
96    UMA_HISTOGRAM_ENUMERATION("GData.AuthSuccess",
97                              kSuccessRatioHistogramNoConnection,
98                              kSuccessRatioHistogramMaxValue);
99    callback_.Run(GDATA_NO_CONNECTION, std::string());
100  } else if (error.state() == GoogleServiceAuthError::SERVICE_UNAVAILABLE) {
101    // Temporary auth error.
102    UMA_HISTOGRAM_ENUMERATION("GData.AuthSuccess",
103                              kSuccessRatioHistogramTemporaryFailure,
104                              kSuccessRatioHistogramMaxValue);
105    callback_.Run(HTTP_FORBIDDEN, std::string());
106  } else {
107    // Permanent auth error.
108    UMA_HISTOGRAM_ENUMERATION("GData.AuthSuccess",
109                              kSuccessRatioHistogramFailure,
110                              kSuccessRatioHistogramMaxValue);
111    callback_.Run(HTTP_UNAUTHORIZED, std::string());
112  }
113  delete this;
114}
115
116}  // namespace
117
118AuthService::AuthService(
119    OAuth2TokenService* oauth2_token_service,
120    net::URLRequestContextGetter* url_request_context_getter,
121    const std::vector<std::string>& scopes)
122    : oauth2_token_service_(oauth2_token_service),
123      url_request_context_getter_(url_request_context_getter),
124      scopes_(scopes),
125      weak_ptr_factory_(this) {
126  DCHECK(oauth2_token_service);
127
128  // Get OAuth2 refresh token (if we have any) and register for its updates.
129  oauth2_token_service_->AddObserver(this);
130  has_refresh_token_ = oauth2_token_service_->RefreshTokenIsAvailable();
131}
132
133AuthService::~AuthService() {
134  oauth2_token_service_->RemoveObserver(this);
135}
136
137void AuthService::StartAuthentication(const AuthStatusCallback& callback) {
138  DCHECK(thread_checker_.CalledOnValidThread());
139  scoped_refptr<base::MessageLoopProxy> relay_proxy(
140      base::MessageLoopProxy::current());
141
142  if (HasAccessToken()) {
143    // We already have access token. Give it back to the caller asynchronously.
144    relay_proxy->PostTask(FROM_HERE,
145                          base::Bind(callback, HTTP_SUCCESS, access_token_));
146  } else if (HasRefreshToken()) {
147    // We have refresh token, let's get an access token.
148    new AuthRequest(oauth2_token_service_,
149                    url_request_context_getter_,
150                    base::Bind(&AuthService::OnAuthCompleted,
151                               weak_ptr_factory_.GetWeakPtr(),
152                               callback),
153                    scopes_);
154  } else {
155    relay_proxy->PostTask(FROM_HERE,
156                          base::Bind(callback, GDATA_NOT_READY, std::string()));
157  }
158}
159
160bool AuthService::HasAccessToken() const {
161  return !access_token_.empty();
162}
163
164bool AuthService::HasRefreshToken() const {
165  return has_refresh_token_;
166}
167
168const std::string& AuthService::access_token() const {
169  return access_token_;
170}
171
172void AuthService::ClearAccessToken() {
173  access_token_.clear();
174}
175
176void AuthService::ClearRefreshToken() {
177  has_refresh_token_ = false;
178
179  FOR_EACH_OBSERVER(AuthServiceObserver,
180                    observers_,
181                    OnOAuth2RefreshTokenChanged());
182}
183
184void AuthService::OnAuthCompleted(const AuthStatusCallback& callback,
185                                  GDataErrorCode error,
186                                  const std::string& access_token) {
187  DCHECK(thread_checker_.CalledOnValidThread());
188  DCHECK(!callback.is_null());
189
190  if (error == HTTP_SUCCESS) {
191    access_token_ = access_token;
192  } else if (error == HTTP_UNAUTHORIZED) {
193    // Refreshing access token using the refresh token is failed with 401 error
194    // (HTTP_UNAUTHORIZED). This means the current refresh token is invalid for
195    // Drive, hence we clear the refresh token here to make HasRefreshToken()
196    // false, thus the invalidness is clearly observable.
197    // This is not for triggering refetch of the refresh token. UI should
198    // show some message to encourage user to log-off and log-in again in order
199    // to fetch new valid refresh token.
200    ClearRefreshToken();
201  }
202
203  // TODO(zelidrag): Add retry, back-off logic when things go wrong here.
204  callback.Run(error, access_token);
205}
206
207void AuthService::AddObserver(AuthServiceObserver* observer) {
208  observers_.AddObserver(observer);
209}
210
211void AuthService::RemoveObserver(AuthServiceObserver* observer) {
212  observers_.RemoveObserver(observer);
213}
214
215void AuthService::OnRefreshTokenAvailable(const std::string& account_id) {
216  OnHandleRefreshToken(true);
217}
218
219void AuthService::OnRefreshTokenRevoked(const std::string& account_id,
220                                        const GoogleServiceAuthError& error) {
221  OnHandleRefreshToken(false);
222}
223
224void AuthService::OnHandleRefreshToken(bool has_refresh_token) {
225  access_token_.clear();
226  has_refresh_token_ = has_refresh_token;
227
228  FOR_EACH_OBSERVER(AuthServiceObserver,
229                    observers_,
230                    OnOAuth2RefreshTokenChanged());
231}
232
233}  // namespace google_apis
234