blob: fd4370040b64d7900b5057fb453faf06c3426e99 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/webui/policy_tool_ui_handler.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task_scheduler/post_task.h"
#include "build/build_config.h"
#include "chrome/browser/profiles/profile.h"
// static
const base::FilePath::CharType PolicyToolUIHandler::kPolicyToolSessionsDir[] =
FILE_PATH_LITERAL("Policy sessions");
// static
const base::FilePath::CharType
PolicyToolUIHandler::kPolicyToolDefaultSessionName[] =
FILE_PATH_LITERAL("policy");
// static
const base::FilePath::CharType
PolicyToolUIHandler::kPolicyToolSessionExtension[] =
FILE_PATH_LITERAL("json");
PolicyToolUIHandler::PolicyToolUIHandler() : callback_weak_ptr_factory_(this) {}
PolicyToolUIHandler::~PolicyToolUIHandler() {}
void PolicyToolUIHandler::RegisterMessages() {
// Set directory for storing sessions.
sessions_dir_ =
Profile::FromWebUI(web_ui())->GetPath().Append(kPolicyToolSessionsDir);
web_ui()->RegisterMessageCallback(
"initialized", base::Bind(&PolicyToolUIHandler::HandleInitializedAdmin,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"loadSession", base::Bind(&PolicyToolUIHandler::HandleLoadSession,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"updateSession", base::Bind(&PolicyToolUIHandler::HandleUpdateSession,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"resetSession", base::Bind(&PolicyToolUIHandler::HandleResetSession,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"deleteSession", base::Bind(&PolicyToolUIHandler::HandleDeleteSession,
base::Unretained(this)));
}
void PolicyToolUIHandler::OnJavascriptDisallowed() {
callback_weak_ptr_factory_.InvalidateWeakPtrs();
}
base::FilePath PolicyToolUIHandler::GetSessionPath(
const base::FilePath::StringType& name) const {
return sessions_dir_.Append(name).AddExtension(kPolicyToolSessionExtension);
}
base::ListValue PolicyToolUIHandler::GetSessionsList() {
base::FilePath::StringType sessions_pattern =
FILE_PATH_LITERAL("*.") +
base::FilePath::StringType(kPolicyToolSessionExtension);
base::FileEnumerator enumerator(sessions_dir_, /*recursive=*/false,
base::FileEnumerator::FILES,
sessions_pattern);
// A vector of session names and their last access times.
using Session = std::pair<base::Time, base::FilePath::StringType>;
std::vector<Session> sessions;
for (base::FilePath name = enumerator.Next(); !name.empty();
name = enumerator.Next()) {
base::File::Info info;
base::GetFileInfo(name, &info);
sessions.push_back(
{info.last_accessed, name.BaseName().RemoveExtension().value()});
}
// Sort the sessions by the the time of last access in decreasing order.
std::sort(sessions.begin(), sessions.end(), std::greater<Session>());
// Convert sessions to the list containing only names.
base::ListValue session_names;
for (const Session& session : sessions)
session_names.GetList().push_back(base::Value(session.second));
return session_names;
}
void PolicyToolUIHandler::SetDefaultSessionName() {
base::ListValue sessions = GetSessionsList();
if (sessions.empty()) {
// If there are no sessions, fallback to the default session name.
session_name_ = kPolicyToolDefaultSessionName;
} else {
session_name_ =
base::FilePath::FromUTF8Unsafe(sessions.GetList()[0].GetString())
.value();
}
}
std::string PolicyToolUIHandler::ReadOrCreateFileCallback() {
// Create sessions directory, if it doesn't exist yet.
// If unable to create a directory, just silently return a dictionary
// indicating that saving was unsuccessful.
// TODO(urusant): add a possibility to disable saving to disk in similar
// cases.
if (!base::CreateDirectory(sessions_dir_))
is_saving_enabled_ = false;
// Initialize session name if it is not initialized yet.
if (session_name_.empty())
SetDefaultSessionName();
const base::FilePath session_path = GetSessionPath(session_name_);
// Check if the file for the current session already exists. If not, create it
// and put an empty dictionary in it.
base::File session_file(session_path, base::File::Flags::FLAG_CREATE |
base::File::Flags::FLAG_WRITE);
// If unable to open the file, just return an empty dictionary.
if (session_file.created()) {
session_file.WriteAtCurrentPos("{}", 2);
return "{}";
}
session_file.Close();
// Check that the file exists by now. If it doesn't, it means that at least
// one of the filesystem operations wasn't successful. In this case, return
// a dictionary indicating that saving was unsuccessful. Potentially this can
// also be the place to disable saving to disk.
if (!PathExists(session_path))
is_saving_enabled_ = false;
// Read file contents.
std::string contents;
base::ReadFileToString(session_path, &contents);
// Touch the file to remember the last session.
base::File::Info info;
base::GetFileInfo(session_path, &info);
base::TouchFile(session_path, base::Time::Now(), info.last_modified);
return contents;
}
void PolicyToolUIHandler::OnFileRead(const std::string& contents) {
// If the saving is disabled, send a message about that to the UI.
if (!is_saving_enabled_) {
CallJavascriptFunction("policy.Page.disableSaving");
return;
}
std::unique_ptr<base::DictionaryValue> value =
base::DictionaryValue::From(base::JSONReader::Read(contents));
// If contents is not a properly formed JSON string, disable editing in the
// UI to prevent the user from accidentally overriding it.
if (!value) {
CallJavascriptFunction("policy.Page.setPolicyValues",
base::DictionaryValue());
CallJavascriptFunction("policy.Page.disableEditing");
} else {
// TODO(urusant): convert the policy values so that the types are
// consistent with actual policy types.
CallJavascriptFunction("policy.Page.setPolicyValues", *value);
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
base::BindOnce(&PolicyToolUIHandler::GetSessionsList,
base::Unretained(this)),
base::BindOnce(&PolicyToolUIHandler::OnSessionsListReceived,
callback_weak_ptr_factory_.GetWeakPtr()));
}
}
void PolicyToolUIHandler::OnSessionsListReceived(base::ListValue list) {
CallJavascriptFunction("policy.Page.setSessionsList", list);
}
void PolicyToolUIHandler::ImportFile() {
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
base::BindOnce(&PolicyToolUIHandler::ReadOrCreateFileCallback,
base::Unretained(this)),
base::BindOnce(&PolicyToolUIHandler::OnFileRead,
callback_weak_ptr_factory_.GetWeakPtr()));
}
void PolicyToolUIHandler::HandleInitializedAdmin(const base::ListValue* args) {
DCHECK_EQ(0U, args->GetSize());
AllowJavascript();
is_saving_enabled_ = true;
SendPolicyNames();
ImportFile();
}
bool PolicyToolUIHandler::IsValidSessionName(
const base::FilePath::StringType& name) const {
// Check if the session name is valid, which means that it doesn't use
// filesystem navigation (e.g. ../ or nested folder).
base::FilePath session_path = GetSessionPath(name);
return !session_path.empty() && session_path.DirName() == sessions_dir_;
}
void PolicyToolUIHandler::HandleLoadSession(const base::ListValue* args) {
DCHECK_EQ(1U, args->GetSize());
base::FilePath::StringType new_session_name =
base::FilePath::FromUTF8Unsafe(args->GetList()[0].GetString()).value();
if (!IsValidSessionName(new_session_name)) {
CallJavascriptFunction("policy.Page.showInvalidSessionNameError");
return;
}
session_name_ = new_session_name;
ImportFile();
}
bool PolicyToolUIHandler::DoUpdateSession(const std::string& contents) {
// Sanity check that contents is not too big. Otherwise, passing it to
// WriteFile will be int overflow.
if (contents.size() > static_cast<size_t>(std::numeric_limits<int>::max()))
return false;
int bytes_written = base::WriteFile(GetSessionPath(session_name_),
contents.c_str(), contents.size());
return bytes_written == static_cast<int>(contents.size());
}
void PolicyToolUIHandler::OnSessionUpdated(bool is_successful) {
if (!is_successful) {
is_saving_enabled_ = false;
CallJavascriptFunction("policy.Page.disableSaving");
}
}
void PolicyToolUIHandler::HandleUpdateSession(const base::ListValue* args) {
DCHECK(is_saving_enabled_);
DCHECK_EQ(1U, args->GetSize());
const base::DictionaryValue* policy_values = nullptr;
args->GetDictionary(0, &policy_values);
DCHECK(policy_values);
std::string converted_values;
base::JSONWriter::Write(*policy_values, &converted_values);
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND},
base::BindOnce(&PolicyToolUIHandler::DoUpdateSession,
base::Unretained(this), converted_values),
base::BindOnce(&PolicyToolUIHandler::OnSessionUpdated,
callback_weak_ptr_factory_.GetWeakPtr()));
}
void PolicyToolUIHandler::HandleResetSession(const base::ListValue* args) {
DCHECK_EQ(0U, args->GetSize());
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND},
base::BindOnce(&PolicyToolUIHandler::DoUpdateSession,
base::Unretained(this), "{}"),
base::BindOnce(&PolicyToolUIHandler::OnSessionUpdated,
callback_weak_ptr_factory_.GetWeakPtr()));
}
void PolicyToolUIHandler::OnSessionDeleted(bool is_successful) {
if (is_successful) {
ImportFile();
}
}
void PolicyToolUIHandler::HandleDeleteSession(const base::ListValue* args) {
DCHECK_EQ(1U, args->GetSize());
base::FilePath::StringType name =
base::FilePath::FromUTF8Unsafe(args->GetList()[0].GetString()).value();
if (!IsValidSessionName(name)) {
return;
}
// Clear the current session name to ensure that we force a reload of the
// active session. This is important in case the user has deleted the current
// active session, in which case a new one is selected.
session_name_.clear();
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
base::BindOnce(&base::DeleteFile, GetSessionPath(name),
/*recursive=*/false),
base::BindOnce(&PolicyToolUIHandler::OnSessionDeleted,
callback_weak_ptr_factory_.GetWeakPtr()));
}