| // Copyright 2016 The Chromium OS 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 "authpolicy/authpolicy.h" |
| |
| #include <utility> |
| #include <vector> |
| |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| #include <base/threading/thread_task_runner_handle.h> |
| #include <brillo/dbus/dbus_method_invoker.h> |
| #include <dbus/authpolicy/dbus-constants.h> |
| #include <dbus/login_manager/dbus-constants.h> |
| |
| #include "authpolicy/authpolicy_metrics.h" |
| #include "authpolicy/path_service.h" |
| #include "authpolicy/proto_bindings/active_directory_info.pb.h" |
| #include "authpolicy/samba_helper.h" |
| #include "authpolicy/samba_interface.h" |
| #include "bindings/device_management_backend.pb.h" |
| |
| namespace em = enterprise_management; |
| |
| using brillo::dbus_utils::DBusObject; |
| using brillo::dbus_utils::ExtractMethodCallResults; |
| |
| namespace authpolicy { |
| |
| const char kChromeUserPolicyType[] = "google/chromeos/user"; |
| const char kChromeDevicePolicyType[] = "google/chromeos/device"; |
| |
| namespace { |
| |
| void PrintError(const char* msg, ErrorType error) { |
| if (error == ERROR_NONE) |
| LOG(INFO) << msg << " succeeded"; |
| else |
| LOG(INFO) << msg << " failed with code " << error; |
| } |
| |
| const char* GetSessionManagerStoreMethod(bool is_user_policy) { |
| return is_user_policy |
| ? login_manager::kSessionManagerStoreUnsignedPolicyForUser |
| : login_manager::kSessionManagerStoreUnsignedPolicy; |
| } |
| |
| DBusCallType GetPolicyDBusCallType(bool is_user_policy) { |
| return is_user_policy ? DBUS_CALL_REFRESH_USER_POLICY |
| : DBUS_CALL_REFRESH_DEVICE_POLICY; |
| } |
| |
| // Serializes |proto| to the byte array |proto_blob|. Returns ERROR_NONE on |
| // success and ERROR_PARSE_FAILED otherwise. |
| template <typename ProtoType> |
| ErrorType SerializeProto(ProtoType proto, std::vector<uint8_t>* proto_blob) { |
| std::string buffer; |
| if (!proto.SerializeToString(&buffer)) { |
| LOG(ERROR) << "Failed to serialize proto"; |
| return ERROR_PARSE_FAILED; |
| } |
| proto_blob->assign(buffer.begin(), buffer.end()); |
| return ERROR_NONE; |
| } |
| |
| // Creates dbus::Response. Appends |error| to it. If |blob| is not nullptr |
| // appends it to the response as well. |
| std::unique_ptr<dbus::Response> CreateResponse(dbus::MethodCall* method_call, |
| int32_t error, |
| std::vector<uint8_t>* blob) { |
| std::unique_ptr<dbus::Response> response = |
| dbus::Response::FromMethodCall(method_call); |
| dbus::MessageWriter writer(response.get()); |
| writer.AppendInt32(error); |
| if (blob) |
| writer.AppendArrayOfBytes(blob->data(), blob->size()); |
| return response; |
| } |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<DBusObject> AuthPolicy::GetDBusObject( |
| brillo::dbus_utils::ExportedObjectManager* object_manager) { |
| return std::make_unique<DBusObject>( |
| object_manager, object_manager->GetBus(), |
| org::chromium::AuthPolicyAdaptor::GetObjectPath()); |
| } |
| |
| AuthPolicy::AuthPolicy(AuthPolicyMetrics* metrics, |
| const PathService* path_service) |
| : org::chromium::AuthPolicyAdaptor(this), |
| metrics_(metrics), |
| samba_(base::ThreadTaskRunnerHandle::Get(), |
| metrics, |
| path_service, |
| base::Bind(&AuthPolicy::OnUserKerberosFilesChanged, |
| base::Unretained(this))), |
| weak_ptr_factory_(this) {} |
| |
| ErrorType AuthPolicy::Initialize(bool device_is_locked) { |
| device_is_locked_ = device_is_locked; |
| return samba_.Initialize(/* expect_config */ device_is_locked_); |
| } |
| |
| void AuthPolicy::RegisterAsync( |
| std::unique_ptr<brillo::dbus_utils::DBusObject> dbus_object, |
| const AsyncEventSequencer::CompletionAction& completion_callback) { |
| DCHECK(!dbus_object_); |
| dbus_object_ = std::move(dbus_object); |
| // Make sure the task runner passed to |samba_| in the constructor is actually |
| // the D-Bus task runner. This guarantees that automatic TGT renewal won't |
| // interfere with D-Bus calls. Note that |GetDBusTaskRunner()| returns a |
| // TaskRunner, which is a base class of SingleThreadTaskRunner accepted by |
| // |samba_|. |
| CHECK_EQ(base::ThreadTaskRunnerHandle::Get(), |
| dbus_object_->GetBus()->GetDBusTaskRunner()); |
| RegisterWithDBusObject(dbus_object_.get()); |
| dbus_object_->RegisterAsync(completion_callback); |
| session_manager_proxy_ = dbus_object_->GetBus()->GetObjectProxy( |
| login_manager::kSessionManagerServiceName, |
| dbus::ObjectPath(login_manager::kSessionManagerServicePath)); |
| DCHECK(session_manager_proxy_); |
| } |
| |
| void AuthPolicy::AuthenticateUser(dbus::MethodCall* method_call, |
| brillo::dbus_utils::ResponseSender sender) { |
| // Read input arguments. |
| dbus::MessageReader reader(method_call); |
| dbus::FileDescriptor password_fd; |
| authpolicy::AuthenticateUserRequest request; |
| bool success = reader.PopArrayOfBytesAsProto(&request) || |
| (reader.PopString(request.mutable_user_principal_name()) && |
| reader.PopString(request.mutable_account_id())); |
| |
| success = success && reader.PopFileDescriptor(&password_fd); |
| int32_t error = ERROR_NONE; |
| std::vector<uint8_t> account_data_blob; |
| if (success) { |
| password_fd.CheckValidity(); |
| AuthenticateUser(request, password_fd, &error, &account_data_blob); |
| } else { |
| error = ERROR_DBUS_FAILURE; |
| } |
| |
| // Send response. |
| sender.Run(CreateResponse(method_call, error, &account_data_blob)); |
| } |
| |
| void AuthPolicy::AuthenticateUser( |
| const authpolicy::AuthenticateUserRequest& request, |
| const dbus::FileDescriptor& password_fd, |
| int32_t* int_error, |
| std::vector<uint8_t>* account_info_blob) { |
| LOG(INFO) << "Received 'AuthenticateUser' request"; |
| ScopedTimerReporter timer(TIMER_AUTHENTICATE_USER); |
| |
| authpolicy::ActiveDirectoryAccountInfo account_info; |
| ErrorType error = samba_.AuthenticateUser(request.user_principal_name(), |
| request.account_id(), |
| password_fd.value(), &account_info); |
| if (error == ERROR_NONE) |
| error = SerializeProto(account_info, account_info_blob); |
| PrintError("AuthenticateUser", error); |
| metrics_->ReportDBusResult(DBUS_CALL_AUTHENTICATE_USER, error); |
| *int_error = static_cast<int>(error); |
| } |
| |
| void AuthPolicy::GetUserStatus(const std::string& account_id, |
| int32_t* int_error, |
| std::vector<uint8_t>* user_status_blob) { |
| LOG(INFO) << "Received 'GetUserStatus' request"; |
| ScopedTimerReporter timer(TIMER_GET_USER_STATUS); |
| |
| authpolicy::ActiveDirectoryUserStatus user_status; |
| ErrorType error = samba_.GetUserStatus(account_id, &user_status); |
| if (error == ERROR_NONE) |
| error = SerializeProto(user_status, user_status_blob); |
| PrintError("GetUserStatus", error); |
| metrics_->ReportDBusResult(DBUS_CALL_GET_USER_STATUS, error); |
| *int_error = static_cast<int>(error); |
| } |
| |
| void AuthPolicy::GetUserKerberosFiles( |
| const std::string& account_id, |
| int32_t* int_error, |
| std::vector<uint8_t>* kerberos_files_blob) { |
| LOG(INFO) << "Received 'GetUserKerberosFiles' request"; |
| ScopedTimerReporter timer(TIMER_GET_USER_KERBEROS_FILES); |
| |
| authpolicy::KerberosFiles kerberos_files; |
| ErrorType error = samba_.GetUserKerberosFiles(account_id, &kerberos_files); |
| if (error == ERROR_NONE) |
| error = SerializeProto(kerberos_files, kerberos_files_blob); |
| PrintError("GetUserKerberosFiles", error); |
| metrics_->ReportDBusResult(DBUS_CALL_GET_USER_KERBEROS_FILES, error); |
| *int_error = static_cast<int>(error); |
| } |
| |
| void AuthPolicy::JoinADDomain(dbus::MethodCall* method_call, |
| brillo::dbus_utils::ResponseSender sender) { |
| // Read input arguments. |
| dbus::MessageReader reader(method_call); |
| dbus::FileDescriptor password_fd; |
| authpolicy::JoinDomainRequest request; |
| bool success = reader.PopArrayOfBytesAsProto(&request) || |
| (reader.PopString(request.mutable_machine_name()) && |
| reader.PopString(request.mutable_user_principal_name())); |
| |
| success = success && reader.PopFileDescriptor(&password_fd); |
| int32_t error = ERROR_NONE; |
| if (success) { |
| password_fd.CheckValidity(); |
| error = JoinADDomain(request, password_fd); |
| } else { |
| error = ERROR_DBUS_FAILURE; |
| } |
| |
| // Send response. |
| sender.Run(CreateResponse(method_call, error, nullptr)); |
| } |
| |
| int32_t AuthPolicy::JoinADDomain(const authpolicy::JoinDomainRequest& request, |
| const dbus::FileDescriptor& password_fd) { |
| LOG(INFO) << "Received 'JoinADDomain' request"; |
| ScopedTimerReporter timer(TIMER_JOIN_AD_DOMAIN); |
| |
| ErrorType error = |
| samba_.JoinMachine(request.machine_name(), request.user_principal_name(), |
| password_fd.value()); |
| PrintError("JoinADDomain", error); |
| metrics_->ReportDBusResult(DBUS_CALL_JOIN_AD_DOMAIN, error); |
| return error; |
| } |
| |
| void AuthPolicy::RefreshUserPolicy(PolicyResponseCallback callback, |
| const std::string& account_id) { |
| std::string real_account_id = account_id; |
| if (base::StartsWith(real_account_id, kActiveDirectoryPrefix, |
| base::CompareCase::SENSITIVE)) { |
| real_account_id = real_account_id.substr(strlen(kActiveDirectoryPrefix)); |
| } |
| LOG(INFO) << "Received 'RefreshUserPolicy' request"; |
| auto timer = std::make_unique<ScopedTimerReporter>(TIMER_REFRESH_USER_POLICY); |
| |
| // Fetch GPOs for the current user. |
| std::unique_ptr<protos::GpoPolicyData> gpo_policy_data = |
| std::make_unique<protos::GpoPolicyData>(); |
| ErrorType error = |
| samba_.FetchUserGpos(real_account_id, gpo_policy_data.get()); |
| PrintError("User policy fetch and parsing", error); |
| |
| // Return immediately on error. |
| if (error != ERROR_NONE) { |
| metrics_->ReportDBusResult(DBUS_CALL_REFRESH_USER_POLICY, error); |
| callback->Return(error); |
| return; |
| } |
| |
| const std::string account_id_key = GetAccountIdKey(real_account_id); |
| // Send policy to Session Manager. |
| StorePolicy(std::move(gpo_policy_data), &account_id_key, std::move(timer), |
| std::move(callback)); |
| } |
| |
| void AuthPolicy::RefreshDevicePolicy(PolicyResponseCallback callback) { |
| LOG(INFO) << "Received 'RefreshDevicePolicy' request"; |
| auto timer = |
| std::make_unique<ScopedTimerReporter>(TIMER_REFRESH_DEVICE_POLICY); |
| |
| if (cached_device_policy_data_) { |
| // Send policy to Session Manager. |
| LOG(INFO) << "Using cached policy"; |
| StorePolicy(std::move(cached_device_policy_data_), nullptr, |
| std::move(timer), std::move(callback)); |
| return; |
| } |
| |
| // Fetch GPOs for the device. |
| std::unique_ptr<protos::GpoPolicyData> gpo_policy_data = |
| std::make_unique<protos::GpoPolicyData>(); |
| ErrorType error = samba_.FetchDeviceGpos(gpo_policy_data.get()); |
| PrintError("Device policy fetch and parsing", error); |
| |
| device_is_locked_ = device_is_locked_ || InstallAttributesReader().IsLocked(); |
| if (!device_is_locked_ && error == ERROR_NONE) { |
| LOG(INFO) << "Device is not locked yet. Caching device policy."; |
| cached_device_policy_data_ = std::move(gpo_policy_data); |
| error = ERROR_DEVICE_POLICY_CACHED_BUT_NOT_SENT; |
| } |
| |
| // Return immediately on error. |
| if (error != ERROR_NONE) { |
| metrics_->ReportDBusResult(DBUS_CALL_REFRESH_DEVICE_POLICY, error); |
| callback->Return(error); |
| return; |
| } |
| |
| // Send policy to Session Manager. |
| StorePolicy(std::move(gpo_policy_data), nullptr, std::move(timer), |
| std::move(callback)); |
| } |
| |
| std::string AuthPolicy::SetDefaultLogLevel(int32_t level) { |
| LOG(INFO) << "Received 'SetDefaultLogLevel' request"; |
| if (level < AuthPolicyFlags::kMinLevel || |
| level > AuthPolicyFlags::kMaxLevel) { |
| std::string message = base::StringPrintf("Level must be between %i and %i.", |
| AuthPolicyFlags::kMinLevel, |
| AuthPolicyFlags::kMaxLevel); |
| LOG(ERROR) << message; |
| return message; |
| } |
| samba_.SetDefaultLogLevel(static_cast<AuthPolicyFlags::DefaultLevel>(level)); |
| return std::string(); |
| } |
| |
| void AuthPolicy::OnUserKerberosFilesChanged() { |
| LOG(INFO) << "Firing signal UserKerberosFilesChanged"; |
| SendUserKerberosFilesChangedSignal(); |
| } |
| |
| void AuthPolicy::StorePolicy( |
| std::unique_ptr<protos::GpoPolicyData> gpo_policy_data, |
| const std::string* account_id_key, |
| std::unique_ptr<ScopedTimerReporter> timer, |
| PolicyResponseCallback callback) { |
| // Note: Only policy_value required here, the other data only impacts |
| // signature, but since we don't sign, we don't need it. |
| const bool is_user_policy = account_id_key != nullptr; |
| const char* const policy_type = |
| is_user_policy ? kChromeUserPolicyType : kChromeDevicePolicyType; |
| |
| em::PolicyData em_policy_data; |
| em_policy_data.set_policy_value(gpo_policy_data->user_or_device_policy()); |
| em_policy_data.set_policy_type(policy_type); |
| if (is_user_policy) { |
| em_policy_data.set_username(samba_.user_sam_account_name()); |
| // Device id in the proto also could be used as an account/client id. |
| em_policy_data.set_device_id(samba_.user_account_id()); |
| } else { |
| em_policy_data.set_device_id(samba_.machine_name()); |
| } |
| em_policy_data.set_timestamp(base::Time::Now().ToJavaTime()); |
| em_policy_data.set_management_mode(em::PolicyData::ENTERPRISE_MANAGED); |
| // Note: No signature required here, Active Directory policy is unsigned! |
| |
| em::PolicyFetchResponse policy_response; |
| std::string response_blob; |
| if (!em_policy_data.SerializeToString( |
| policy_response.mutable_policy_data()) || |
| !policy_response.SerializeToString(&response_blob)) { |
| LOG(ERROR) << "Failed to serialize policy data"; |
| const DBusCallType call_type = GetPolicyDBusCallType(is_user_policy); |
| metrics_->ReportDBusResult(call_type, ERROR_STORE_POLICY_FAILED); |
| callback->Return(ERROR_STORE_POLICY_FAILED); |
| return; |
| } |
| |
| const char* const method = GetSessionManagerStoreMethod(is_user_policy); |
| dbus::MethodCall method_call(login_manager::kSessionManagerInterface, method); |
| dbus::MessageWriter writer(&method_call); |
| if (account_id_key) |
| writer.AppendString(*account_id_key); |
| writer.AppendArrayOfBytes( |
| reinterpret_cast<const uint8_t*>(response_blob.data()), |
| response_blob.size()); |
| session_manager_proxy_->CallMethod( |
| &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, |
| base::Bind(&AuthPolicy::OnPolicyStored, weak_ptr_factory_.GetWeakPtr(), |
| is_user_policy, base::Passed(&timer), |
| base::Passed(&callback))); |
| } |
| |
| void AuthPolicy::OnPolicyStored( |
| bool is_user_policy, |
| std::unique_ptr<ScopedTimerReporter> /* timer */, |
| PolicyResponseCallback callback, |
| dbus::Response* response) { |
| const char* const method = GetSessionManagerStoreMethod(is_user_policy); |
| brillo::ErrorPtr brillo_error; |
| std::string msg; |
| if (!response) { |
| // In case of error, session_manager_proxy_ prints out the error string and |
| // response is empty. |
| msg = |
| base::StringPrintf("Call to %s failed. No response or error.", method); |
| } else if (!ExtractMethodCallResults(response, &brillo_error)) { |
| // Response is expected have no call results. |
| msg = base::StringPrintf( |
| "Call to %s failed. %s", method, |
| brillo_error ? brillo_error->GetMessage().c_str() : "Unknown error."); |
| } |
| |
| ErrorType error; |
| if (!msg.empty()) { |
| LOG(ERROR) << msg; |
| error = ERROR_STORE_POLICY_FAILED; |
| } else { |
| LOG(INFO) << "Call to " << method << " succeeded."; |
| error = ERROR_NONE; |
| } |
| const DBusCallType call_type = GetPolicyDBusCallType(is_user_policy); |
| metrics_->ReportDBusResult(call_type, error); |
| callback->Return(error); |
| } |
| |
| } // namespace authpolicy |