| // Copyright 2019 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 "remoting/test/it2me_cli_host.h" |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/json/json_reader.h" |
| #include "base/json/json_writer.h" |
| #include "base/run_loop.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "net/base/network_change_notifier.h" |
| #include "remoting/base/auto_thread_task_runner.h" |
| #include "remoting/base/logging.h" |
| #include "remoting/host/chromoting_host_context.h" |
| #include "remoting/host/it2me/it2me_constants.h" |
| #include "remoting/host/it2me/it2me_native_messaging_host.h" |
| #include "remoting/host/policy_watcher.h" |
| #include "remoting/test/test_oauth_token_getter.h" |
| #include "remoting/test/test_token_storage.h" |
| |
| namespace remoting { |
| |
| namespace { |
| |
| constexpr char kCRDDebugLog[] = "_debug_log"; |
| |
| constexpr char kSwitchNameHelp[] = "help"; |
| constexpr char kSwitchNameUsername[] = "username"; |
| constexpr char kSwitchNameStoragePath[] = "storage-path"; |
| |
| std::unique_ptr<It2MeNativeMessagingHost> CreateNativeMessagingHost( |
| scoped_refptr<AutoThreadTaskRunner> ui_task_runner) { |
| auto context = ChromotingHostContext::Create(ui_task_runner); |
| std::unique_ptr<PolicyWatcher> policy_watcher = |
| PolicyWatcher::CreateWithTaskRunner(context->file_task_runner(), |
| context->management_service()); |
| auto factory = std::make_unique<It2MeHostFactory>(); |
| return std::make_unique<It2MeNativeMessagingHost>( |
| /* needs_elevation */ false, std::move(policy_watcher), |
| std::move(context), std::move(factory)); |
| } |
| |
| } // namespace |
| |
| // static |
| bool It2MeCliHost::ShouldPrintHelp() { |
| return base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchNameHelp); |
| } |
| |
| // static |
| void It2MeCliHost::PrintHelp() { |
| fprintf(stderr, |
| "Usage: %s [--storage-path=<storage-path>] " |
| "[--username=<example@gmail.com>]\n ", |
| base::CommandLine::ForCurrentProcess() |
| ->GetProgram() |
| .AsUTF8Unsafe() |
| .c_str()); |
| } |
| |
| It2MeCliHost::It2MeCliHost() {} |
| It2MeCliHost::~It2MeCliHost() = default; |
| |
| void It2MeCliHost::Start() { |
| base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); |
| std::string username = cmd_line->GetSwitchValueASCII(kSwitchNameUsername); |
| base::FilePath storage_path = |
| cmd_line->GetSwitchValuePath(kSwitchNameStoragePath); |
| |
| storage_ = test::TestTokenStorage::OnDisk(username, storage_path); |
| token_getter_ = std::make_unique<test::TestOAuthTokenGetter>(storage_.get()); |
| |
| base::RunLoop initialize_token_getter_loop; |
| token_getter_->Initialize(initialize_token_getter_loop.QuitClosure()); |
| initialize_token_getter_loop.Run(); |
| |
| base::RunLoop ui_loop; |
| ui_task_runner_ = new AutoThreadTaskRunner( |
| base::ThreadTaskRunnerHandle::Get(), ui_loop.QuitClosure()); |
| |
| token_getter_->CallWithToken(base::BindOnce( |
| &It2MeCliHost::StartCRDHostAndGetCode, base::Unretained(this))); |
| |
| std::unique_ptr<net::NetworkChangeNotifier> network_change_notifier( |
| net::NetworkChangeNotifier::CreateIfNeeded()); |
| ui_loop.Run(); |
| } |
| |
| void It2MeCliHost::PostMessageFromNativeHost(const std::string& message) { |
| auto message_value = base::JSONReader::Read(message); |
| if (!message_value || !message_value->is_dict()) { |
| OnProtocolBroken("Message is not a dictionary"); |
| return; |
| } |
| |
| auto* type_value = |
| message_value->FindKeyOfType(kMessageType, base::Value::Type::STRING); |
| if (!type_value) { |
| OnProtocolBroken("Message without type"); |
| return; |
| } |
| std::string type = type_value->GetString(); |
| |
| if (type == kHelloResponse) { |
| OnHelloResponse(); |
| } else if (type == kConnectResponse) { |
| // Ok, just ignore. |
| } else if (type == kDisconnectResponse) { |
| OnDisconnectResponse(); |
| } else if (type == kHostStateChangedMessage) { |
| // Handle CRD host state changes |
| auto* state_value = |
| message_value->FindKeyOfType(kState, base::Value::Type::STRING); |
| if (!state_value) { |
| OnProtocolBroken("No state in message"); |
| return; |
| } |
| std::string state = state_value->GetString(); |
| |
| if (state == kHostStateReceivedAccessCode) { |
| OnStateReceivedAccessCode(*message_value); |
| } else if (state == kHostStateConnected) { |
| OnStateRemoteConnected(*message_value); |
| } else if (state == kHostStateDisconnected) { |
| OnStateRemoteDisconnected(); |
| } else if (state == kHostStateError || state == kHostStateDomainError) { |
| OnStateError(state, *message_value); |
| } else if (state == kHostStateStarting || |
| state == kHostStateRequestedAccessCode) { |
| // Just ignore these states. |
| } else { |
| LOG(WARNING) << "Unhandled state: " << state; |
| } |
| } else if (type == kCRDDebugLog) { |
| // The It2Me host already prints the log to stdout/stderr. |
| } else { |
| LOG(WARNING) << "Unknown message type: " << type; |
| } |
| } |
| |
| void It2MeCliHost::CloseChannel(const std::string& error_message) { |
| LOG(ERROR) << "CRD Host closed channel: " << error_message; |
| command_awaiting_crd_access_code_ = false; |
| |
| ShutdownHost(); |
| } |
| |
| void It2MeCliHost::SendMessageToHost(const std::string& type, |
| base::Value params) { |
| std::string message_json; |
| params.SetKey(kMessageType, base::Value(type)); |
| base::JSONWriter::Write(params, &message_json); |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&It2MeCliHost::DoSendMessage, |
| weak_factory_.GetWeakPtr(), message_json)); |
| } |
| |
| void It2MeCliHost::DoSendMessage(const std::string& json) { |
| if (!host_) |
| return; |
| host_->OnMessage(json); |
| } |
| |
| void It2MeCliHost::OnProtocolBroken(const std::string& message) { |
| LOG(ERROR) << "Error communicating with CRD Host : " << message; |
| command_awaiting_crd_access_code_ = false; |
| |
| ShutdownHost(); |
| } |
| |
| void It2MeCliHost::StartCRDHostAndGetCode(OAuthTokenGetter::Status status, |
| const std::string& user_email, |
| const std::string& access_token) { |
| DCHECK(!host_); |
| |
| // Store all parameters for future connect call. |
| base::Value connect_params(base::Value::Type::DICTIONARY); |
| |
| connect_params.SetKey(kUserName, base::Value(user_email)); |
| connect_params.SetKey(kAuthServiceWithToken, |
| base::Value("oauth2:" + access_token)); |
| connect_params.SetKey(kSuppressUserDialogs, base::Value(true)); |
| connect_params.SetKey(kSuppressNotifications, base::Value(true)); |
| connect_params_ = std::move(connect_params); |
| |
| remote_connected_ = false; |
| command_awaiting_crd_access_code_ = true; |
| |
| host_ = CreateNativeMessagingHost(ui_task_runner_); |
| host_->Start(this); |
| |
| base::Value params(base::Value::Type::DICTIONARY); |
| SendMessageToHost(kHelloMessage, std::move(params)); |
| } |
| |
| void It2MeCliHost::ShutdownHost() { |
| if (!host_) |
| return; |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&It2MeCliHost::DoShutdownHost, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void It2MeCliHost::DoShutdownHost() { |
| host_.reset(); |
| ui_task_runner_.reset(); |
| } |
| |
| void It2MeCliHost::OnHelloResponse() { |
| // Host is initialized, start connection. |
| SendMessageToHost(kConnectMessage, std::move(connect_params_)); |
| } |
| |
| void It2MeCliHost::OnDisconnectResponse() { |
| // Should happen only when remoting session finished and we |
| // have requested host to shut down, or when we have got second auth code |
| // without receiving connection. |
| DCHECK(!command_awaiting_crd_access_code_); |
| DCHECK(!remote_connected_); |
| ShutdownHost(); |
| } |
| |
| void It2MeCliHost::OnStateError(const std::string& error_state, |
| const base::Value& message) { |
| std::string error_message; |
| if (error_state == kHostStateDomainError) { |
| error_message = "CRD Error : Invalid domain"; |
| } else { |
| auto* error_code_value = |
| message.FindKeyOfType(kErrorMessageCode, base::Value::Type::STRING); |
| if (error_code_value) |
| error_message = error_code_value->GetString(); |
| else |
| error_message = "Unknown CRD Error"; |
| } |
| // Notify callback if command is still running. |
| if (command_awaiting_crd_access_code_) { |
| command_awaiting_crd_access_code_ = false; |
| LOG(ERROR) << "CRD Error state " + error_state; |
| } |
| // Shut down host, if any |
| ShutdownHost(); |
| } |
| |
| void It2MeCliHost::OnStateRemoteConnected(const base::Value& message) { |
| remote_connected_ = true; |
| auto* client_value = |
| message.FindKeyOfType(kClient, base::Value::Type::STRING); |
| if (client_value) { |
| HOST_LOG << "Remote connection by " << client_value->GetString(); |
| } |
| } |
| |
| void It2MeCliHost::OnStateRemoteDisconnected() { |
| // There could be a connection attempt that was not successful, we will |
| // receive "disconnected" message without actually receiving "connected". |
| if (!remote_connected_) |
| return; |
| remote_connected_ = false; |
| // Remote has disconnected, time to send "disconnect" that would result |
| // in shutting down the host. |
| base::Value params(base::Value::Type::DICTIONARY); |
| SendMessageToHost(kDisconnectMessage, std::move(params)); |
| } |
| |
| void It2MeCliHost::OnStateReceivedAccessCode(const base::Value& message) { |
| if (!command_awaiting_crd_access_code_) { |
| if (!remote_connected_) { |
| // We have already sent the access code back to the server which initiated |
| // this CRD session through a remote command, and we can not send a new |
| // access code. Assuming that the old access code is no longer valid, we |
| // can only terminate the current CRD session. |
| base::Value params(base::Value::Type::DICTIONARY); |
| SendMessageToHost(kDisconnectMessage, std::move(params)); |
| } |
| return; |
| } |
| |
| auto* code_value = |
| message.FindKeyOfType(kAccessCode, base::Value::Type::STRING); |
| auto* code_lifetime_value = |
| message.FindKeyOfType(kAccessCodeLifetime, base::Value::Type::INTEGER); |
| if (!code_value || !code_lifetime_value) { |
| OnProtocolBroken("Can not obtain access code"); |
| return; |
| } |
| command_awaiting_crd_access_code_ = false; |
| |
| // Prints the access code. |
| base::TimeDelta expires_in = base::Seconds(code_lifetime_value->GetInt()); |
| HOST_LOG << "It2Me access code is generated: " << code_value->GetString(); |
| HOST_LOG << "Expires at: " << (base::Time::Now() + expires_in); |
| } |
| |
| } // namespace remoting |