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