1// Copyright (c) 2013 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/gaia_web_auth_flow.h"
6
7#include "base/strings/string_number_conversions.h"
8#include "base/strings/string_split.h"
9#include "base/strings/string_util.h"
10#include "base/strings/stringprintf.h"
11#include "google_apis/gaia/gaia_urls.h"
12#include "net/base/escape.h"
13
14namespace extensions {
15
16GaiaWebAuthFlow::GaiaWebAuthFlow(Delegate* delegate,
17                                 Profile* profile,
18                                 const std::string& extension_id,
19                                 const OAuth2Info& oauth2_info,
20                                 const std::string& locale)
21    : delegate_(delegate),
22      profile_(profile) {
23  const char kOAuth2RedirectPathFormat[] = "/%s#";
24  const char kOAuth2AuthorizeFormat[] =
25      "%s?response_type=token&approval_prompt=force&authuser=0&"
26      "client_id=%s&"
27      "scope=%s&"
28      "origin=chrome-extension://%s/&"
29      "redirect_uri=%s:/%s&"
30      "hl=%s";
31
32  std::vector<std::string> client_id_parts;
33  base::SplitString(oauth2_info.client_id, '.', &client_id_parts);
34  std::reverse(client_id_parts.begin(), client_id_parts.end());
35  redirect_scheme_ = JoinString(client_id_parts, '.');
36
37  redirect_path_prefix_ =
38      base::StringPrintf(kOAuth2RedirectPathFormat, extension_id.c_str());
39
40  auth_url_ = GURL(base::StringPrintf(
41      kOAuth2AuthorizeFormat,
42      GaiaUrls::GetInstance()->oauth2_auth_url().c_str(),
43      oauth2_info.client_id.c_str(),
44      net::EscapeUrlEncodedData(JoinString(oauth2_info.scopes, ' '), true)
45          .c_str(),
46      extension_id.c_str(),
47      redirect_scheme_.c_str(),
48      extension_id.c_str(),
49      locale.c_str()));
50}
51
52GaiaWebAuthFlow::~GaiaWebAuthFlow() {
53  if (web_flow_)
54    web_flow_.release()->DetachDelegateAndDelete();
55}
56
57void GaiaWebAuthFlow::Start() {
58  ubertoken_fetcher_.reset(new UbertokenFetcher(profile_, this));
59  ubertoken_fetcher_->StartFetchingToken();
60}
61
62void GaiaWebAuthFlow::OnUbertokenSuccess(const std::string& token) {
63  const char kMergeSessionQueryFormat[] = "?uberauth=%s&"
64                                          "continue=%s&"
65                                          "source=appsv2";
66
67  std::string merge_query = base::StringPrintf(
68      kMergeSessionQueryFormat,
69      net::EscapeUrlEncodedData(token, true).c_str(),
70      net::EscapeUrlEncodedData(auth_url_.spec(), true).c_str());
71  GURL merge_url(GaiaUrls::GetInstance()->merge_session_url() + merge_query);
72
73  web_flow_ = CreateWebAuthFlow(merge_url);
74  web_flow_->Start();
75}
76
77void GaiaWebAuthFlow::OnUbertokenFailure(const GoogleServiceAuthError& error) {
78  delegate_->OnGaiaFlowFailure(
79      GaiaWebAuthFlow::SERVICE_AUTH_ERROR, error, std::string());
80}
81
82void GaiaWebAuthFlow::OnAuthFlowFailure(WebAuthFlow::Failure failure) {
83  GaiaWebAuthFlow::Failure gaia_failure;
84
85  switch (failure) {
86    case WebAuthFlow::WINDOW_CLOSED:
87      gaia_failure = GaiaWebAuthFlow::WINDOW_CLOSED;
88      break;
89    case WebAuthFlow::LOAD_FAILED:
90      gaia_failure = GaiaWebAuthFlow::LOAD_FAILED;
91      break;
92    default:
93      NOTREACHED() << "Unexpected error from web auth flow: " << failure;
94      gaia_failure = GaiaWebAuthFlow::LOAD_FAILED;
95      break;
96  }
97
98  delegate_->OnGaiaFlowFailure(
99      gaia_failure,
100      GoogleServiceAuthError(GoogleServiceAuthError::NONE),
101      std::string());
102}
103
104void GaiaWebAuthFlow::OnAuthFlowURLChange(const GURL& url) {
105  const char kOAuth2RedirectAccessTokenKey[] = "access_token";
106  const char kOAuth2RedirectErrorKey[] = "error";
107  const char kOAuth2ExpiresInKey[] = "expires_in";
108
109  // The format of the target URL is:
110  //     reversed.oauth.client.id:/extensionid#access_token=TOKEN
111  //
112  // Because there is no double slash, everything after the scheme is
113  // interpreted as a path, including the fragment.
114
115  if (url.scheme() == redirect_scheme_ && !url.has_host() && !url.has_port() &&
116      StartsWithASCII(url.GetContent(), redirect_path_prefix_, true)) {
117    web_flow_.release()->DetachDelegateAndDelete();
118
119    std::string fragment = url.GetContent().substr(
120        redirect_path_prefix_.length(), std::string::npos);
121    std::vector<std::pair<std::string, std::string> > pairs;
122    base::SplitStringIntoKeyValuePairs(fragment, '=', '&', &pairs);
123    std::string access_token;
124    std::string error;
125    std::string expiration;
126
127    for (std::vector<std::pair<std::string, std::string> >::iterator
128             it = pairs.begin();
129         it != pairs.end();
130         ++it) {
131      if (it->first == kOAuth2RedirectAccessTokenKey)
132        access_token = it->second;
133      else if (it->first == kOAuth2RedirectErrorKey)
134        error = it->second;
135      else if (it->first == kOAuth2ExpiresInKey)
136        expiration = it->second;
137    }
138
139    if (access_token.empty() && error.empty()) {
140      delegate_->OnGaiaFlowFailure(
141          GaiaWebAuthFlow::INVALID_REDIRECT,
142          GoogleServiceAuthError(GoogleServiceAuthError::NONE),
143          std::string());
144    } else if (!error.empty()) {
145      delegate_->OnGaiaFlowFailure(
146          GaiaWebAuthFlow::OAUTH_ERROR,
147          GoogleServiceAuthError(GoogleServiceAuthError::NONE),
148          error);
149    } else {
150      delegate_->OnGaiaFlowCompleted(access_token, expiration);
151    }
152  }
153}
154
155void GaiaWebAuthFlow::OnAuthFlowTitleChange(const std::string& title) {
156  // On the final page the title will be "Loading <redirect-url>".
157  // Treat it as though we'd really been redirected to <redirect-url>.
158  const char kRedirectPrefix[] = "Loading ";
159  std::string prefix(kRedirectPrefix);
160
161  if (StartsWithASCII(title, prefix, true)) {
162    GURL url(title.substr(prefix.length(), std::string::npos));
163    if (url.is_valid())
164      OnAuthFlowURLChange(url);
165  }
166}
167
168scoped_ptr<WebAuthFlow> GaiaWebAuthFlow::CreateWebAuthFlow(GURL url) {
169  return scoped_ptr<WebAuthFlow>(new WebAuthFlow(this,
170                                                 profile_,
171                                                 url,
172                                                 WebAuthFlow::INTERACTIVE));
173}
174
175}  // extensions
176