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