Commit 008e0bfa authored by bartfab@chromium.org's avatar bartfab@chromium.org

Use GAIA headers to distinguish between GAIA and SAML IdP cookies

During login, GAIA sends the "Google-Accounts-SAML=Start" and
"Google-Accounts-SAML=End" headers to indicate when a redirect to a SAML
IdP starts and ends. This CL uses these headers to distinguish between
cookies set by GAIA and the IdP:
* Any cookies set between the start and the end of the redirect were
  created by the IdP.
* Any cookies set before the start or after the end of the redirect
  were created by GAIA.

This allows cookies set by the IdP to be copied to the user's session
while ensuring that GAIA cookies are not copied (these need to be
transferred to the session via GAIA's /MergeSession API).

BUG=381123
TEST=Extended browser test; also manual

Review URL: https://codereview.chromium.org/447013003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@288068 0039d316-1c4b-4281-b951-d872f2087c98
parent 0f59f358
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "base/logging.h" #include "base/logging.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h" #include "base/message_loop/message_loop.h"
#include "base/time/time.h"
#include "content/public/browser/browser_context.h" #include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
#include "net/cookies/canonical_cookie.h" #include "net/cookies/canonical_cookie.h"
...@@ -33,15 +34,8 @@ namespace chromeos { ...@@ -33,15 +34,8 @@ namespace chromeos {
namespace { namespace {
// Given a |cookie| set during login, returns true if the cookie may have been const char kSAMLStartCookie[] = "google-accounts-saml-start";
// set by GAIA. While GAIA can set cookies for many different domains, the const char kSAMLEndCookie[] = "google-accounts-saml-end";
// domain names it sets cookies for during Chrome OS login will always contain
// the strings "google" or "youtube".
bool IsGAIACookie(const net::CanonicalCookie& cookie) {
const std::string& domain = cookie.Domain();
return domain.find("google") != std::string::npos ||
domain.find("youtube") != std::string::npos;
}
class ProfileAuthDataTransferer { class ProfileAuthDataTransferer {
public: public:
...@@ -86,6 +80,17 @@ class ProfileAuthDataTransferer { ...@@ -86,6 +80,17 @@ class ProfileAuthDataTransferer {
void OnChannelIDsToTransferRetrieved( void OnChannelIDsToTransferRetrieved(
const net::ChannelIDStore::ChannelIDList& channel_ids_to_transfer); const net::ChannelIDStore::ChannelIDList& channel_ids_to_transfer);
// Given a |cookie| set during login, returns true if the cookie may have been
// set by GAIA. The main criterion is the |cookie|'s creation date. The points
// in time at which redirects from GAIA to SAML IdP and back occur are stored
// in |saml_start_time_| and |saml_end_time_|. If the cookie was set between
// these two times, it was created by the SAML IdP. Otherwise, it was created
// by GAIA.
// As an additional precaution, the cookie's domain is checked. If the domain
// contains "google" or "youtube", the cookie is considered to have been set
// by GAIA as well.
bool IsGAIACookie(const net::CanonicalCookie& cookie);
// If all data to be transferred has been retrieved already, transfer it to // If all data to be transferred has been retrieved already, transfer it to
// |to_context_| and call Finish(). // |to_context_| and call Finish().
void MaybeTransferCookiesAndChannelIDs(); void MaybeTransferCookiesAndChannelIDs();
...@@ -103,6 +108,11 @@ class ProfileAuthDataTransferer { ...@@ -103,6 +108,11 @@ class ProfileAuthDataTransferer {
net::CookieList cookies_to_transfer_; net::CookieList cookies_to_transfer_;
net::ChannelIDStore::ChannelIDList channel_ids_to_transfer_; net::ChannelIDStore::ChannelIDList channel_ids_to_transfer_;
// The time at which a redirect from GAIA to a SAML IdP occurred.
base::Time saml_start_time_;
// The time at which a redirect from a SAML IdP back to GAIA occurred.
base::Time saml_end_time_;
bool first_login_; bool first_login_;
bool waiting_for_auth_cookies_; bool waiting_for_auth_cookies_;
bool waiting_for_channel_ids_; bool waiting_for_channel_ids_;
...@@ -214,6 +224,25 @@ void ProfileAuthDataTransferer::OnCookiesToTransferRetrieved( ...@@ -214,6 +224,25 @@ void ProfileAuthDataTransferer::OnCookiesToTransferRetrieved(
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
waiting_for_auth_cookies_ = false; waiting_for_auth_cookies_ = false;
cookies_to_transfer_ = cookies_to_transfer; cookies_to_transfer_ = cookies_to_transfer;
// Look for cookies indicating the points in time at which redirects from GAIA
// to SAML IdP and back occurred. These cookies are synthesized by
// chrome/browser/resources/gaia_auth/background.js. If the cookies are found,
// their creation times are stored in |saml_start_time_| and
// |cookies_to_transfer_| and the cookies are deleted.
for (net::CookieList::iterator it = cookies_to_transfer_.begin();
it != cookies_to_transfer_.end(); ) {
if (it->Name() == kSAMLStartCookie) {
saml_start_time_ = it->CreationDate();
it = cookies_to_transfer_.erase(it);
} else if (it->Name() == kSAMLEndCookie) {
saml_end_time_ = it->CreationDate();
it = cookies_to_transfer_.erase(it);
} else {
++it;
}
}
MaybeTransferCookiesAndChannelIDs(); MaybeTransferCookiesAndChannelIDs();
} }
...@@ -235,6 +264,19 @@ void ProfileAuthDataTransferer::OnChannelIDsToTransferRetrieved( ...@@ -235,6 +264,19 @@ void ProfileAuthDataTransferer::OnChannelIDsToTransferRetrieved(
MaybeTransferCookiesAndChannelIDs(); MaybeTransferCookiesAndChannelIDs();
} }
bool ProfileAuthDataTransferer::IsGAIACookie(
const net::CanonicalCookie& cookie) {
const base::Time& creation_date = cookie.CreationDate();
if (creation_date < saml_start_time_)
return true;
if (!saml_end_time_.is_null() && creation_date > saml_end_time_)
return true;
const std::string& domain = cookie.Domain();
return domain.find("google") != std::string::npos ||
domain.find("youtube") != std::string::npos;
}
void ProfileAuthDataTransferer::MaybeTransferCookiesAndChannelIDs() { void ProfileAuthDataTransferer::MaybeTransferCookiesAndChannelIDs() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (waiting_for_auth_cookies_ || waiting_for_channel_ids_) if (waiting_for_auth_cookies_ || waiting_for_channel_ids_)
......
...@@ -58,10 +58,10 @@ BackgroundBridgeManager.prototype = { ...@@ -58,10 +58,10 @@ BackgroundBridgeManager.prototype = {
chrome.webRequest.onHeadersReceived.addListener( chrome.webRequest.onHeadersReceived.addListener(
function(details) { function(details) {
if (this.bridges_[details.tabId]) if (this.bridges_[details.tabId])
this.bridges_[details.tabId].onHeadersReceived(details); return this.bridges_[details.tabId].onHeadersReceived(details);
}.bind(this), }.bind(this),
{urls: ['*://*/*'], types: ['sub_frame']}, {urls: ['*://*/*'], types: ['sub_frame']},
['responseHeaders']); ['blocking', 'responseHeaders']);
chrome.webRequest.onCompleted.addListener( chrome.webRequest.onCompleted.addListener(
function(details) { function(details) {
...@@ -260,31 +260,86 @@ BackgroundBridge.prototype = { ...@@ -260,31 +260,86 @@ BackgroundBridge.prototype = {
/** /**
* Handler or webRequest.onHeadersReceived. It reads the authenticated user * Handler or webRequest.onHeadersReceived. It reads the authenticated user
* email from google-accounts-signin-header. * email from google-accounts-signin-header.
* @return {!Object} Modified request headers.
*/ */
onHeadersReceived: function(details) { onHeadersReceived: function(details) {
if (!this.isDesktopFlow_ || var headers = details.responseHeaders;
!this.gaiaUrl_ ||
details.url.lastIndexOf(this.gaiaUrl_) != 0) { if (this.isDesktopFlow_ &&
this.gaiaUrl_ &&
details.url.lastIndexOf(this.gaiaUrl_) == 0) {
// TODO(xiyuan, guohui): CrOS should reuse the logic below for reading the // TODO(xiyuan, guohui): CrOS should reuse the logic below for reading the
// email for SAML users and cut off the /ListAccount call. // email for SAML users and cut off the /ListAccount call.
return; for (var i = 0; headers && i < headers.length; ++i) {
if (headers[i].name.toLowerCase() == 'google-accounts-signin') {
var headerValues = headers[i].value.toLowerCase().split(',');
var signinDetails = {};
headerValues.forEach(function(e) {
var pair = e.split('=');
signinDetails[pair[0].trim()] = pair[1].trim();
});
// Remove "" around.
this.email_ = signinDetails['email'].slice(1, -1);
this.sessionIndex_ = signinDetails['sessionindex'];
break;
}
}
} }
var headers = details.responseHeaders; if (!this.isDesktopFlow_) {
for (var i = 0; headers && i < headers.length; ++i) { // Check whether GAIA headers indicating the start or end of a SAML
if (headers[i].name.toLowerCase() == 'google-accounts-signin') { // redirect are present. If so, synthesize cookies to mark these points.
var headerValues = headers[i].value.toLowerCase().split(','); for (var i = 0; headers && i < headers.length; ++i) {
var signinDetails = {}; if (headers[i].name.toLowerCase() == 'google-accounts-saml') {
headerValues.forEach(function(e) { var action = headers[i].value.toLowerCase();
var pair = e.split('='); if (action == 'start') {
signinDetails[pair[0].trim()] = pair[1].trim(); // GAIA is redirecting to a SAML IdP. Any cookies contained in the
}); // current |headers| were set by GAIA. Any cookies set in future
// Remove "" around. // requests will be coming from the IdP. Append a cookie to the
this.email_ = signinDetails['email'].slice(1, -1); // current |headers| that marks the point at which the redirect
this.sessionIndex_ = signinDetails['sessionindex']; // occurred.
return; headers.push({name: 'Set-Cookie',
value: 'google-accounts-saml-start=now'});
return {responseHeaders: headers};
} else if (action == 'end') {
// The SAML IdP has redirected back to GAIA. Add a cookie that marks
// the point at which the redirect occurred occurred. It is
// important that this cookie be prepended to the current |headers|
// because any cookies contained in the |headers| were already set
// by GAIA, not the IdP. Due to limitations in the webRequest API,
// it is not trivial to prepend a cookie:
//
// The webRequest API only allows for deleting and appending
// headers. To prepend a cookie (C), three steps are needed:
// 1) Delete any headers that set cookies (e.g., A, B).
// 2) Append a header which sets the cookie (C).
// 3) Append the original headers (A, B).
//
// Due to a further limitation of the webRequest API, it is not
// possible to delete a header in step 1) and append an identical
// header in step 3). To work around this, a trailing semicolon is
// added to each header before appending it. Trailing semicolons are
// ignored by Chrome in cookie headers, causing the modified headers
// to actually set the original cookies.
var otherHeaders = [];
var cookies = [{name: 'Set-Cookie',
value: 'google-accounts-saml-end=now'}];
for (var j = 0; j < headers.length; ++j) {
if (headers[j].name.toLowerCase().indexOf('set-cookie') == 0) {
var header = headers[j];
header.value += ';';
cookies.push(header);
} else {
otherHeaders.push(headers[j]);
}
}
return {responseHeaders: otherHeaders.concat(cookies)};
}
}
} }
} }
return {};
}, },
/** /**
......
...@@ -385,6 +385,7 @@ void FakeGaia::HandleServiceLoginAuth(const HttpRequest& request, ...@@ -385,6 +385,7 @@ void FakeGaia::HandleServiceLoginAuth(const HttpRequest& request,
url = net::AppendQueryParameter(url, "SAMLRequest", "fake_request"); url = net::AppendQueryParameter(url, "SAMLRequest", "fake_request");
url = net::AppendQueryParameter(url, "RelayState", continue_url); url = net::AppendQueryParameter(url, "RelayState", continue_url);
redirect_url = url.spec(); redirect_url = url.spec();
http_response->AddCustomHeader("Google-Accounts-SAML", "Start");
} else if (!merge_session_params_.auth_sid_cookie.empty() && } else if (!merge_session_params_.auth_sid_cookie.empty() &&
!merge_session_params_.auth_lsid_cookie.empty()) { !merge_session_params_.auth_lsid_cookie.empty()) {
SetCookies(http_response, SetCookies(http_response,
...@@ -409,6 +410,7 @@ void FakeGaia::HandleSSO(const HttpRequest& request, ...@@ -409,6 +410,7 @@ void FakeGaia::HandleSSO(const HttpRequest& request,
std::string redirect_url = relay_state; std::string redirect_url = relay_state;
http_response->set_code(net::HTTP_TEMPORARY_REDIRECT); http_response->set_code(net::HTTP_TEMPORARY_REDIRECT);
http_response->AddCustomHeader("Location", redirect_url); http_response->AddCustomHeader("Location", redirect_url);
http_response->AddCustomHeader("Google-Accounts-SAML", "End");
} }
void FakeGaia::HandleAuthToken(const HttpRequest& request, void FakeGaia::HandleAuthToken(const HttpRequest& request,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment