Source release 17.1.1
This commit is contained in:
10
factory_upload_tool/ce/README
Normal file
10
factory_upload_tool/ce/README
Normal file
@@ -0,0 +1,10 @@
|
||||
Instructions:
|
||||
1. Write the main function or modify the example main function in example_main.cpp
|
||||
2. To compile the example_main:
|
||||
clang++ *.cpp ../common/src/*.cpp ../../util/src/string_conversions.cpp -ldl -rdynamic -I../../util/include -I../../oemcrypto/include -I../common/include -o example_main.out
|
||||
3. Specify the location of liboemcrypto.so:
|
||||
export LIBOEMCRYPTO_PATH=/path/to/liboemcrypto.so
|
||||
4. Run the main program to extract the BCC and save it to a file.
|
||||
./example_main.out > csr.bin
|
||||
5. Upload the csr.bin with the following command. cred.json is the OAuth 2.0 client credentials obtained via Google cloud platform.
|
||||
python3 upload.py --credentials=cred.json --org-name={your organization name} --json-csr=csr.bin
|
||||
33
factory_upload_tool/ce/example_main.cpp
Normal file
33
factory_upload_tool/ce/example_main.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "log.h"
|
||||
#include "wv_factory_extractor.h"
|
||||
|
||||
int main() {
|
||||
widevine::ClientInfo client_info;
|
||||
client_info.company_name = "";
|
||||
client_info.arch_name = "";
|
||||
client_info.device_name = "";
|
||||
client_info.model_name = "";
|
||||
client_info.product_name = "";
|
||||
client_info.build_info = "";
|
||||
|
||||
auto extractor = widevine::WidevineFactoryExtractor::Create(client_info);
|
||||
if (extractor == nullptr) {
|
||||
LOGE("Failed to create WidevineFactoryExtractor");
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string request;
|
||||
widevine::Status status = extractor->GenerateUploadRequest(request);
|
||||
if (status != widevine::Status::kSuccess) {
|
||||
LOGE("Fail to generate upload request: %d", status);
|
||||
return 2;
|
||||
}
|
||||
std::cout << request << std::endl;
|
||||
return 0;
|
||||
}
|
||||
42
factory_upload_tool/ce/log.cpp
Normal file
42
factory_upload_tool/ce/log.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
//
|
||||
// Log - implemented using stderr.
|
||||
|
||||
#include "log.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace wvutil {
|
||||
|
||||
LogPriority g_cutoff = CDM_LOG_INFO;
|
||||
|
||||
void InitLogging() {}
|
||||
|
||||
void Log(const char* file, const char* function, int line, LogPriority level,
|
||||
const char* fmt, ...) {
|
||||
const char* severities[] = {"ERROR", "WARN", "INFO", "DEBUG", "VERBOSE"};
|
||||
if (level >=
|
||||
static_cast<LogPriority>(sizeof(severities) / sizeof(*severities))) {
|
||||
fprintf(stderr, "[FATAL:%s(%d)] Invalid log priority level: %d\n", file,
|
||||
line, level);
|
||||
return;
|
||||
}
|
||||
if (level > g_cutoff) return;
|
||||
|
||||
fprintf(stderr, "[%s:%s(%d):%s] ", severities[level], file, line, function);
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
putc('\n', stderr);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
} // namespace wvutil
|
||||
87
factory_upload_tool/ce/properties_ce.cpp
Normal file
87
factory_upload_tool/ce/properties_ce.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
#include "properties_ce.h"
|
||||
|
||||
#include "log.h"
|
||||
#include "properties.h"
|
||||
#include "wv_factory_extractor.h"
|
||||
|
||||
// This anonymous namespace is shared between both the widevine namespace and
|
||||
// wvcdm namespace objects below.
|
||||
namespace {
|
||||
widevine::ClientInfo client_info_;
|
||||
|
||||
bool GetValue(const std::string& source, std::string* output) {
|
||||
if (!output) {
|
||||
LOGE("Null output");
|
||||
return false;
|
||||
}
|
||||
*output = source;
|
||||
return source.size() != 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace widevine {
|
||||
// static
|
||||
bool PropertiesCE::SetClientInfo(const ClientInfo& client_info) {
|
||||
if (client_info.product_name.empty() || client_info.company_name.empty() ||
|
||||
client_info.device_name.empty() || client_info.model_name.empty() ||
|
||||
client_info.arch_name.empty() || client_info.build_info.empty()) {
|
||||
return false;
|
||||
}
|
||||
client_info_ = client_info;
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
ClientInfo PropertiesCE::GetClientInfo() { return client_info_; }
|
||||
|
||||
} // namespace widevine
|
||||
|
||||
namespace wvcdm {
|
||||
// static
|
||||
bool Properties::GetCompanyName(std::string* company_name) {
|
||||
return GetValue(client_info_.company_name, company_name);
|
||||
}
|
||||
|
||||
// static
|
||||
bool Properties::GetModelName(std::string* model_name) {
|
||||
return GetValue(client_info_.model_name, model_name);
|
||||
}
|
||||
|
||||
// static
|
||||
bool Properties::GetArchitectureName(std::string* arch_name) {
|
||||
return GetValue(client_info_.arch_name, arch_name);
|
||||
}
|
||||
|
||||
// static
|
||||
bool Properties::GetDeviceName(std::string* device_name) {
|
||||
return GetValue(client_info_.device_name, device_name);
|
||||
}
|
||||
|
||||
// static
|
||||
bool Properties::GetProductName(std::string* product_name) {
|
||||
return GetValue(client_info_.product_name, product_name);
|
||||
}
|
||||
|
||||
// static
|
||||
bool Properties::GetBuildInfo(std::string* build_info) {
|
||||
return GetValue(client_info_.build_info, build_info);
|
||||
}
|
||||
|
||||
// static
|
||||
bool Properties::GetOEMCryptoPath(std::string* path) {
|
||||
if (path == nullptr) return false;
|
||||
// Using an environment variable is useful for testing.
|
||||
const char* env_path = getenv("LIBOEMCRYPTO_PATH");
|
||||
if (env_path) {
|
||||
*path = std::string(env_path);
|
||||
} else {
|
||||
*path = "liboemcrypto.so";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
19
factory_upload_tool/ce/properties_ce.h
Normal file
19
factory_upload_tool/ce/properties_ce.h
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
#ifndef WVCDM_CDM_PROPERTIES_CE_H_
|
||||
#define WVCDM_CDM_PROPERTIES_CE_H_
|
||||
|
||||
#include "wv_factory_extractor.h"
|
||||
|
||||
namespace widevine {
|
||||
|
||||
class PropertiesCE {
|
||||
public:
|
||||
static bool SetClientInfo(const ClientInfo& client_info);
|
||||
static ClientInfo GetClientInfo();
|
||||
};
|
||||
|
||||
} // namespace widevine
|
||||
|
||||
#endif // WVCDM_CDM_PROPERTIES_CE_H_
|
||||
83
factory_upload_tool/ce/wv_factory_extractor.cpp
Normal file
83
factory_upload_tool/ce/wv_factory_extractor.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
|
||||
#include "wv_factory_extractor.h"
|
||||
|
||||
#include "log.h"
|
||||
#include "properties.h"
|
||||
#include "properties_ce.h"
|
||||
#include "string_conversions.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace {
|
||||
std::string StringMapToJson(
|
||||
const std::map<std::string, std::string>& string_map) {
|
||||
std::string json = "{";
|
||||
for (const auto& value_pair : string_map) {
|
||||
json.append("\"" + value_pair.first + "\": " + "\"" + value_pair.second +
|
||||
"\",");
|
||||
}
|
||||
json.resize(json.size() - 1); // Remove the last comma.
|
||||
json.append("}");
|
||||
return json;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<WidevineFactoryExtractor> WidevineFactoryExtractor::Create(
|
||||
const ClientInfo& client_info) {
|
||||
if (!PropertiesCE::SetClientInfo(client_info)) {
|
||||
LOGE("Invalid client info.");
|
||||
return nullptr;
|
||||
}
|
||||
return std::unique_ptr<WidevineFactoryExtractor>(
|
||||
new WidevineFactoryExtractor());
|
||||
}
|
||||
|
||||
Status WidevineFactoryExtractor::GenerateUploadRequest(std::string& request) {
|
||||
if (crypto_interface_ == nullptr) {
|
||||
std::string oemcrypto_path;
|
||||
if (!wvcdm::Properties::GetOEMCryptoPath(&oemcrypto_path)) {
|
||||
LOGE("Failed to get OEMCrypto path.");
|
||||
return kOEMCryptoError;
|
||||
}
|
||||
LOGI("OEMCrypto path is %s", oemcrypto_path.c_str());
|
||||
|
||||
crypto_interface_ = std::make_unique<OEMCryptoInterface>();
|
||||
if (!crypto_interface_->Init(oemcrypto_path)) {
|
||||
LOGE("Failed to initialize OEMCrypto interface.");
|
||||
crypto_interface_.reset(nullptr);
|
||||
return kOEMCryptoError;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> bcc;
|
||||
OEMCryptoResult result = crypto_interface_->GetBcc(bcc);
|
||||
if (result != OEMCrypto_SUCCESS) {
|
||||
LOGE("Failed to get BCC.");
|
||||
return kOEMCryptoError;
|
||||
}
|
||||
|
||||
std::string oemcrypto_build_info;
|
||||
result = crypto_interface_->GetOEMCryptoBuildInfo(oemcrypto_build_info);
|
||||
if (result != OEMCrypto_SUCCESS) {
|
||||
LOGE("Failed to get oemcrypto build info.");
|
||||
return kOEMCryptoError;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> request_map;
|
||||
request_map["company"] = PropertiesCE::GetClientInfo().company_name;
|
||||
request_map["architecture"] = PropertiesCE::GetClientInfo().arch_name;
|
||||
request_map["name"] = PropertiesCE::GetClientInfo().device_name;
|
||||
request_map["model"] = PropertiesCE::GetClientInfo().model_name;
|
||||
request_map["product"] = PropertiesCE::GetClientInfo().product_name;
|
||||
request_map["build_info"] = PropertiesCE::GetClientInfo().build_info;
|
||||
request_map["oemcrypto_build_info"] = oemcrypto_build_info;
|
||||
request_map["bcc"] = wvutil::Base64Encode(bcc);
|
||||
std::string request_json = StringMapToJson(request_map);
|
||||
|
||||
request.assign(request_json.begin(), request_json.end());
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
} // namespace widevine
|
||||
70
factory_upload_tool/ce/wv_factory_extractor.h
Normal file
70
factory_upload_tool/ce/wv_factory_extractor.h
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
|
||||
#ifndef WV_FACTORY_UPLOADER_H_
|
||||
#define WV_FACTORY_UPLOADER_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "WidevineOemcryptoInterface.h"
|
||||
|
||||
namespace widevine {
|
||||
|
||||
enum Status : int32_t {
|
||||
kSuccess = 0,
|
||||
kUnknownError = 1,
|
||||
kOEMCryptoError = 2,
|
||||
};
|
||||
|
||||
// Client information, provided by the application, independent of CDM
|
||||
// instances.
|
||||
// See Cdm::initialize().
|
||||
// These parameters end up as client identification in license requests.
|
||||
// All fields may be used by a license server proxy to drive business logic.
|
||||
// Some fields are required (indicated below), but please fill out as many
|
||||
// as make sense for your application.
|
||||
// No user-identifying information may be put in these fields!
|
||||
struct ClientInfo {
|
||||
// The name of the product or application, e.g. "TurtleTube"
|
||||
// Required.
|
||||
std::string product_name;
|
||||
|
||||
// The name of the company who makes the device, e.g. "Kubrick, Inc."
|
||||
// Required.
|
||||
std::string company_name;
|
||||
|
||||
// The name of the device, e.g. "HAL"
|
||||
std::string device_name;
|
||||
|
||||
// The device model, e.g. "HAL 9000"
|
||||
// Required.
|
||||
std::string model_name;
|
||||
|
||||
// The architecture of the device, e.g. "x86-64"
|
||||
std::string arch_name;
|
||||
|
||||
// Information about the build of the browser, application, or platform into
|
||||
// which the CDM is integrated, e.g. "v2.71828, 2038-01-19-03:14:07"
|
||||
std::string build_info;
|
||||
};
|
||||
|
||||
class WidevineFactoryExtractor {
|
||||
public:
|
||||
~WidevineFactoryExtractor() = default;
|
||||
|
||||
static std::unique_ptr<WidevineFactoryExtractor> Create(
|
||||
const ClientInfo& client_info);
|
||||
|
||||
Status GenerateUploadRequest(std::string& request);
|
||||
|
||||
private:
|
||||
WidevineFactoryExtractor() = default;
|
||||
std::unique_ptr<OEMCryptoInterface> crypto_interface_;
|
||||
};
|
||||
|
||||
} // namespace widevine
|
||||
|
||||
#endif // WV_FACTORY_UPLOADER_H_
|
||||
347
factory_upload_tool/ce/wv_upload_tool.py
Normal file
347
factory_upload_tool/ce/wv_upload_tool.py
Normal file
@@ -0,0 +1,347 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright 2022 Google LLC. All rights reserved.
|
||||
"""Uploader tool for sending device keys to Widevine remote provisioning server.
|
||||
|
||||
This tool consumes an input file containing device info, which includes the
|
||||
device's public key, and batch uploads it to Google. Once uploaded, the device
|
||||
may use Widevine provisioning 4 to request OEM certificates from Google.
|
||||
|
||||
This tool is designed to be used with the widevine factory extraction tool.
|
||||
Therefore, the JSON output from widevine factory extraction tool is the expected
|
||||
input, with one JSON string per line of input.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import http.server
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
import uuid
|
||||
import webbrowser
|
||||
|
||||
DEFAULT_BASE = 'https://widevine.googleapis.com/v1/orgs/'
|
||||
UPLOAD_PATH = '/uniqueDeviceInfo:batchUpload'
|
||||
TOKEN_CACHE_FILE = os.path.join(
|
||||
os.path.expanduser('~'), '.device_info_uploader.token')
|
||||
|
||||
OAUTH_SERVICE_BASE = 'https://accounts.google.com/o/oauth2'
|
||||
OAUTH_AUTHN_URL = OAUTH_SERVICE_BASE + '/auth'
|
||||
OAUTH_TOKEN_URL = OAUTH_SERVICE_BASE + '/token'
|
||||
|
||||
|
||||
class OAuthHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
|
||||
"""HTTP Handler used to accept the oauth response when the user logs in."""
|
||||
|
||||
def do_GET(self): # pylint: disable=invalid-name
|
||||
"""Handles GET, extracting the authorization code in the query params."""
|
||||
print(f'GET path: {self.path}')
|
||||
parsed_path = urllib.parse.urlparse(self.path)
|
||||
params = dict(urllib.parse.parse_qsl(parsed_path.query))
|
||||
if 'error' in params:
|
||||
error = params['error']
|
||||
self.respond(400, error,
|
||||
f'Error received from the OAuth server: {error}.')
|
||||
sys.exit(-1)
|
||||
elif 'code' not in params:
|
||||
self.respond(400, 'ERROR',
|
||||
('Response from OAuth server is missing the authorization '
|
||||
f'code. Full response: "{self.path}"'))
|
||||
sys.exit(-1)
|
||||
else:
|
||||
self.respond(200, 'Success!',
|
||||
'Success! You may close this browser window.')
|
||||
self.server.code = params['code']
|
||||
|
||||
def do_POST(self): # pylint: disable=invalid-name
|
||||
print(f'POST path: {self.path}')
|
||||
|
||||
def respond(self, code, title, message):
|
||||
"""Send a response to the HTTP client.
|
||||
|
||||
Args:
|
||||
code: The HTTP status code to send
|
||||
title: The page title to display
|
||||
message: The message to display to the user on the page
|
||||
"""
|
||||
if code != 200:
|
||||
eprint(message)
|
||||
self.send_response(code)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
self.wfile.write(('<html>'
|
||||
f' <title>{title}</title>'
|
||||
f' <body>'
|
||||
f' <p style="font-size:24px;">{message}</p>'
|
||||
f' </body>'
|
||||
'</html>').encode('utf-8'))
|
||||
|
||||
|
||||
class LocalOAuthReceiver(http.server.HTTPServer):
|
||||
"""HTTP server that will wait for an OAuth authorization code."""
|
||||
|
||||
def __init__(self):
|
||||
super(LocalOAuthReceiver, self).__init__(('127.0.0.1', 0),
|
||||
OAuthHTTPRequestHandler)
|
||||
self.code = None
|
||||
|
||||
def port(self):
|
||||
return self.socket.getsockname()[1]
|
||||
|
||||
def wait_for_code(self):
|
||||
print('Waiting for a response from the Google OAuth service.')
|
||||
print('If you receive an error in your browser, interrupt this script.')
|
||||
self.handle_request()
|
||||
return self.code
|
||||
|
||||
|
||||
def eprint(message):
|
||||
print(message, file=sys.stderr)
|
||||
|
||||
|
||||
def die(message):
|
||||
eprint(message)
|
||||
sys.exit(-1)
|
||||
|
||||
|
||||
def parse_args():
|
||||
"""Parse and return the command line args.
|
||||
|
||||
Returns:
|
||||
An argparse.Namespace object populated with the arguments.
|
||||
"""
|
||||
parser = argparse.ArgumentParser(description='Upload device info')
|
||||
parser.add_argument(
|
||||
'--json-csr',
|
||||
nargs='+',
|
||||
required=True,
|
||||
help='list of files containing JSON output from rkp_factory_extraction_tool'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--credentials', required=True, help='JSON credentials file')
|
||||
|
||||
parser.add_argument(
|
||||
'--endpoint', default=DEFAULT_BASE, help='destination server URL')
|
||||
|
||||
parser.add_argument('--org-name', required=True, help='orgnization name')
|
||||
|
||||
parser.add_argument(
|
||||
'--cache-token',
|
||||
action='store_true',
|
||||
help='Use a locally cached a refresh token')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def parse_json_csrs(filename, batches):
|
||||
"""Parse the given file and insert it into batches.
|
||||
|
||||
If the input is not a valid JSON CSR blob, exit the program.
|
||||
|
||||
Args:
|
||||
filename: The file that contains a JSON-formatted build and CSR
|
||||
batches: Output dict containing a mapping from json dumped device metadata
|
||||
to BCCs.
|
||||
"""
|
||||
line_count = 0
|
||||
for line in open(filename):
|
||||
line_count = line_count + 1
|
||||
try:
|
||||
obj = json.loads(line)
|
||||
except json.JSONDecodeError as e:
|
||||
die(f'{e.msg} {filename}:{line_count}, char {e.pos}')
|
||||
|
||||
try:
|
||||
bcc = {'boot_certificate_chain': obj['bcc']}
|
||||
device_metadata = json.dumps({
|
||||
'company': obj['company'],
|
||||
'architecture': obj['architecture'],
|
||||
'name': obj['name'],
|
||||
'model': obj['model'],
|
||||
'product': obj['product'],
|
||||
'build_info': obj['build_info']
|
||||
})
|
||||
except KeyError as e:
|
||||
die(f'Invalid object at {filename}:{line_count}, missing {e}')
|
||||
|
||||
if device_metadata not in batches:
|
||||
batches[device_metadata] = []
|
||||
batches[device_metadata].append(bcc)
|
||||
|
||||
|
||||
def format_request_body(args, device_metadata, bccs):
|
||||
"""Generate a formatted request buffer for the given build and CSRs."""
|
||||
request = {
|
||||
'parent': 'orgs/' + args.org_name,
|
||||
'request_id': uuid.uuid4().hex,
|
||||
'metadata': json.loads(device_metadata),
|
||||
'device_info': bccs,
|
||||
}
|
||||
return json.dumps(request).encode('utf-8')
|
||||
|
||||
|
||||
def load_refresh_token():
|
||||
if not os.path.exists(TOKEN_CACHE_FILE):
|
||||
return None
|
||||
with open(TOKEN_CACHE_FILE) as f:
|
||||
return f.readline()
|
||||
|
||||
|
||||
def store_refresh_token(refresh_token):
|
||||
with open(TOKEN_CACHE_FILE, 'w') as f:
|
||||
f.write(refresh_token)
|
||||
|
||||
|
||||
def fetch_access_token(creds, cache_token=False, code=None, redirect_uri=None):
|
||||
"""Fetch an oauth2 access token.
|
||||
|
||||
If a code is passed, then it is used to get the token. If code
|
||||
is None, then look for a persisted refresh token and use that to
|
||||
get the access token instead.
|
||||
|
||||
Args:
|
||||
creds: The OAuth client credentials, including client secret and id.
|
||||
cache_token: If True, then the refresh token is cached on disk so that the
|
||||
user does not have to reauthenticate when the script is used again.
|
||||
code: The OAuth authorization code, returned by Google's OAuth service.
|
||||
redirect_uri: If an authorization code is supplied, then the redirect_uri
|
||||
used to fetch the code must be passed here.
|
||||
|
||||
Returns:
|
||||
A base64-encode OAuth access token, suitable for including in a request.
|
||||
"""
|
||||
request = urllib.request.Request(OAUTH_TOKEN_URL)
|
||||
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||
body = 'client_id=' + creds['client_id']
|
||||
body += '&client_secret=' + creds['client_secret']
|
||||
|
||||
if code is not None:
|
||||
if redirect_uri is None:
|
||||
raise ValueError('"code" was supplied, but "redirect_uri" is None')
|
||||
body += '&grant_type=authorization_code'
|
||||
body += '&code=' + code
|
||||
body += '&redirect_uri=' + redirect_uri
|
||||
else:
|
||||
refresh_token = load_refresh_token()
|
||||
if refresh_token is None:
|
||||
return None
|
||||
body += '&grant_type=refresh_token'
|
||||
body += '&refresh_token=' + refresh_token
|
||||
|
||||
try:
|
||||
response = urllib.request.urlopen(request, body.encode('utf-8'))
|
||||
parsed_response = json.load(response)
|
||||
if cache_token:
|
||||
store_refresh_token(parsed_response['refresh_token'])
|
||||
return parsed_response['access_token']
|
||||
except urllib.error.HTTPError as e:
|
||||
# Catch bogus/expired refresh tokens, but bubble up errors when
|
||||
# an authorization code is used.
|
||||
if code is None:
|
||||
return None
|
||||
die(f'Failed to receive access token: {e.code} {e.reason}')
|
||||
|
||||
|
||||
def load_and_validate_creds(credfile):
|
||||
"""Loads the credentials from the given file and validates them.
|
||||
|
||||
Args:
|
||||
credfile: the name of the file containing the client credentials
|
||||
|
||||
Returns:
|
||||
A map containing the credentials for connecting to the APE backend.
|
||||
"""
|
||||
credmap = json.load(open(credfile))
|
||||
|
||||
not_local_app_creds_error = (
|
||||
'ERROR: Invalid credential file.\n'
|
||||
' The given credentials do not appear to be for a locally installed\n'
|
||||
' application. Please navigate to the credentials dashboard and\n'
|
||||
' ensure that the "Type" of your client is "Desktop":\n'
|
||||
' https://console.cloud.google.com/apis/credentials')
|
||||
|
||||
if 'installed' not in credmap:
|
||||
die(not_local_app_creds_error)
|
||||
|
||||
creds = credmap['installed']
|
||||
|
||||
expected_keys = set(['client_id', 'client_secret', 'redirect_uris'])
|
||||
if not expected_keys.issubset(creds.keys()):
|
||||
die(('ERROR: Invalid credential file.\n'
|
||||
' The given credentials do not appear to be valid. Please\n'
|
||||
' re-download the client credentials file from the dashboard:\n'
|
||||
' https://console.cloud.google.com/apis/credentials'))
|
||||
|
||||
if 'http://localhost' not in creds['redirect_uris']:
|
||||
die(not_local_app_creds_error)
|
||||
|
||||
return creds
|
||||
|
||||
|
||||
def authenticate_and_fetch_token(args):
|
||||
"""Authenticate the user and fetch an OAUTH2 access token."""
|
||||
creds = load_and_validate_creds(args.credentials)
|
||||
|
||||
access_type = 'online'
|
||||
if args.cache_token:
|
||||
token = fetch_access_token(creds)
|
||||
if token is not None:
|
||||
return token
|
||||
access_type = 'offline'
|
||||
|
||||
httpd = LocalOAuthReceiver()
|
||||
redirect_uri = f'http://127.0.0.1:{httpd.port()}'
|
||||
url = (
|
||||
OAUTH_AUTHN_URL + '?response_type=code' + '&client_id=' +
|
||||
creds['client_id'] + '&redirect_uri=' + redirect_uri +
|
||||
'&scope=https://www.googleapis.com/auth/widevine/frontend' +
|
||||
'&access_type=' + access_type + '&prompt=select_account')
|
||||
print('Opening your web browser to authenticate...')
|
||||
if not webbrowser.open(url, new=1, autoraise=True):
|
||||
print('Error opening the browser. Please open this link in a browser')
|
||||
print(f'that is running on this same system:\n {url}\n')
|
||||
code = httpd.wait_for_code()
|
||||
return fetch_access_token(creds, args.cache_token, code, redirect_uri)
|
||||
|
||||
|
||||
def upload_batch(args, device_metadata, bccs):
|
||||
"""Batch upload all the CSRs associated build device_metadata.
|
||||
|
||||
Args:
|
||||
args: The parsed command-line arguments
|
||||
device_metadata: The build for which we're uploading CSRs
|
||||
bccs: a list of BCCs to be uploaded for the given build
|
||||
"""
|
||||
print("Uploading {} bcc(s) for build '{}'".format(len(bccs), device_metadata))
|
||||
body = format_request_body(args, device_metadata, bccs)
|
||||
print(body)
|
||||
print(args.endpoint + args.org_name + UPLOAD_PATH)
|
||||
request = urllib.request.Request(args.endpoint + args.org_name + UPLOAD_PATH)
|
||||
request.add_header('Content-Type', 'application/json')
|
||||
request.add_header('X-GFE-SSL', 'yes')
|
||||
request.add_header('Authorization',
|
||||
'Bearer ' + authenticate_and_fetch_token(args))
|
||||
try:
|
||||
response = urllib.request.urlopen(request, body)
|
||||
except urllib.error.HTTPError as e:
|
||||
eprint(f'Error uploading bccs. {e}')
|
||||
for line in e:
|
||||
eprint(line.decode('utf-8').rstrip())
|
||||
sys.exit(1)
|
||||
|
||||
while chunk := response.read(1024):
|
||||
print(chunk.decode('utf-8'))
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
batches = {}
|
||||
for filename in args.json_csr:
|
||||
parse_json_csrs(filename, batches)
|
||||
|
||||
for device_metadata, bccs in batches.items():
|
||||
upload_batch(args, device_metadata, bccs)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user