| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "remoting/host/chromeos/remote_support_host_ash.h" |
| |
| #include <stddef.h> |
| #include <utility> |
| |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/sequence_checker.h" |
| #include "base/strings/stringize_macros.h" |
| #include "remoting/host/chromeos/browser_interop.h" |
| #include "remoting/host/chromeos/chromeos_enterprise_params.h" |
| #include "remoting/host/chromeos/features.h" |
| #include "remoting/host/chromeos/session_storage.h" |
| #include "remoting/host/chromoting_host_context.h" |
| #include "remoting/host/it2me/it2me_constants.h" |
| #include "remoting/host/it2me/it2me_host.h" |
| #include "remoting/host/it2me/it2me_native_messaging_host_ash.h" |
| #include "remoting/host/it2me/reconnect_params.h" |
| #include "remoting/host/policy_watcher.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| namespace remoting { |
| |
| namespace { |
| |
| using remoting::features::kEnableCrdAdminRemoteAccessV2; |
| |
| base::Value::Dict EnterpriseParamsToDict( |
| const ChromeOsEnterpriseParams& params) { |
| return base::Value::Dict() |
| .Set(kSuppressUserDialogs, params.suppress_user_dialogs) |
| .Set(kSuppressNotifications, params.suppress_notifications) |
| .Set(kTerminateUponInput, params.terminate_upon_input) |
| .Set(kCurtainLocalUserSession, params.curtain_local_user_session) |
| .Set(kShowTroubleshootingTools, params.show_troubleshooting_tools) |
| .Set(kAllowTroubleshootingTools, params.allow_troubleshooting_tools) |
| .Set(kAllowReconnections, params.allow_reconnections) |
| .Set(kAllowFileTransfer, params.allow_file_transfer); |
| } |
| |
| ChromeOsEnterpriseParams EnterpriseParamsFromDict( |
| const base::Value::Dict& dict) { |
| return ChromeOsEnterpriseParams{ |
| .suppress_user_dialogs = |
| dict.FindBool(kSuppressUserDialogs).value_or(false), |
| .suppress_notifications = |
| dict.FindBool(kSuppressNotifications).value_or(false), |
| .terminate_upon_input = |
| dict.FindBool(kTerminateUponInput).value_or(false), |
| .curtain_local_user_session = |
| dict.FindBool(kCurtainLocalUserSession).value_or(false), |
| .show_troubleshooting_tools = |
| dict.FindBool(kShowTroubleshootingTools).value_or(false), |
| .allow_troubleshooting_tools = |
| dict.FindBool(kAllowTroubleshootingTools).value_or(false), |
| .allow_reconnections = dict.FindBool(kAllowReconnections).value_or(false), |
| .allow_file_transfer = dict.FindBool(kAllowFileTransfer).value_or(false), |
| }; |
| } |
| |
| base::Value::Dict SessionParamsToDict( |
| const mojom::SupportSessionParams& params) { |
| auto session_params = base::Value::Dict() |
| .Set(kUserName, params.user_name) |
| .Set(kAuthorizedHelper, *params.authorized_helper); |
| |
| return session_params; |
| } |
| |
| mojom::SupportSessionParams SessionParamsFromDict( |
| const base::Value::Dict& dict) { |
| mojom::SupportSessionParams result; |
| const std::string* user_name = dict.FindString(kUserName); |
| if (user_name) { |
| result.user_name = *user_name; |
| } else { |
| LOG(ERROR) << "SupportSessionParams missing field: " << kUserName; |
| } |
| |
| const std::string* authorized_helper = dict.FindString(kAuthorizedHelper); |
| if (authorized_helper) { |
| result.authorized_helper = *authorized_helper; |
| } else { |
| LOG(ERROR) << "SupportSessionParams missing field: " << kAuthorizedHelper; |
| } |
| |
| return result; |
| } |
| |
| mojom::StartSupportSessionResponsePtr GetUnableToReconnectError() { |
| // TODO(joedow): Add better error messages. |
| return mojom::StartSupportSessionResponse::NewSupportSessionError( |
| mojom::StartSupportSessionError::kUnknown); |
| } |
| |
| } // namespace |
| |
| RemoteSupportHostAsh::RemoteSupportHostAsh(base::OnceClosure cleanup_callback, |
| SessionStorage& session_storage) |
| : RemoteSupportHostAsh(std::make_unique<It2MeHostFactory>(), |
| base::MakeRefCounted<BrowserInterop>(), |
| session_storage, |
| std::move(cleanup_callback)) {} |
| |
| RemoteSupportHostAsh::RemoteSupportHostAsh( |
| std::unique_ptr<It2MeHostFactory> host_factory, |
| scoped_refptr<BrowserInterop> browser_interop, |
| SessionStorage& session_storage, |
| base::OnceClosure cleanup_callback) |
| : host_factory_(std::move(host_factory)), |
| browser_interop_(browser_interop), |
| session_storage_(session_storage), |
| cleanup_callback_(std::move(cleanup_callback)) {} |
| |
| RemoteSupportHostAsh::~RemoteSupportHostAsh() = default; |
| |
| void RemoteSupportHostAsh::StartSession( |
| const mojom::SupportSessionParams& params, |
| const absl::optional<ChromeOsEnterpriseParams>& enterprise_params, |
| StartSessionCallback callback) { |
| StartSession(params, enterprise_params, absl::nullopt, std::move(callback)); |
| } |
| |
| void RemoteSupportHostAsh::StartSession( |
| const mojom::SupportSessionParams& params, |
| const absl::optional<ChromeOsEnterpriseParams>& enterprise_params, |
| const absl::optional<ReconnectParams>& reconnect_params, |
| StartSessionCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Ensure there is at most one active remote support connection. |
| // Since we are initiating the disconnect, don't run the cleanup callback. |
| if (it2me_native_message_host_ash_) { |
| auto temp = std::move(it2me_native_message_host_ash_); |
| temp->Disconnect(); |
| } |
| |
| it2me_native_message_host_ash_ = |
| std::make_unique<It2MeNativeMessageHostAsh>(host_factory_->Clone()); |
| |
| mojo::PendingReceiver<mojom::SupportHostObserver> pending_receiver = |
| it2me_native_message_host_ash_->Start( |
| browser_interop_->CreateChromotingHostContext(), |
| browser_interop_->CreatePolicyWatcher()); |
| |
| mojom::StartSupportSessionResponsePtr response = |
| mojom::StartSupportSessionResponse::NewObserver( |
| std::move(pending_receiver)); |
| |
| it2me_native_message_host_ash_->Connect( |
| params, enterprise_params, reconnect_params, |
| base::BindOnce(std::move(callback), std::move(response)), |
| base::BindOnce(&RemoteSupportHostAsh::OnHostStateConnected, |
| base::Unretained(this), params, enterprise_params), |
| base::BindOnce(&RemoteSupportHostAsh::OnHostStateDisconnected, |
| base::Unretained(this)), |
| base::BindOnce(&RemoteSupportHostAsh::OnSessionDisconnected, |
| base::Unretained(this))); |
| } |
| |
| void RemoteSupportHostAsh::ReconnectToSession(SessionId session_id, |
| const std::string& access_token, |
| StartSessionCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!base::FeatureList::IsEnabled(kEnableCrdAdminRemoteAccessV2)) { |
| std::move(callback).Run(GetUnableToReconnectError()); |
| return; |
| } |
| |
| if (session_id != kEnterpriseSessionId) { |
| LOG(ERROR) << "CRD: No reconnectable session found with id " << session_id; |
| std::move(callback).Run(GetUnableToReconnectError()); |
| return; |
| } |
| |
| LOG(INFO) << "CRD: Retrieving details for reconnectable session id:" |
| << session_id; |
| session_storage_->RetrieveSession(base::BindOnce( |
| &RemoteSupportHostAsh::OnSessionRetrieved, weak_ptr_factory_.GetWeakPtr(), |
| session_id, access_token, std::move(callback))); |
| } |
| |
| void RemoteSupportHostAsh::OnSessionRetrieved( |
| SessionId session_id, |
| const std::string& access_token, |
| StartSessionCallback callback, |
| absl::optional<base::Value::Dict> session) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!session.has_value()) { |
| LOG(ERROR) << "CRD: No reconnectable session found for id: " << session_id; |
| std::move(callback).Run(GetUnableToReconnectError()); |
| return; |
| } |
| |
| // Remove the stored session information now that we've read it, so we |
| // do not keep it around forever. |
| session_storage_->DeleteSession(base::DoNothing()); |
| |
| auto session_params = |
| SessionParamsFromDict(*session->EnsureDict(kSessionParamsDict)); |
| // DCHECK is added to detect cases where the access_token prefix is still |
| // being used when it shouldn't as this will mess up the store/retrieve cycle. |
| // TODO(b/309958013): Remove this DCHECK after M122. |
| DCHECK(!access_token.starts_with("oauth2:")); |
| session_params.oauth_access_token = access_token; |
| |
| LOG(INFO) << "CRD: Reconnectable session found - starting connection"; |
| StartSession( |
| std::move(session_params), |
| EnterpriseParamsFromDict(*session->EnsureDict(kEnterpriseParamsDict)), |
| ReconnectParams::FromDict(*session->EnsureDict(kReconnectParamsDict)), |
| std::move(callback)); |
| } |
| |
| // static |
| mojom::SupportHostDetailsPtr RemoteSupportHostAsh::GetHostDetails() { |
| return mojom::SupportHostDetails::New( |
| STRINGIZE(VERSION), std::vector<std::string>({kFeatureAccessTokenAuth, |
| kFeatureAuthorizedHelper})); |
| } |
| |
| void RemoteSupportHostAsh::OnHostStateConnected( |
| mojom::SupportSessionParams session_params, |
| absl::optional<ChromeOsEnterpriseParams> enterprise_params, |
| absl::optional<ReconnectParams> reconnect_params) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!base::FeatureList::IsEnabled(kEnableCrdAdminRemoteAccessV2)) { |
| return; |
| } |
| |
| if (reconnect_params.has_value()) { |
| CHECK(enterprise_params.has_value()); |
| CHECK(enterprise_params->allow_reconnections); |
| |
| LOG(INFO) << "CRD: Storing information for reconnectable session"; |
| session_storage_->StoreSession( |
| base::Value::Dict() |
| .Set(kSessionParamsDict, SessionParamsToDict(session_params)) |
| .Set(kEnterpriseParamsDict, |
| EnterpriseParamsToDict(*enterprise_params)) |
| .Set(kReconnectParamsDict, |
| ReconnectParams::ToDict(*reconnect_params)), |
| base::DoNothing()); |
| return; |
| } |
| |
| VLOG(3) << "CRD: Not a reconnectable session"; |
| } |
| |
| void RemoteSupportHostAsh::OnHostStateDisconnected() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Don't allow reconnecting to the session if the client disconnects. |
| LOG(INFO) << "Deleting reconnectable session info after client disconnect"; |
| session_storage_->DeleteSession(base::DoNothing()); |
| return; |
| } |
| |
| void RemoteSupportHostAsh::OnSessionDisconnected() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Don't allow reconnecting to the session if we explicitly disconnect the |
| // session. |
| LOG(INFO) << "Deleting reconnectable session info after host-side disconnect"; |
| session_storage_->DeleteSession(base::DoNothing()); |
| |
| if (it2me_native_message_host_ash_) { |
| // Do not access any instance members after |cleanup_callback_| is run as |
| // this instance will be destroyed by running this. |
| std::move(cleanup_callback_).Run(); |
| } |
| } |
| |
| } // namespace remoting |