blob: f3ce042db8876f57e2fcf0d121972171410f6db2 [file] [log] [blame]
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/xhr/xhr_modify_headers.h"
#include "base/logging.h"
#include "base/synchronization/waitable_event.h"
#include "starboard/mutex.h"
#include "starboard/once.h"
#include "starboard/shared/uwp/app_accessors.h"
#include "starboard/shared/uwp/async_utils.h"
#include "starboard/shared/win32/wchar_utils.h"
#include "starboard/string.h"
using Windows::Security::Authentication::Web::Core::WebTokenResponse;
using Windows::Security::Authentication::Web::Core::WebTokenRequestResult;
using Windows::Security::Authentication::Web::Core::WebTokenRequestStatus;
using Windows::System::UserAuthenticationStatus;
namespace sbwin32 = starboard::shared::win32;
namespace sbuwp = starboard::shared::uwp;
namespace {
const char kCobaltBootstrapUrl[] =
"https://www.youtube.com/api/xbox/cobalt_bootstrap";
const char kXboxLiveAccountProviderId[] = "https://xsts.auth.xboxlive.com";
// The name of the header to send the STS token out on.
const char kXauthHeaderName[] = "Authorization";
// The prefix for the value of the Authorization header when there is an XAuth
// token to attach to the request. The token itself should directly follow this
// prefix.
const char kXauthHeaderPrefix[] = "XBL3.0 x=";
// The name of the header to detect on requests as a trigger to add an STS token
// for the given RelyingPartyId (the value of the header). The header itself
// will be removed from the outgoing request, and replaced with an Authorization
// header with a valid STS token for the current primary user.
const base::StringPiece kXauthTriggerHeaderName = "X-STS-RelyingPartyId";
class LastTokenCache {
bool has_last_token_ = false;
std::string last_token_;
starboard::Mutex mutex_;
public:
bool MaybeGetLastToken(std::string* out) {
starboard::ScopedLock lock(mutex_);
out->assign(last_token_);
return has_last_token_;
}
void SetLastToken(const std::string& in) {
starboard::ScopedLock lock(mutex_);
has_last_token_ = true;
last_token_ = in;
}
};
SB_ONCE_INITIALIZE_FUNCTION(LastTokenCache, GetLastTokenCache);
bool PopulateToken(const std::string& relying_party, std::string* out) {
out->clear();
DCHECK(!sbuwp::GetDispatcher()->HasThreadAccess)
<< "Must not be called from the UWP main thread";
WebTokenRequestResult^ token_result;
try {
token_result = sbuwp::TryToFetchSsoToken(relying_party).get();
} catch (Platform::Exception^) {
token_result = nullptr;
}
if (!token_result ||
token_result->ResponseStatus != WebTokenRequestStatus::Success ||
token_result->ResponseData->Size != 1) {
// The token is valid for 16 hours, however there appears to be
// no easy way for us to check on the client side when it expires.
// We must use it hourly.
// However, it appears that sometimes asking for a new token will
// fail for unknown reasons.
// Therefore, simply use the last token we had in case it's still good.
// Note that while "relying_party" may change slightly, it
// never changes any of the token's attributes.
return GetLastTokenCache()->MaybeGetLastToken(out);
}
WebTokenResponse^ token_response = token_result->ResponseData->GetAt(0);
*out = sbwin32::platformStringToString(token_response->Token);
GetLastTokenCache()->SetLastToken(
sbwin32::platformStringToString(token_response->Token));
// Always get the token through the cache so the exceptional case
// is less exceptional.
return GetLastTokenCache()->MaybeGetLastToken(out);
}
void AppendUrlPath(const std::string& path, std::string* url_parameter) {
DCHECK(url_parameter);
if (path.empty()) {
return;
}
std::string& url(*url_parameter);
// if path starts with a '/' and url ends with a '/', remove trailing slash
// from url before appending the path
if (!url.empty() && *(url.rbegin()) == '/' && path[0] == '/') {
url.resize(url.size() - 1);
}
url.append(path);
}
bool StringStartsWith(const std::string& str, const char* starts_with) {
size_t starts_with_length = SbStringGetLength(starts_with);
if (str.size() < starts_with_length) {
return false;
}
std::string sub_str = str.substr(0, starts_with_length);
return sub_str == starts_with;
}
} // namespace
namespace cobalt {
namespace loader {
std::string CobaltFetchMaybeAddHeader(const GURL& url) {
std::string out_string;
if (!StringStartsWith(url.spec(), kCobaltBootstrapUrl)) {
return out_string;
}
if (!PopulateToken(url.spec(), &out_string)) {
return out_string;
}
std::string result;
result.append(kXauthHeaderName);
result.append(": ");
result.append(out_string);
return result;
}
} // namespace loader
} // namespace cobalt
namespace cobalt {
namespace xhr {
void CobaltXhrModifyHeader(const GURL& request_url,
net::HttpRequestHeaders* headers) {
DCHECK(headers);
std::string relying_party;
bool trigger_header_found = headers->GetHeader(kXauthTriggerHeaderName,
&relying_party);
if (!trigger_header_found) {
return;
}
if (request_url.has_path()) {
std::string request_url_path = request_url.path();
AppendUrlPath(request_url_path, &relying_party);
}
std::string out_string;
if (!PopulateToken(relying_party, &out_string)) {
return;
}
headers->RemoveHeader(kXauthTriggerHeaderName);
headers->SetHeader(kXauthHeaderName, out_string);
}
} // namespace xhr
} // namespace cobalt