Commit 1c0f0cb6 authored by Steven Bennetts's avatar Steven Bennetts Committed by Commit Bot

ONC: OpenVPN: Make Password and OTP recommended if auth type unset

In ONC, if a policy provided network configuration does not explicitly
list a property as 'Recommended', the user is not able to set the
property.

In Google's OpenVPN policy configuration (and potentially others),
UserAuthenticationType is not explicitly set, and OTP is not included
in the Recommended list, even though it needs to be provided by the
user.

To work around this technically incorrect policy configuration (and not
break existing policies with the new UI), when UserAuthenticationType
is not explicitly set in the policy, this change adds Password and
OTP to the Recommended list so that they can be provided by the UI.

This also adds a test that catches the failure case and passes with
the new changes.

Bug: 817617
Cq-Include-Trybots: master.tryserver.chromium.linux:closure_compilation
Change-Id: If22f34e8943a2b82cf8becc93f257413c3cdcf92
Reviewed-on: https://chromium-review.googlesource.com/961365
Commit-Queue: Steven Bennetts <stevenjb@chromium.org>
Reviewed-by: default avatarToni Barzic <tbarzic@chromium.org>
Cr-Commit-Position: refs/heads/master@{#543171}
parent e8bb3cdb
...@@ -630,9 +630,6 @@ Polymer({ ...@@ -630,9 +630,6 @@ Polymer({
this.showTetherDialog_(); this.showTetherDialog_();
return; return;
} }
// Clear the error state when 'Connect' is clicked to force a connect
// attempt instead of showing the configuration UI.
this.networkProperties.ErrorState = '';
this.fire('network-connect', {networkProperties: this.networkProperties}); this.fire('network-connect', {networkProperties: this.networkProperties});
}, },
......
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
#include <utility> #include <utility>
#include "base/bind.h" #include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/json/json_writer.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/message_loop/message_loop.h" #include "base/message_loop/message_loop.h"
#include "base/run_loop.h" #include "base/run_loop.h"
...@@ -18,9 +20,12 @@ ...@@ -18,9 +20,12 @@
#include "chromeos/network/network_configuration_handler.h" #include "chromeos/network/network_configuration_handler.h"
#include "chromeos/network/network_policy_observer.h" #include "chromeos/network/network_policy_observer.h"
#include "chromeos/network/network_profile_handler.h" #include "chromeos/network/network_profile_handler.h"
#include "chromeos/network/network_state.h"
#include "chromeos/network/network_state_handler.h" #include "chromeos/network/network_state_handler.h"
#include "chromeos/network/onc/onc_signature.h"
#include "chromeos/network/onc/onc_test_utils.h" #include "chromeos/network/onc/onc_test_utils.h"
#include "chromeos/network/onc/onc_utils.h" #include "chromeos/network/onc/onc_utils.h"
#include "chromeos/network/onc/onc_validator.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/service_constants.h" #include "third_party/cros_system_api/dbus/service_constants.h"
...@@ -32,6 +37,21 @@ namespace { ...@@ -32,6 +37,21 @@ namespace {
constexpr char kUser1[] = "user1"; constexpr char kUser1[] = "user1";
constexpr char kUser1ProfilePath[] = "/profile/user1/shill"; constexpr char kUser1ProfilePath[] = "/profile/user1/shill";
constexpr char kTestGuid1[] = "{a3860e83-f03d-4cb1-bafa-b22c9e746950}";
std::string PrettyJson(const base::DictionaryValue& value) {
std::string pretty;
base::JSONWriter::WriteWithOptions(
value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &pretty);
return pretty;
}
void ErrorCallback(const std::string& error_name,
std::unique_ptr<base::DictionaryValue> error_data) {
ADD_FAILURE() << "Unexpected error: " << error_name
<< " with associated data: \n"
<< PrettyJson(*error_data);
}
class TestNetworkProfileHandler : public NetworkProfileHandler { class TestNetworkProfileHandler : public NetworkProfileHandler {
public: public:
...@@ -129,28 +149,42 @@ class ManagedNetworkConfigurationHandlerTest : public testing::Test { ...@@ -129,28 +149,42 @@ class ManagedNetworkConfigurationHandlerTest : public testing::Test {
? onc::ReadDictionaryFromJson(onc::kEmptyUnencryptedConfiguration) ? onc::ReadDictionaryFromJson(onc::kEmptyUnencryptedConfiguration)
: test_utils::ReadTestDictionary(path_to_onc); : test_utils::ReadTestDictionary(path_to_onc);
base::ListValue network_configs; base::ListValue validated_network_configs;
{ const base::Value* found_network_configs =
const base::Value* found = policy->FindKeyOfType(::onc::toplevel_config::kNetworkConfigurations,
policy->FindKeyOfType(::onc::toplevel_config::kNetworkConfigurations, base::Value::Type::LIST);
base::Value::Type::LIST); if (found_network_configs) {
if (found) for (const auto& network_config : found_network_configs->GetList()) {
network_configs = base::ListValue(found->GetList()); onc::Validator validator(true, // error_on_unknown_field
true, // error_on_wrong_recommended
false, // error_on_missing_field
true); // managed_onc
validator.SetOncSource(onc_source);
onc::Validator::Result validation_result;
std::unique_ptr<base::DictionaryValue> validated_network_config =
validator.ValidateAndRepairObject(
&onc::kNetworkConfigurationSignature, network_config,
&validation_result);
if (validation_result == onc::Validator::INVALID) {
ADD_FAILURE() << "Network configuration invalid.";
return;
}
validated_network_configs.GetList().push_back(
std::move(*(validated_network_config)));
}
} }
base::DictionaryValue global_config; base::DictionaryValue global_config;
{ const base::Value* found_global_config = policy->FindKeyOfType(
const base::Value* found = policy->FindKeyOfType( ::onc::toplevel_config::kGlobalNetworkConfiguration,
::onc::toplevel_config::kGlobalNetworkConfiguration, base::Value::Type::DICTIONARY);
base::Value::Type::DICTIONARY); if (found_global_config) {
if (found) { global_config = std::move(*base::DictionaryValue::From(
global_config = std::move(*base::DictionaryValue::From( base::Value::ToUniquePtrValue(found_global_config->Clone())));
base::Value::ToUniquePtrValue(found->Clone())));
}
} }
managed_network_configuration_handler_->SetPolicy( managed_network_configuration_handler_->SetPolicy(
onc_source, userhash, network_configs, global_config); onc_source, userhash, validated_network_configs, global_config);
} }
void SetUpEntry(const std::string& path_to_shill_json, void SetUpEntry(const std::string& path_to_shill_json,
...@@ -168,7 +202,7 @@ class ManagedNetworkConfigurationHandlerTest : public testing::Test { ...@@ -168,7 +202,7 @@ class ManagedNetworkConfigurationHandlerTest : public testing::Test {
managed_network_configuration_handler_.reset(); managed_network_configuration_handler_.reset();
} }
private: protected:
base::MessageLoop message_loop_; base::MessageLoop message_loop_;
TestNetworkPolicyObserver policy_observer_; TestNetworkPolicyObserver policy_observer_;
...@@ -240,8 +274,7 @@ TEST_F(ManagedNetworkConfigurationHandlerTest, EnableManagedCredentialsVPN) { ...@@ -240,8 +274,7 @@ TEST_F(ManagedNetworkConfigurationHandlerTest, EnableManagedCredentialsVPN) {
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
const base::DictionaryValue* properties = const base::DictionaryValue* properties =
GetShillServiceClient()->GetServiceProperties( GetShillServiceClient()->GetServiceProperties(kTestGuid1);
"{a3860e83-f03d-4cb1-bafa-b22c9e746950}");
ASSERT_TRUE(properties); ASSERT_TRUE(properties);
EXPECT_EQ(*expected_shill_properties, *properties); EXPECT_EQ(*expected_shill_properties, *properties);
} }
...@@ -418,16 +451,46 @@ TEST_F(ManagedNetworkConfigurationHandlerTest, SetPolicyUpdateManagedVPN) { ...@@ -418,16 +451,46 @@ TEST_F(ManagedNetworkConfigurationHandlerTest, SetPolicyUpdateManagedVPN) {
InitializeStandardProfiles(); InitializeStandardProfiles();
SetUpEntry("policy/shill_managed_vpn.json", kUser1ProfilePath, "entry_path"); SetUpEntry("policy/shill_managed_vpn.json", kUser1ProfilePath, "entry_path");
SetPolicy(::onc::ONC_SOURCE_USER_POLICY, kUser1, "policy/policy_vpn.onc");
base::RunLoop().RunUntilIdle();
const base::DictionaryValue* properties =
GetShillServiceClient()->GetServiceProperties(kTestGuid1);
ASSERT_TRUE(properties);
std::unique_ptr<base::DictionaryValue> expected_shill_properties = std::unique_ptr<base::DictionaryValue> expected_shill_properties =
test_utils::ReadTestDictionary("policy/shill_policy_on_managed_vpn.json"); test_utils::ReadTestDictionary("policy/shill_policy_on_managed_vpn.json");
EXPECT_EQ(*expected_shill_properties, *properties);
}
SetPolicy(::onc::ONC_SOURCE_USER_POLICY, kUser1, "policy/policy_vpn.onc"); TEST_F(ManagedNetworkConfigurationHandlerTest,
SetPolicyUpdateManagedVPNPlusUi) {
InitializeStandardProfiles();
SetUpEntry("policy/shill_managed_vpn.json", kUser1ProfilePath, "entry_path");
// Apply a policy that does not provide an authenticaiton type.
SetPolicy(::onc::ONC_SOURCE_USER_POLICY, kUser1,
"policy/policy_vpn_no_auth.onc");
base::RunLoop().RunUntilIdle();
// Apply additional configuration (e.g. from the UI). This includes password
// and OTP which should be allowed when authentication type is not explicitly
// set. See https://crbug.com/817617 for details.
const NetworkState* network_state =
network_state_handler_->GetNetworkStateFromGuid(kTestGuid1);
ASSERT_TRUE(network_state);
std::unique_ptr<base::DictionaryValue> ui_config =
test_utils::ReadTestDictionary("policy/policy_vpn_ui.json");
managed_network_configuration_handler_->SetProperties(
network_state->path(), *ui_config, base::DoNothing(),
base::Bind(&ErrorCallback));
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
const base::DictionaryValue* properties = const base::DictionaryValue* properties =
GetShillServiceClient()->GetServiceProperties( GetShillServiceClient()->GetServiceProperties(kTestGuid1);
"{a3860e83-f03d-4cb1-bafa-b22c9e746950}");
ASSERT_TRUE(properties); ASSERT_TRUE(properties);
std::unique_ptr<base::DictionaryValue> expected_shill_properties =
test_utils::ReadTestDictionary(
"policy/shill_policy_on_managed_vpn_plus_ui.json");
EXPECT_EQ(*expected_shill_properties, *properties); EXPECT_EQ(*expected_shill_properties, *properties);
} }
...@@ -444,8 +507,7 @@ TEST_F(ManagedNetworkConfigurationHandlerTest, ...@@ -444,8 +507,7 @@ TEST_F(ManagedNetworkConfigurationHandlerTest,
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
const base::DictionaryValue* properties = const base::DictionaryValue* properties =
GetShillServiceClient()->GetServiceProperties( GetShillServiceClient()->GetServiceProperties(kTestGuid1);
"{a3860e83-f03d-4cb1-bafa-b22c9e746950}");
ASSERT_TRUE(properties); ASSERT_TRUE(properties);
EXPECT_EQ(*expected_shill_properties, *properties); EXPECT_EQ(*expected_shill_properties, *properties);
} }
......
...@@ -58,7 +58,7 @@ void CopyServiceResult(bool* called, ...@@ -58,7 +58,7 @@ void CopyServiceResult(bool* called,
*guid_out = guid; *guid_out = guid;
} }
static std::string PrettyJson(const base::DictionaryValue& value) { std::string PrettyJson(const base::DictionaryValue& value) {
std::string pretty; std::string pretty;
base::JSONWriter::WriteWithOptions( base::JSONWriter::WriteWithOptions(
value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &pretty); value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &pretty);
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#include "base/strings/string_util.h" #include "base/strings/string_util.h"
#include "base/values.h" #include "base/values.h"
#include "chromeos/network/client_cert_util.h" #include "chromeos/network/client_cert_util.h"
#include "chromeos/network/network_event_log.h"
#include "chromeos/network/onc/onc_signature.h" #include "chromeos/network/onc/onc_signature.h"
#include "chromeos/network/onc/onc_translation_tables.h" #include "chromeos/network/onc/onc_translation_tables.h"
#include "chromeos/network/onc/onc_translator.h" #include "chromeos/network/onc/onc_translator.h"
...@@ -175,7 +176,7 @@ void LocalTranslator::TranslateOpenVPN() { ...@@ -175,7 +176,7 @@ void LocalTranslator::TranslateOpenVPN() {
// identical to kPasswordAndOTP. // identical to kPasswordAndOTP.
if (user_auth_type.empty()) if (user_auth_type.empty())
user_auth_type = ::onc::openvpn_user_auth_type::kPasswordAndOTP; user_auth_type = ::onc::openvpn_user_auth_type::kPasswordAndOTP;
NET_LOG(DEBUG) << "USER AUTH TYPE: " << user_auth_type;
if (user_auth_type == ::onc::openvpn_user_auth_type::kPassword || if (user_auth_type == ::onc::openvpn_user_auth_type::kPassword ||
user_auth_type == ::onc::openvpn_user_auth_type::kPasswordAndOTP) { user_auth_type == ::onc::openvpn_user_auth_type::kPasswordAndOTP) {
CopyFieldFromONCToShill(::onc::openvpn::kPassword, CopyFieldFromONCToShill(::onc::openvpn::kPassword,
...@@ -394,12 +395,12 @@ void LocalTranslator::CopyFieldFromONCToShill( ...@@ -394,12 +395,12 @@ void LocalTranslator::CopyFieldFromONCToShill(
base::Value::Type expected_type = base::Value::Type expected_type =
field_signature->value_signature->onc_type; field_signature->value_signature->onc_type;
if (value->type() != expected_type) { if (value->type() != expected_type) {
LOG(ERROR) << "Found field " << onc_field_name << " of type " NET_LOG(ERROR) << "Found field " << onc_field_name << " of type "
<< value->type() << " but expected type " << expected_type; << value->type() << " but expected type " << expected_type;
return; return;
} }
} else { } else {
LOG(ERROR) NET_LOG(ERROR)
<< "Attempt to translate a field that is not part of the ONC format."; << "Attempt to translate a field that is not part of the ONC format.";
return; return;
} }
...@@ -433,9 +434,9 @@ void LocalTranslator::TranslateWithTableAndSet( ...@@ -433,9 +434,9 @@ void LocalTranslator::TranslateWithTableAndSet(
// As we previously validate ONC, this case should never occur. If it still // As we previously validate ONC, this case should never occur. If it still
// occurs, we should check here. Otherwise the failure will only show up much // occurs, we should check here. Otherwise the failure will only show up much
// later in Shill. // later in Shill.
LOG(ERROR) << "Value '" << onc_value NET_LOG(ERROR) << "Value '" << onc_value
<< "' cannot be translated to Shill property: " << "' cannot be translated to Shill property: "
<< shill_property_name; << shill_property_name;
} }
// Iterates recursively over |onc_object| and its |signature|. At each object // Iterates recursively over |onc_object| and its |signature|. At each object
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "base/json/json_writer.h" #include "base/json/json_writer.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h" #include "base/strings/string_piece.h"
#include "base/strings/string_util.h" #include "base/strings/string_util.h"
...@@ -48,7 +49,7 @@ Validator::~Validator() = default; ...@@ -48,7 +49,7 @@ Validator::~Validator() = default;
std::unique_ptr<base::DictionaryValue> Validator::ValidateAndRepairObject( std::unique_ptr<base::DictionaryValue> Validator::ValidateAndRepairObject(
const OncValueSignature* object_signature, const OncValueSignature* object_signature,
const base::DictionaryValue& onc_object, const base::Value& onc_object,
Result* result) { Result* result) {
CHECK(object_signature); CHECK(object_signature);
*result = VALID; *result = VALID;
...@@ -806,6 +807,26 @@ bool Validator::ValidateOpenVPN(base::DictionaryValue* result) { ...@@ -806,6 +807,26 @@ bool Validator::ValidateOpenVPN(base::DictionaryValue* result) {
return false; return false;
} }
if ((onc_source_ == ::onc::ONC_SOURCE_DEVICE_POLICY ||
onc_source_ == ::onc::ONC_SOURCE_USER_POLICY) &&
!result->FindKeyOfType(::onc::openvpn::kUserAuthenticationType,
base::Value::Type::STRING)) {
// If kUserAuthenticationType is unspecified for a policy controlled
// network, allow Password and OTP to be specified by the user by adding
// them to the recommended list.
base::Value* recommended =
result->FindKeyOfType(::onc::kRecommended, base::Value::Type::LIST);
if (!recommended)
recommended = result->SetKey(::onc::kRecommended, base::ListValue());
base::Value::ListStorage& result_list = recommended->GetList();
base::Value password_value(::onc::openvpn::kPassword);
if (!base::ContainsValue(result_list, password_value))
result_list.push_back(std::move(password_value));
base::Value otp_value(::onc::openvpn::kOTP);
if (!base::ContainsValue(result_list, otp_value))
result_list.push_back(std::move(otp_value));
}
if (result->HasKey(kServerCARefs) && result->HasKey(kServerCARef)) { if (result->HasKey(kServerCARefs) && result->HasKey(kServerCARef)) {
error_or_warning_found_ = true; error_or_warning_found_ = true;
LOG(ERROR) << MessageHeader() << "At most one of " << kServerCARefs LOG(ERROR) << MessageHeader() << "At most one of " << kServerCARefs
......
...@@ -81,8 +81,8 @@ class CHROMEOS_EXPORT Validator : public Mapper { ...@@ -81,8 +81,8 @@ class CHROMEOS_EXPORT Validator : public Mapper {
onc_source_ = source; onc_source_ = source;
} }
// Validate the given |onc_object| according to |object_signature|. The // Validate the given |onc_object| dictionary according to |object_signature|.
// |object_signature| has to be a pointer to one of the signatures in // The |object_signature| has to be a pointer to one of the signatures in
// |onc_signature.h|. If an error is found, the function returns NULL and sets // |onc_signature.h|. If an error is found, the function returns NULL and sets
// |result| to INVALID. If possible (no error encountered) a DeepCopy is // |result| to INVALID. If possible (no error encountered) a DeepCopy is
// created that contains all but the invalid fields and values and returns // created that contains all but the invalid fields and values and returns
...@@ -96,7 +96,7 @@ class CHROMEOS_EXPORT Validator : public Mapper { ...@@ -96,7 +96,7 @@ class CHROMEOS_EXPORT Validator : public Mapper {
// For details, see the class comment. // For details, see the class comment.
std::unique_ptr<base::DictionaryValue> ValidateAndRepairObject( std::unique_ptr<base::DictionaryValue> ValidateAndRepairObject(
const OncValueSignature* object_signature, const OncValueSignature* object_signature,
const base::DictionaryValue& onc_object, const base::Value& onc_object,
Result* result); Result* result);
private: private:
......
{
"NetworkConfigurations":[
{
"GUID":"{a3860e83-f03d-4cb1-bafa-b22c9e746950}",
"Name":"my vpn",
"Type":"VPN",
"VPN":{
"AutoConnect":false,
"Host":"vpn.my.domain.com",
"OpenVPN":{
"Port":443,
"Proto":"udp",
"Recommended": ["Username", "Password", "SaveCredentials"],
"SaveCredentials":false,
},
"Type":"OpenVPN"
}
}
],
"Type":"UnencryptedConfiguration"
}
{
"GUID":"{a3860e83-f03d-4cb1-bafa-b22c9e746950}",
"Name":"my vpn",
"Type":"VPN",
"VPN":{
"AutoConnect":false,
"Host":"vpn.my.domain.com",
"OpenVPN":{
"OTP":"user OTP",
"Password":"user password",
"SaveCredentials":true,
"UserAuthenticationType":"PasswordAndOTP",
"Username":"user name"
},
"Type":"OpenVPN"
}
}
...@@ -17,7 +17,6 @@ ...@@ -17,7 +17,6 @@
"SaveCredentials": false, "SaveCredentials": false,
"State": "idle", "State": "idle",
"Type": "vpn", "Type": "vpn",
// Password is not 'Recommended' by policy, thus removed here.
"UIData": "{\"onc_source\":\"user_policy\",\"user_settings\":{}}", "UIData": "{\"onc_source\":\"user_policy\",\"user_settings\":{}}",
"Visible": true, "Visible": true,
"WiFi.HexSSID": "7B61333836306538332D663033642D346362312D626166612D6232326339653734363935307D" "WiFi.HexSSID": "7B61333836306538332D663033642D346362312D626166612D6232326339653734363935307D"
......
{
"AutoConnect": false,
"Device": "",
"GUID": "{a3860e83-f03d-4cb1-bafa-b22c9e746950}",
"Name": "my vpn",
"Profile": "/profile/user1/shill",
"Provider": {
"Host": "vpn.my.domain.com",
"OpenVPN.OTP": "user OTP",
"OpenVPN.Password": "user password",
"OpenVPN.Port": "443",
"OpenVPN.Proto": "udp",
"OpenVPN.RemoteCertKU": "",
"OpenVPN.User": "user name",
"Type": "openvpn"
},
"SSID": "{a3860e83-f03d-4cb1-bafa-b22c9e746950}",
"SaveCredentials": true,
"State": "idle",
"Type": "vpn",
"UIData": "{\"onc_source\":\"user_policy\",\"user_settings\":{\"GUID\":\"{a3860e83-f03d-4cb1-bafa-b22c9e746950}\",\"Name\":\"my vpn\",\"Type\":\"VPN\",\"VPN\":{\"AutoConnect\":false,\"Host\":\"vpn.my.domain.com\",\"OpenVPN\":{\"OTP\":\"user OTP\",\"Password\":\"FAKE_CREDENTIAL_VPaJDV9x\",\"SaveCredentials\":true,\"UserAuthenticationType\":\"PasswordAndOTP\",\"Username\":\"user name\"},\"Type\":\"OpenVPN\"}}}",
"Visible": true,
"WiFi.HexSSID": "7B61333836306538332D663033642D346362312D626166612D6232326339653734363935307D"
}
...@@ -15,7 +15,8 @@ ...@@ -15,7 +15,8 @@
"OpenVPN": { "OpenVPN": {
"Port": 1194, "Port": 1194,
"Username": "abc fake email def", "Username": "abc fake email def",
"Recommended": [ "Username", "Password" ], // "UNKNOWN_FIELD" is removed and "OTP" is added (issue 817617).
"Recommended": [ "Username", "Password", "OTP" ],
"ClientCertType": "Pattern", "ClientCertType": "Pattern",
"ClientCertPattern": { "ClientCertPattern": {
"IssuerCAPEMs": [ "-----BEGIN CERTIFICATE-----\nMIIG\n-----END CERTIFICATE-----\n" ], "IssuerCAPEMs": [ "-----BEGIN CERTIFICATE-----\nMIIG\n-----END CERTIFICATE-----\n" ],
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment