blob: 62450b0a8bfc230cc80921f65f9d0fde939c5bd7 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/arc/policy/managed_configuration_variables.h"
#include <string>
#include <vector>
#include "base/check.h"
#include "base/containers/flat_map.h"
#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/strings/string_piece_forward.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
#include "chrome/browser/ash/policy/core/device_attributes.h"
#include "chrome/browser/ash/policy/core/device_attributes_impl.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chromeos/ash/components/system/statistics_provider.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/user_manager/user.h"
#include "third_party/re2/src/re2/re2.h"
#include "third_party/re2/src/re2/stringpiece.h"
namespace arc {
namespace {
// Part before "@" of the given |email| address.
// "some_email@domain.com" => "some_email"
//
// Returns empty string if |email| does not contain an "@".
std::string EmailName(const std::string& email) {
size_t at_sign_pos = email.find("@");
if (at_sign_pos == std::string::npos)
return "";
return email.substr(0, at_sign_pos);
}
// Part after "@" of an email address.
// "some_email@domain.com" => "domain.com"
//
// Returns empty string if |email| does not contain an "@".
std::string EmailDomain(const std::string& email) {
size_t at_sign_pos = email.find("@");
if (at_sign_pos == std::string::npos)
return "";
return email.substr(at_sign_pos + 1);
}
std::string SignedInUserEmail(const Profile* profile) {
DCHECK(profile);
signin::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfileIfExists(profile);
CoreAccountInfo info =
identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
return info.email;
}
std::string DeviceDirectoryId(policy::DeviceAttributes* device_attributes) {
return device_attributes->GetDirectoryApiID();
}
std::string DeviceAssetId(policy::DeviceAttributes* device_attributes) {
return device_attributes->GetDeviceAssetID();
}
std::string DeviceAnnotatedLocation(
policy::DeviceAttributes* device_attributes) {
return device_attributes->GetDeviceAnnotatedLocation();
}
std::string DeviceSerialNumber() {
return std::string(
ash::system::StatisticsProvider::GetInstance()->GetMachineID().value_or(
""));
}
// Map associating known variables to functions that return the corresponding
// values. For example, "USER_EMAIL" is associated to |SignedInUserEmail|.
typedef base::flat_map<std::string, base::RepeatingCallback<std::string()>>
VariableResolver;
bool IsAffiliatedUser(const Profile* profile) {
const user_manager::User* user =
ash::ProfileHelper::Get()->GetUserByProfile(profile);
return user && user->IsAffiliated();
}
// Build a |VariableResolver| from all known variables.
const VariableResolver BuildVariableResolver(
const Profile* profile,
policy::DeviceAttributes* attributes) {
// Use |empty_string_getter| for device attributes if user is not affiliated.
const bool is_affiliated = IsAffiliatedUser(profile);
const auto empty_string_getter =
base::BindRepeating([]() { return std::string(); });
return VariableResolver{
{kUserEmail,
base::BindRepeating(
[](const Profile* profile) { return SignedInUserEmail(profile); },
profile)},
{kUserEmailName, base::BindRepeating(
[](const Profile* profile) {
return EmailName(SignedInUserEmail(profile));
},
profile)},
{kUserEmailDomain, base::BindRepeating(
[](const Profile* profile) {
return EmailDomain(SignedInUserEmail(profile));
},
profile)},
{kDeviceDirectoryId, is_affiliated
? base::BindRepeating(
[](policy::DeviceAttributes* attributes) {
return DeviceDirectoryId(attributes);
},
attributes)
: empty_string_getter},
{kDeviceSerialNumber, is_affiliated
? base::BindRepeating(&DeviceSerialNumber)
: empty_string_getter},
{kDeviceAssetId, is_affiliated
? base::BindRepeating(
[](policy::DeviceAttributes* attributes) {
return DeviceAssetId(attributes);
},
attributes)
: empty_string_getter},
{kDeviceAnnotatedLocation,
is_affiliated ? base::BindRepeating(
[](policy::DeviceAttributes* attributes) {
return DeviceAnnotatedLocation(attributes);
},
attributes)
: empty_string_getter},
};
}
// Return the value associated to the first item in |variables| that is not
// empty.
std::string ResolveVariableChain(const VariableResolver& resolver,
std::vector<base::StringPiece> variables) {
for (const auto& variable : variables) {
// Variables should always be valid and have a mapping in |resolver|.
DCHECK(resolver.find(variable) != resolver.end());
// Resolve the given variable and return if it has a value.
std::string result = resolver.at(variable).Run();
if (!result.empty())
return result;
}
return "";
}
std::vector<base::StringPiece> SplitByColon(const re2::StringPiece& input) {
return base::SplitStringPiece(base::StringPiece(input.data(), input.size()),
":", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
}
// Return a new string where all captures of |regex| in |search_input| have been
// replaced with the output of |replacement_getter.Run(capture)|.
std::string SearchAndReplace(
const re2::RE2& regex,
base::RepeatingCallback<std::string(const re2::StringPiece&)>
replacement_getter,
re2::StringPiece search_input) {
std::vector<std::string> output;
re2::StringPiece capture;
// Loop as long as |regex| matches |search_input|.
while (re2::RE2::PartialMatch(search_input, regex, &capture)) {
DCHECK(capture.data() != nullptr);
// Output the prefix skipped by PartialMatch until |capture| is found.
DCHECK(capture.begin() >= search_input.begin());
size_t prefix_size = capture.begin() - search_input.begin();
output.emplace_back(search_input.begin(), prefix_size);
// Output the replacement for |capture|.
output.emplace_back(replacement_getter.Run(capture));
// Update |search_input| to the suffix after |capture|.
DCHECK(search_input.length() >= prefix_size + capture.length());
size_t remaining_size =
search_input.length() - (prefix_size + capture.length());
search_input.set(capture.end(), remaining_size);
}
// Output the remaining |search_input|.
output.emplace_back(search_input.data(), search_input.length());
return base::JoinString(output, /*separator=*/"");
}
// Returns a regular expression that matches any one variable in |resolver|.
std::string ResolverKeyMatcher(const VariableResolver& resolver) {
std::vector<base::StringPiece> keys;
for (const auto& item : resolver)
keys.emplace_back(item.first);
return base::JoinString(keys, /*separator=*/"|");
}
// Replace all variable chains in |configuration| in-place using the provided
// |resolver| rules.
//
// A variable chain is separated by ":", e.g. "${DEVICE_ASSET_ID:USER_EMAIL}".
//
// Chains resolve to the first value that is non-empty. In the example above if
// the asset ID is empty, the chain resolves to the email of the current user.
void ReplaceVariables(const VariableResolver& resolver,
std::string* configuration) {
DCHECK(configuration);
// |variable_matcher| matches any of the supported variables in |resolver|.
const std::string variable_matcher = ResolverKeyMatcher(resolver);
// |variable_capture| will match and capture a variable template including a
// variable chain. This regex does not match templates with invalid variables.
const std::string variable_capture =
base::StringPrintf("(\\$\\{(?:%s)(?::(?:%s))*\\})",
variable_matcher.c_str(), variable_matcher.c_str());
const re2::RE2 regex(variable_capture);
DCHECK(regex.ok()) << "Error compiling regex: " << regex.error();
// Callback to compute values of variable chains matched with |regex|.
auto chain_resolver = base::BindRepeating(
[](const VariableResolver& resolver, const re2::StringPiece& variable) {
// Remove the "${" prefix and the "}" suffix from |variable|.
DCHECK(variable.starts_with("${") && variable.ends_with("}"));
const re2::StringPiece chain = variable.substr(2, variable.size() - 3);
const std::vector<base::StringPiece> variables = SplitByColon(chain);
const std::string chain_value =
ResolveVariableChain(resolver, variables);
return chain_value;
},
resolver);
std::string replaced_configuration =
SearchAndReplace(regex, std::move(chain_resolver), *configuration);
*configuration = std::move(replaced_configuration);
}
void RecursivelySearchAndReplaceVariables(const VariableResolver& resolver,
base::Value* managedConfiguration) {
// Recursive call for dictionary values.
if (managedConfiguration->is_dict()) {
for (auto kv : managedConfiguration->DictItems()) {
RecursivelySearchAndReplaceVariables(resolver, &kv.second);
}
return;
}
// Exit early for non string values.
if (!managedConfiguration->is_string())
return;
// Find variable chains and replace them with the corresponding value.
ReplaceVariables(resolver, &managedConfiguration->GetString());
}
} // namespace
const char kUserEmail[] = "USER_EMAIL";
const char kUserEmailName[] = "USER_EMAIL_NAME";
const char kUserEmailDomain[] = "USER_EMAIL_DOMAIN";
const char kDeviceDirectoryId[] = "DEVICE_DIRECTORY_ID";
const char kDeviceSerialNumber[] = "DEVICE_SERIAL_NUMBER";
const char kDeviceAssetId[] = "DEVICE_ASSET_ID";
const char kDeviceAnnotatedLocation[] = "DEVICE_ANNOTATED_LOCATION";
void RecursivelyReplaceManagedConfigurationVariables(
const Profile* profile,
base::Value* managedConfiguration) {
policy::DeviceAttributesImpl device_attributes;
RecursivelyReplaceManagedConfigurationVariables(profile, &device_attributes,
managedConfiguration);
}
void RecursivelyReplaceManagedConfigurationVariables(
const Profile* profile,
policy::DeviceAttributes* device_attributes,
base::Value* managedConfiguration) {
const VariableResolver resolver =
BuildVariableResolver(profile, device_attributes);
RecursivelySearchAndReplaceVariables(resolver, managedConfiguration);
}
} // namespace arc