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.
5 #include "google_apis/gaia/fake_gaia.h"
9 #include "base/base_paths.h"
10 #include "base/file_util.h"
11 #include "base/files/file_path.h"
12 #include "base/json/json_writer.h"
13 #include "base/logging.h"
14 #include "base/path_service.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/string_split.h"
17 #include "base/strings/string_util.h"
18 #include "base/values.h"
19 #include "google_apis/gaia/gaia_urls.h"
20 #include "net/base/url_util.h"
21 #include "net/http/http_status_code.h"
22 #include "net/test/embedded_test_server/http_request.h"
23 #include "net/test/embedded_test_server/http_response.h"
24 #include "url/url_parse.h"
26 using namespace net::test_server
;
29 const base::FilePath::CharType kServiceLogin
[] =
30 FILE_PATH_LITERAL("google_apis/test/service_login.html");
33 FakeGaia::AccessTokenInfo::AccessTokenInfo()
36 FakeGaia::AccessTokenInfo::~AccessTokenInfo() {}
38 FakeGaia::FakeGaia() {
39 base::FilePath source_root_dir
;
40 PathService::Get(base::DIR_SOURCE_ROOT
, &source_root_dir
);
41 CHECK(base::ReadFileToString(
42 source_root_dir
.Append(base::FilePath(kServiceLogin
)),
43 &service_login_response_
));
46 FakeGaia::~FakeGaia() {}
48 scoped_ptr
<HttpResponse
> FakeGaia::HandleRequest(const HttpRequest
& request
) {
49 GaiaUrls
* gaia_urls
= GaiaUrls::GetInstance();
51 // The scheme and host of the URL is actually not important but required to
52 // get a valid GURL in order to parse |request.relative_url|.
53 GURL request_url
= GURL("http://localhost").Resolve(request
.relative_url
);
54 std::string request_path
= request_url
.path();
56 scoped_ptr
<BasicHttpResponse
> http_response(new BasicHttpResponse());
57 if (request_path
== gaia_urls
->service_login_url().path()) {
58 http_response
->set_code(net::HTTP_OK
);
59 http_response
->set_content(service_login_response_
);
60 http_response
->set_content_type("text/html");
61 } else if (request_path
== gaia_urls
->service_login_auth_url().path()) {
62 std::string continue_url
= gaia_urls
->service_login_url().spec();
63 GetQueryParameter(request
.content
, "continue", &continue_url
);
64 std::string redirect_url
= continue_url
;
67 if (GetQueryParameter(request
.content
, "Email", &email
) &&
68 saml_account_idp_map_
.find(email
) != saml_account_idp_map_
.end()) {
69 GURL
url(saml_account_idp_map_
[email
]);
70 url
= net::AppendQueryParameter(url
, "SAMLRequest", "fake_request");
71 url
= net::AppendQueryParameter(url
, "RelayState", continue_url
);
72 redirect_url
= url
.spec();
75 http_response
->set_code(net::HTTP_TEMPORARY_REDIRECT
);
76 http_response
->AddCustomHeader("Location", redirect_url
);
77 } else if (request_path
== gaia_urls
->oauth2_token_url().path()) {
78 std::string refresh_token
;
79 std::string client_id
;
81 const AccessTokenInfo
* token_info
= NULL
;
82 GetQueryParameter(request
.content
, "scope", &scope
);
83 if (GetQueryParameter(request
.content
, "refresh_token", &refresh_token
) &&
84 GetQueryParameter(request
.content
, "client_id", &client_id
) &&
85 (token_info
= GetAccessTokenInfo(refresh_token
, client_id
, scope
))) {
86 base::DictionaryValue response_dict
;
87 response_dict
.SetString("access_token", token_info
->token
);
88 response_dict
.SetInteger("expires_in", 3600);
89 FormatJSONResponse(response_dict
, http_response
.get());
91 http_response
->set_code(net::HTTP_BAD_REQUEST
);
93 } else if (request_path
== gaia_urls
->oauth2_token_info_url().path()) {
94 const AccessTokenInfo
* token_info
= NULL
;
95 std::string access_token
;
96 if (GetQueryParameter(request
.content
, "access_token", &access_token
)) {
97 for (AccessTokenInfoMap::const_iterator
entry(
98 access_token_info_map_
.begin());
99 entry
!= access_token_info_map_
.end();
101 if (entry
->second
.token
== access_token
) {
102 token_info
= &(entry
->second
);
109 base::DictionaryValue response_dict
;
110 response_dict
.SetString("issued_to", token_info
->issued_to
);
111 response_dict
.SetString("audience", token_info
->audience
);
112 response_dict
.SetString("user_id", token_info
->user_id
);
113 std::vector
<std::string
> scope_vector(token_info
->scopes
.begin(),
114 token_info
->scopes
.end());
115 response_dict
.SetString("scope", JoinString(scope_vector
, " "));
116 response_dict
.SetInteger("expires_in", token_info
->expires_in
);
117 response_dict
.SetString("email", token_info
->email
);
118 FormatJSONResponse(response_dict
, http_response
.get());
120 http_response
->set_code(net::HTTP_BAD_REQUEST
);
122 } else if (request_path
== gaia_urls
->oauth2_issue_token_url().path()) {
123 std::string access_token
;
124 std::map
<std::string
, std::string
>::const_iterator auth_header_entry
=
125 request
.headers
.find("Authorization");
126 if (auth_header_entry
!= request
.headers
.end()) {
127 if (StartsWithASCII(auth_header_entry
->second
, "Bearer ", true))
128 access_token
= auth_header_entry
->second
.substr(7);
132 std::string client_id
;
133 const AccessTokenInfo
* token_info
= NULL
;
134 if (GetQueryParameter(request
.content
, "scope", &scope
) &&
135 GetQueryParameter(request
.content
, "client_id", &client_id
) &&
136 (token_info
= GetAccessTokenInfo(access_token
, client_id
, scope
))) {
137 base::DictionaryValue response_dict
;
138 response_dict
.SetString("issueAdvice", "auto");
139 response_dict
.SetString("expiresIn",
140 base::IntToString(token_info
->expires_in
));
141 response_dict
.SetString("token", token_info
->token
);
142 FormatJSONResponse(response_dict
, http_response
.get());
144 http_response
->set_code(net::HTTP_BAD_REQUEST
);
146 } else if (request_path
== "/SSO") {
147 std::string relay_state
;
148 GetQueryParameter(request
.content
, "RelayState", &relay_state
);
149 std::string redirect_url
= relay_state
;
150 http_response
->set_code(net::HTTP_TEMPORARY_REDIRECT
);
151 http_response
->AddCustomHeader("Location", redirect_url
);
153 // Request not understood.
154 return scoped_ptr
<HttpResponse
>();
157 return http_response
.PassAs
<HttpResponse
>();
160 void FakeGaia::IssueOAuthToken(const std::string
& auth_token
,
161 const AccessTokenInfo
& token_info
) {
162 access_token_info_map_
.insert(std::make_pair(auth_token
, token_info
));
165 void FakeGaia::RegisterSamlUser(const std::string
& account_id
,
166 const GURL
& saml_idp
) {
167 saml_account_idp_map_
[account_id
] = saml_idp
;
171 bool FakeGaia::GetQueryParameter(const std::string
& query
,
172 const std::string
& key
,
173 std::string
* value
) {
174 // Name and scheme actually don't matter, but are required to get a valid URL
176 GURL
query_url("http://localhost?" + query
);
177 return net::GetValueForKeyInQuery(query_url
, key
, value
);
180 void FakeGaia::FormatJSONResponse(const base::DictionaryValue
& response_dict
,
181 BasicHttpResponse
* http_response
) {
182 std::string response_json
;
183 base::JSONWriter::Write(&response_dict
, &response_json
);
184 http_response
->set_content(response_json
);
185 http_response
->set_code(net::HTTP_OK
);
188 const FakeGaia::AccessTokenInfo
* FakeGaia::GetAccessTokenInfo(
189 const std::string
& auth_token
,
190 const std::string
& client_id
,
191 const std::string
& scope_string
) const {
192 if (auth_token
.empty() || client_id
.empty())
195 std::vector
<std::string
> scope_list
;
196 base::SplitString(scope_string
, ' ', &scope_list
);
197 ScopeSet
scopes(scope_list
.begin(), scope_list
.end());
199 for (AccessTokenInfoMap::const_iterator
entry(
200 access_token_info_map_
.lower_bound(auth_token
));
201 entry
!= access_token_info_map_
.upper_bound(auth_token
);
203 if (entry
->second
.audience
== client_id
&&
204 (scope_string
.empty() || entry
->second
.scopes
== scopes
)) {
205 return &(entry
->second
);