Commit ff03b241 authored by Aya ElAttar's avatar Aya ElAttar Committed by Commit Bot

Add tests for ClipboardDlpController

1. Changed TestClipboard to support DLP controller.
2. Added unittests to cover allowed/disallowed clipboard
read based on the DLP controller.

Bug: 1103215
Change-Id: Ibb6c7bf575dd0e514349c395fb965af203fe3b1d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2283207
Commit-Queue: Aya Elsayed <ayaelattar@google.com>
Reviewed-by: default avatarNikita Podguzov <nikitapodguzov@chromium.org>
Reviewed-by: default avatarDarwin Huang <huangdarwin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#789890}
parent f4b37cb6
...@@ -23,10 +23,12 @@ ...@@ -23,10 +23,12 @@
#include "base/pickle.h" #include "base/pickle.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/strings/string16.h"
#include "base/strings/string_util.h" #include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "testing/gmock/include/gmock/gmock-matchers.h" #include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h" #include "testing/platform_test.h"
#include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkBitmap.h"
...@@ -35,6 +37,8 @@ ...@@ -35,6 +37,8 @@
#include "third_party/skia/include/core/SkUnPreMultiply.h" #include "third_party/skia/include/core/SkUnPreMultiply.h"
#include "ui/base/clipboard/clipboard.h" #include "ui/base/clipboard/clipboard.h"
#include "ui/base/clipboard/clipboard_constants.h" #include "ui/base/clipboard/clipboard_constants.h"
#include "ui/base/clipboard/clipboard_data_endpoint.h"
#include "ui/base/clipboard/clipboard_dlp_controller.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h" #include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/clipboard/test/clipboard_test_util.h" #include "ui/base/clipboard/test/clipboard_test_util.h"
#include "ui/base/clipboard/test/test_clipboard.h" #include "ui/base/clipboard/test/test_clipboard.h"
...@@ -58,6 +62,8 @@ using testing::Contains; ...@@ -58,6 +62,8 @@ using testing::Contains;
namespace ui { namespace ui {
class MockClipboardDlpController;
template <typename ClipboardTraits> template <typename ClipboardTraits>
class ClipboardTest : public PlatformTest { class ClipboardTest : public PlatformTest {
public: public:
...@@ -88,14 +94,40 @@ class ClipboardTest : public PlatformTest { ...@@ -88,14 +94,40 @@ class ClipboardTest : public PlatformTest {
return types; return types;
} }
void AddDlpController() {
auto dlp_controller = std::make_unique<MockClipboardDlpController>();
dlp_controller_ = dlp_controller.get();
ClipboardTest::clipboard().SetClipboardDlpController(
std::move(dlp_controller));
}
MockClipboardDlpController* dlp_controller() const { return dlp_controller_; }
private: private:
#if defined(USE_X11) #if defined(USE_X11)
std::unique_ptr<PlatformEventSource> event_source_; std::unique_ptr<PlatformEventSource> event_source_;
#endif #endif
// Clipboard has a protected destructor, so scoped_ptr doesn't work here. // Clipboard has a protected destructor, so scoped_ptr doesn't work here.
Clipboard* clipboard_ = nullptr; Clipboard* clipboard_ = nullptr;
// MockClipboardDlpController object is owned by ClipboardTest.
MockClipboardDlpController* dlp_controller_ = nullptr;
}; };
// A mock delegate for testing.
class MockClipboardDlpController : public ClipboardDlpController {
public:
MockClipboardDlpController();
~MockClipboardDlpController();
MOCK_CONST_METHOD2(IsDataReadAllowed,
bool(const ClipboardDataEndpoint* const data_src,
const ClipboardDataEndpoint* const data_dst));
};
MockClipboardDlpController::MockClipboardDlpController() = default;
MockClipboardDlpController::~MockClipboardDlpController() = default;
// Hack for tests that need to call static methods of ClipboardTest. // Hack for tests that need to call static methods of ClipboardTest.
struct NullClipboardTraits { struct NullClipboardTraits {
static Clipboard* Create() { return nullptr; } static Clipboard* Create() { return nullptr; }
...@@ -1014,6 +1046,52 @@ TYPED_TEST(ClipboardTest, WriteImageEmptyParams) { ...@@ -1014,6 +1046,52 @@ TYPED_TEST(ClipboardTest, WriteImageEmptyParams) {
scw.WriteImage(SkBitmap()); scw.WriteImage(SkBitmap());
} }
// DLP is only intended to be used in Chrome OS, so the following DLP related
// tests are only run on Chrome OS.
#if defined(OS_CHROMEOS)
// Test that copy/paste would work normally if the dlp controller didn't
// restrict the clipboard data.
TYPED_TEST(ClipboardTest, DlpAllowDataRead) {
this->AddDlpController();
const base::string16 kTestText(base::UTF8ToUTF16("World"));
{
ScopedClipboardWriter writer(
ClipboardBuffer::kCopyPaste,
std::make_unique<ClipboardDataEndpoint>(GURL()));
writer.WriteText(kTestText);
}
auto* dlp_controller = this->dlp_controller();
EXPECT_CALL(*dlp_controller, IsDataReadAllowed)
.WillRepeatedly(testing::Return(true));
base::string16 read_result;
this->clipboard().ReadText(ClipboardBuffer::kCopyPaste,
/* data_dst = */ nullptr, &read_result);
::testing::Mock::VerifyAndClearExpectations(dlp_controller);
EXPECT_EQ(kTestText, read_result);
}
// Test that pasting clipboard data would not work if the dlp controller
// restricted it.
TYPED_TEST(ClipboardTest, DlpDisallowDataRead) {
this->AddDlpController();
const base::string16 kTestText(base::UTF8ToUTF16("World"));
{
ScopedClipboardWriter writer(
ClipboardBuffer::kCopyPaste,
std::make_unique<ClipboardDataEndpoint>(GURL()));
writer.WriteText(kTestText);
}
auto* dlp_controller = this->dlp_controller();
EXPECT_CALL(*dlp_controller, IsDataReadAllowed)
.WillRepeatedly(testing::Return(false));
base::string16 read_result;
this->clipboard().ReadText(ClipboardBuffer::kCopyPaste,
/* data_dst = */ nullptr, &read_result);
::testing::Mock::VerifyAndClearExpectations(dlp_controller);
EXPECT_EQ(base::string16(), read_result);
}
#endif // defined(OS_CHROMEOS)
} // namespace ui } // namespace ui
#endif // UI_BASE_CLIPBOARD_CLIPBOARD_TEST_TEMPLATE_H_ #endif // UI_BASE_CLIPBOARD_CLIPBOARD_TEST_TEMPLATE_H_
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
#include "ui/base/clipboard/test/test_clipboard.h" #include "ui/base/clipboard/test/test_clipboard.h"
#include <stddef.h> #include <stddef.h>
#include <memory>
#include <utility>
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "base/numerics/safe_conversions.h" #include "base/numerics/safe_conversions.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
...@@ -12,6 +14,7 @@ ...@@ -12,6 +14,7 @@
#include "skia/ext/skia_utils_base.h" #include "skia/ext/skia_utils_base.h"
#include "ui/base/clipboard/clipboard_constants.h" #include "ui/base/clipboard/clipboard_constants.h"
#include "ui/base/clipboard/clipboard_data_endpoint.h" #include "ui/base/clipboard/clipboard_data_endpoint.h"
#include "ui/base/clipboard/clipboard_dlp_controller.h"
#include "ui/base/clipboard/clipboard_monitor.h" #include "ui/base/clipboard/clipboard_monitor.h"
namespace ui { namespace ui {
...@@ -39,15 +42,18 @@ uint64_t TestClipboard::GetSequenceNumber(ClipboardBuffer buffer) const { ...@@ -39,15 +42,18 @@ uint64_t TestClipboard::GetSequenceNumber(ClipboardBuffer buffer) const {
return GetStore(buffer).sequence_number; return GetStore(buffer).sequence_number;
} }
// TODO(crbug.com/1103215): Setting |dlp_controller| should be supported.
void TestClipboard::SetClipboardDlpController( void TestClipboard::SetClipboardDlpController(
std::unique_ptr<ClipboardDlpController> dlp_controller) {} std::unique_ptr<ClipboardDlpController> dlp_controller) {
dlp_controller_ = std::move(dlp_controller);
}
// TODO(crbug.com/1103215): |data_dst| should be supported.
bool TestClipboard::IsFormatAvailable( bool TestClipboard::IsFormatAvailable(
const ClipboardFormatType& format, const ClipboardFormatType& format,
ClipboardBuffer buffer, ClipboardBuffer buffer,
const ui::ClipboardDataEndpoint* data_dst) const { const ui::ClipboardDataEndpoint* data_dst) const {
if (dlp_controller_ && !dlp_controller_->IsDataReadAllowed(
GetStore(buffer).data_src.get(), data_dst))
return false;
#if defined(OS_LINUX) #if defined(OS_LINUX)
// The linux clipboard treats the presence of text on the clipboard // The linux clipboard treats the presence of text on the clipboard
// as the url format being available. // as the url format being available.
...@@ -63,13 +69,15 @@ void TestClipboard::Clear(ClipboardBuffer buffer) { ...@@ -63,13 +69,15 @@ void TestClipboard::Clear(ClipboardBuffer buffer) {
GetStore(buffer).Clear(); GetStore(buffer).Clear();
} }
// TODO(crbug.com/1103215): |data_dst| should be supported.
void TestClipboard::ReadAvailableTypes( void TestClipboard::ReadAvailableTypes(
ClipboardBuffer buffer, ClipboardBuffer buffer,
const ClipboardDataEndpoint* data_dst, const ClipboardDataEndpoint* data_dst,
std::vector<base::string16>* types) const { std::vector<base::string16>* types) const {
DCHECK(types); DCHECK(types);
types->clear(); types->clear();
if (dlp_controller_ && !dlp_controller_->IsDataReadAllowed(
GetStore(buffer).data_src.get(), data_dst))
return;
if (IsFormatAvailable(ClipboardFormatType::GetPlainTextType(), buffer, if (IsFormatAvailable(ClipboardFormatType::GetPlainTextType(), buffer,
data_dst)) data_dst))
...@@ -83,12 +91,16 @@ void TestClipboard::ReadAvailableTypes( ...@@ -83,12 +91,16 @@ void TestClipboard::ReadAvailableTypes(
types->push_back(base::UTF8ToUTF16(kMimeTypePNG)); types->push_back(base::UTF8ToUTF16(kMimeTypePNG));
} }
// TODO(crbug.com/1103215): |data_dst| should be supported.
std::vector<base::string16> std::vector<base::string16>
TestClipboard::ReadAvailablePlatformSpecificFormatNames( TestClipboard::ReadAvailablePlatformSpecificFormatNames(
ClipboardBuffer buffer, ClipboardBuffer buffer,
const ui::ClipboardDataEndpoint* data_dst) const { const ui::ClipboardDataEndpoint* data_dst) const {
const auto& data = GetStore(buffer).data; const DataStore& store = GetStore(buffer);
if (dlp_controller_ &&
!dlp_controller_->IsDataReadAllowed(store.data_src.get(), data_dst))
return {};
const auto& data = store.data;
std::vector<base::string16> types; std::vector<base::string16> types;
types.reserve(data.size()); types.reserve(data.size());
for (const auto& it : data) for (const auto& it : data)
...@@ -113,10 +125,13 @@ TestClipboard::ReadAvailablePlatformSpecificFormatNames( ...@@ -113,10 +125,13 @@ TestClipboard::ReadAvailablePlatformSpecificFormatNames(
return types; return types;
} }
// TODO(crbug.com/1103215): |data_dst| should be supported.
void TestClipboard::ReadText(ClipboardBuffer buffer, void TestClipboard::ReadText(ClipboardBuffer buffer,
const ClipboardDataEndpoint* data_dst, const ClipboardDataEndpoint* data_dst,
base::string16* result) const { base::string16* result) const {
if (dlp_controller_ && !dlp_controller_->IsDataReadAllowed(
GetStore(buffer).data_src.get(), data_dst))
return;
std::string result8; std::string result8;
ReadAsciiText(buffer, data_dst, &result8); ReadAsciiText(buffer, data_dst, &result8);
*result = base::UTF8ToUTF16(result8); *result = base::UTF8ToUTF16(result8);
...@@ -126,23 +141,30 @@ void TestClipboard::ReadText(ClipboardBuffer buffer, ...@@ -126,23 +141,30 @@ void TestClipboard::ReadText(ClipboardBuffer buffer,
void TestClipboard::ReadAsciiText(ClipboardBuffer buffer, void TestClipboard::ReadAsciiText(ClipboardBuffer buffer,
const ClipboardDataEndpoint* data_dst, const ClipboardDataEndpoint* data_dst,
std::string* result) const { std::string* result) const {
result->clear();
const DataStore& store = GetStore(buffer); const DataStore& store = GetStore(buffer);
if (dlp_controller_ &&
!dlp_controller_->IsDataReadAllowed(store.data_src.get(), data_dst))
return;
result->clear();
auto it = store.data.find(ClipboardFormatType::GetPlainTextType()); auto it = store.data.find(ClipboardFormatType::GetPlainTextType());
if (it != store.data.end()) if (it != store.data.end())
*result = it->second; *result = it->second;
} }
// TODO(crbug.com/1103215): |data_dst| should be supported.
void TestClipboard::ReadHTML(ClipboardBuffer buffer, void TestClipboard::ReadHTML(ClipboardBuffer buffer,
const ClipboardDataEndpoint* data_dst, const ClipboardDataEndpoint* data_dst,
base::string16* markup, base::string16* markup,
std::string* src_url, std::string* src_url,
uint32_t* fragment_start, uint32_t* fragment_start,
uint32_t* fragment_end) const { uint32_t* fragment_end) const {
const DataStore& store = GetStore(buffer);
if (dlp_controller_ &&
!dlp_controller_->IsDataReadAllowed(store.data_src.get(), data_dst))
return;
markup->clear(); markup->clear();
src_url->clear(); src_url->clear();
const DataStore& store = GetStore(buffer);
auto it = store.data.find(ClipboardFormatType::GetHtmlType()); auto it = store.data.find(ClipboardFormatType::GetHtmlType());
if (it != store.data.end()) if (it != store.data.end())
*markup = base::UTF8ToUTF16(it->second); *markup = base::UTF8ToUTF16(it->second);
...@@ -151,18 +173,20 @@ void TestClipboard::ReadHTML(ClipboardBuffer buffer, ...@@ -151,18 +173,20 @@ void TestClipboard::ReadHTML(ClipboardBuffer buffer,
*fragment_end = base::checked_cast<uint32_t>(markup->size()); *fragment_end = base::checked_cast<uint32_t>(markup->size());
} }
// TODO(crbug.com/1103215): |data_dst| should be supported.
void TestClipboard::ReadRTF(ClipboardBuffer buffer, void TestClipboard::ReadRTF(ClipboardBuffer buffer,
const ClipboardDataEndpoint* data_dst, const ClipboardDataEndpoint* data_dst,
std::string* result) const { std::string* result) const {
result->clear();
const DataStore& store = GetStore(buffer); const DataStore& store = GetStore(buffer);
if (dlp_controller_ &&
!dlp_controller_->IsDataReadAllowed(store.data_src.get(), data_dst))
return;
result->clear();
auto it = store.data.find(ClipboardFormatType::GetRtfType()); auto it = store.data.find(ClipboardFormatType::GetRtfType());
if (it != store.data.end()) if (it != store.data.end())
*result = it->second; *result = it->second;
} }
// TODO(crbug.com/1103215): |data_dst| should be supported.
void TestClipboard::ReadImage(ClipboardBuffer buffer, void TestClipboard::ReadImage(ClipboardBuffer buffer,
const ClipboardDataEndpoint* data_dst, const ClipboardDataEndpoint* data_dst,
ReadImageCallback callback) const { ReadImageCallback callback) const {
...@@ -180,6 +204,10 @@ void TestClipboard::ReadBookmark(const ClipboardDataEndpoint* data_dst, ...@@ -180,6 +204,10 @@ void TestClipboard::ReadBookmark(const ClipboardDataEndpoint* data_dst,
base::string16* title, base::string16* title,
std::string* url) const { std::string* url) const {
const DataStore& store = GetDefaultStore(); const DataStore& store = GetDefaultStore();
if (dlp_controller_ &&
!dlp_controller_->IsDataReadAllowed(store.data_src.get(), data_dst))
return;
if (url) { if (url) {
auto it = store.data.find(ClipboardFormatType::GetUrlType()); auto it = store.data.find(ClipboardFormatType::GetUrlType());
if (it != store.data.end()) if (it != store.data.end())
...@@ -189,12 +217,15 @@ void TestClipboard::ReadBookmark(const ClipboardDataEndpoint* data_dst, ...@@ -189,12 +217,15 @@ void TestClipboard::ReadBookmark(const ClipboardDataEndpoint* data_dst,
*title = base::UTF8ToUTF16(store.url_title); *title = base::UTF8ToUTF16(store.url_title);
} }
// TODO(crbug.com/1103215): |data_dst| should be supported.
void TestClipboard::ReadData(const ClipboardFormatType& format, void TestClipboard::ReadData(const ClipboardFormatType& format,
const ClipboardDataEndpoint* data_dst, const ClipboardDataEndpoint* data_dst,
std::string* result) const { std::string* result) const {
result->clear();
const DataStore& store = GetDefaultStore(); const DataStore& store = GetDefaultStore();
if (dlp_controller_ &&
!dlp_controller_->IsDataReadAllowed(store.data_src.get(), data_dst))
return;
result->clear();
auto it = store.data.find(format); auto it = store.data.find(format);
if (it != store.data.end()) if (it != store.data.end())
*result = it->second; *result = it->second;
...@@ -208,7 +239,6 @@ void TestClipboard::ClearLastModifiedTime() { ...@@ -208,7 +239,6 @@ void TestClipboard::ClearLastModifiedTime() {
last_modified_time_ = base::Time(); last_modified_time_ = base::Time();
} }
// TODO(crbug.com/1103215): |data_src| should be supported
void TestClipboard::WritePortableRepresentations( void TestClipboard::WritePortableRepresentations(
ClipboardBuffer buffer, ClipboardBuffer buffer,
const ObjectMap& objects, const ObjectMap& objects,
...@@ -218,9 +248,9 @@ void TestClipboard::WritePortableRepresentations( ...@@ -218,9 +248,9 @@ void TestClipboard::WritePortableRepresentations(
for (const auto& kv : objects) for (const auto& kv : objects)
DispatchPortableRepresentation(kv.first, kv.second); DispatchPortableRepresentation(kv.first, kv.second);
default_store_buffer_ = ClipboardBuffer::kCopyPaste; default_store_buffer_ = ClipboardBuffer::kCopyPaste;
GetStore(buffer).SetDataSource(std::move(data_src));
} }
// TODO(crbug.com/1103215): |data_src| should be supported
void TestClipboard::WritePlatformRepresentations( void TestClipboard::WritePlatformRepresentations(
ClipboardBuffer buffer, ClipboardBuffer buffer,
std::vector<Clipboard::PlatformRepresentation> platform_representations, std::vector<Clipboard::PlatformRepresentation> platform_representations,
...@@ -229,6 +259,7 @@ void TestClipboard::WritePlatformRepresentations( ...@@ -229,6 +259,7 @@ void TestClipboard::WritePlatformRepresentations(
default_store_buffer_ = buffer; default_store_buffer_ = buffer;
DispatchPlatformRepresentations(std::move(platform_representations)); DispatchPlatformRepresentations(std::move(platform_representations));
default_store_buffer_ = ClipboardBuffer::kCopyPaste; default_store_buffer_ = ClipboardBuffer::kCopyPaste;
GetStore(buffer).SetDataSource(std::move(data_src));
} }
void TestClipboard::WriteText(const char* text_data, size_t text_len) { void TestClipboard::WriteText(const char* text_data, size_t text_len) {
...@@ -292,9 +323,31 @@ void TestClipboard::WriteData(const ClipboardFormatType& format, ...@@ -292,9 +323,31 @@ void TestClipboard::WriteData(const ClipboardFormatType& format,
GetDefaultStore().data[format] = std::string(data_data, data_len); GetDefaultStore().data[format] = std::string(data_data, data_len);
} }
TestClipboard::DataStore::DataStore() : sequence_number(0) {} TestClipboard::DataStore::DataStore() = default;
TestClipboard::DataStore::DataStore(const DataStore& other) {
sequence_number = other.sequence_number;
data = other.data;
url_title = other.url_title;
html_src_url = other.html_src_url;
image = other.image;
data_src = other.data_src ? std::make_unique<ClipboardDataEndpoint>(
ClipboardDataEndpoint(*(other.data_src)))
: nullptr;
}
TestClipboard::DataStore::DataStore(const DataStore& other) = default; TestClipboard::DataStore& TestClipboard::DataStore::operator=(
const DataStore& other) {
sequence_number = other.sequence_number;
data = other.data;
url_title = other.url_title;
html_src_url = other.html_src_url;
image = other.image;
data_src = other.data_src ? std::make_unique<ClipboardDataEndpoint>(
ClipboardDataEndpoint(*(other.data_src)))
: nullptr;
return *this;
}
TestClipboard::DataStore::~DataStore() = default; TestClipboard::DataStore::~DataStore() = default;
...@@ -305,6 +358,11 @@ void TestClipboard::DataStore::Clear() { ...@@ -305,6 +358,11 @@ void TestClipboard::DataStore::Clear() {
image = SkBitmap(); image = SkBitmap();
} }
void TestClipboard::DataStore::SetDataSource(
std::unique_ptr<ClipboardDataEndpoint> data_src) {
this->data_src = std::move(data_src);
}
const TestClipboard::DataStore& TestClipboard::GetStore( const TestClipboard::DataStore& TestClipboard::GetStore(
ClipboardBuffer buffer) const { ClipboardBuffer buffer) const {
CHECK(IsSupportedClipboardBuffer(buffer)); CHECK(IsSupportedClipboardBuffer(buffer));
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
...@@ -105,13 +106,16 @@ class TestClipboard : public Clipboard { ...@@ -105,13 +106,16 @@ class TestClipboard : public Clipboard {
struct DataStore { struct DataStore {
DataStore(); DataStore();
DataStore(const DataStore& other); DataStore(const DataStore& other);
DataStore& operator=(const DataStore& other);
~DataStore(); ~DataStore();
void Clear(); void Clear();
uint64_t sequence_number; void SetDataSource(std::unique_ptr<ClipboardDataEndpoint> data_src);
uint64_t sequence_number = 0;
base::flat_map<ClipboardFormatType, std::string> data; base::flat_map<ClipboardFormatType, std::string> data;
std::string url_title; std::string url_title;
std::string html_src_url; std::string html_src_url;
SkBitmap image; SkBitmap image;
std::unique_ptr<ClipboardDataEndpoint> data_src = nullptr;
}; };
// The non-const versions increment the sequence number as a side effect. // The non-const versions increment the sequence number as a side effect.
...@@ -124,6 +128,8 @@ class TestClipboard : public Clipboard { ...@@ -124,6 +128,8 @@ class TestClipboard : public Clipboard {
mutable base::flat_map<ClipboardBuffer, DataStore> stores_; mutable base::flat_map<ClipboardBuffer, DataStore> stores_;
base::Time last_modified_time_; base::Time last_modified_time_;
std::unique_ptr<ClipboardDlpController> dlp_controller_;
DISALLOW_COPY_AND_ASSIGN(TestClipboard); DISALLOW_COPY_AND_ASSIGN(TestClipboard);
}; };
......
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