1 /*
2  *     Copyright 2017-Present Couchbase, Inc.
3  *
4  *   Use of this software is governed by the Business Source License included
5  *   in the file licenses/BSL-Couchbase.txt.  As of the Change Date specified
6  *   in that file, in accordance with the Business Source License, use of this
7  *   software will be governed by the Apache License, Version 2.0, included in
8  *   the file licenses/APL2.txt.
9  */
10 
11 #include "testapp.h"
12 #include "testapp_client_test.h"
13 
14 #include <protocol/connection/client_mcbp_commands.h>
15 #include <xattr/blob.h>
16 #include <xattr/utils.h>
17 
18 class WithMetaTest : public TestappXattrClientTest {
19 public:
20     void SetUp() override {
21         TestappXattrClientTest::SetUp();
22         document.info.cas = testCas; // Must have a cas for meta ops
23     }
24 
25     /**
26      * Check the CAS of the set document against our value
27      * using vattr for the lookup
28      */
29     void checkCas() {
30         BinprotSubdocCommand cmd;
31         cmd.setOp(cb::mcbp::ClientOpcode::SubdocGet);
32         cmd.setKey(name);
33         cmd.setPath("$document");
34         cmd.addPathFlags(SUBDOC_FLAG_XATTR_PATH);
35         cmd.addDocFlags(cb::mcbp::subdoc::doc_flag::None);
36 
37         auto resp = userConnection->execute(cmd);
38 
39         ASSERT_EQ(cb::mcbp::Status::Success, resp.getStatus());
40         auto json = nlohmann::json::parse(resp.getDataString());
41         EXPECT_STREQ(testCasStr, json["CAS"].get<std::string>().c_str());
42     }
43 
44     /**
45      * Make ::document an xattr value
46      */
47     void makeDocumentXattrValue() {
48         cb::xattr::Blob blob;
49         blob.set("user", R"({"author":"bubba"})");
50         blob.set("meta", R"({"content-type":"text"})");
51 
52         auto xattrValue = blob.finalize();
53 
54         // append body to the xattrs and store in data
55         std::string body = "document_body";
56         document.value = xattrValue;
57         document.value += body;
58         document.info.datatype = cb::mcbp::Datatype::Xattr;
59 
60         if (hasSnappySupport() == ClientSnappySupport::Yes) {
61             document.compress();
62         }
63     }
64 
65 protected:
66     /**
67      * Test that DelWithMeta accepts user-xattrs in the payload.
68      *
69      * @param allowValuePruning Whether the engine sanitizes bad user payloads
70      *  or fails the request
71      * @param compressed Whether the payload is compressed
72      */
73     void testDeleteWithMetaAcceptsUserXattrs(bool allowValuePruning,
74                                              bool compressed = false);
75 
76     /**
77      * Test that DelWithMeta rejects body in the payload.
78      *
79      * @param allowValuePruning Whether the engine sanitizes bad user payloads
80      *  or fails the request
81      * @param dtXattr Whether the value under test is DT Xattr
82      */
83     void testDeleteWithMetaRejectsBody(bool allowValuePruning, bool dtXattr);
84 
85     const uint64_t testCas = 0xb33ff00dcafef00dull;
86     const char* testCasStr = "0xb33ff00dcafef00d";
87 };
88 
89 INSTANTIATE_TEST_SUITE_P(
90         TransportProtocols,
91         WithMetaTest,
92         ::testing::Combine(::testing::Values(TransportProtocols::McbpSsl),
93                            ::testing::Values(XattrSupport::Yes,
94                                              XattrSupport::No),
95                            ::testing::Values(ClientJSONSupport::Yes,
96                                              ClientJSONSupport::No),
97                            ::testing::Values(ClientSnappySupport::Yes,
98                                              ClientSnappySupport::No)),
99         PrintToStringCombinedName());
100 
101 TEST_P(WithMetaTest, basicSet) {
102     TESTAPP_SKIP_IF_UNSUPPORTED(cb::mcbp::ClientOpcode::SetWithMeta);
103 
104     MutationInfo resp;
105     try {
106         resp = userConnection->mutateWithMeta(document,
107                                               Vbid(0),
108                                               cb::mcbp::cas::Wildcard,
109                                               /*seqno*/ 1,
110                                               /*options*/ 0,
111                                               {});
112     } catch (std::exception&) {
113         FAIL() << "mutateWithMeta threw an exception";
114     }
115 
116     if (::testing::get<1>(GetParam()) == XattrSupport::Yes) {
117         checkCas();
118     }
119 }
120 
121 TEST_P(WithMetaTest, basicSetXattr) {
122     TESTAPP_SKIP_IF_UNSUPPORTED(cb::mcbp::ClientOpcode::SetWithMeta);
123     makeDocumentXattrValue();
124 
125     MutationInfo resp;
126     try {
127         resp = userConnection->mutateWithMeta(document,
128                                               Vbid(0),
129                                               cb::mcbp::cas::Wildcard,
130                                               /*seqno*/ 1,
131                                               /*options*/ 0,
132                                               {});
133         EXPECT_EQ(XattrSupport::Yes, ::testing::get<1>(GetParam()));
134         EXPECT_EQ(testCas, resp.cas);
135     } catch (std::exception&) {
136         EXPECT_EQ(XattrSupport::No, ::testing::get<1>(GetParam()));
137     }
138 
139     if (::testing::get<1>(GetParam()) == XattrSupport::Yes) {
140         checkCas();
141     }
142 }
143 
144 TEST_P(WithMetaTest, MB36304_DocumetTooBig) {
145     TESTAPP_SKIP_IF_UNSUPPORTED(cb::mcbp::ClientOpcode::SetWithMeta);
146     if (::testing::get<3>(GetParam()) == ClientSnappySupport::No) {
147         return;
148     }
149 
150     document.info.datatype = cb::mcbp::Datatype::Raw;
151     document.value.clear();
152     document.value.resize(21 * 1024 * 1024);
153     document.compress();
154     try {
155         userConnection->mutateWithMeta(
156                 document, Vbid(0), cb::mcbp::cas::Wildcard, 1, 0, {});
157         FAIL() << "It should not be possible to store documents which exceeds "
158                   "the max document size";
159     } catch (const ConnectionError& error) {
160         EXPECT_EQ(cb::mcbp::Status::E2big, error.getReason());
161     }
162 }
163 
164 TEST_P(WithMetaTest, MB36304_DocumentMaxSizeWithXattr) {
165     TESTAPP_SKIP_IF_UNSUPPORTED(cb::mcbp::ClientOpcode::SetWithMeta);
166     if (::testing::get<1>(GetParam()) == XattrSupport::No) {
167         return;
168     }
169 
170     document.value.clear();
171     cb::xattr::Blob blob;
172     blob.set("_sys", R"({"author":"bubba"})");
173     auto xattrValue = blob.finalize();
174     std::copy(xattrValue.begin(),
175               xattrValue.end(),
176               std::back_inserter(document.value));
177 
178     document.value.resize((20 * 1024 * 1024) + xattrValue.size());
179     document.info.datatype = cb::mcbp::Datatype::Xattr;
180     userConnection->mutateWithMeta(
181             document, Vbid(0), cb::mcbp::cas::Wildcard, 1, 0, {});
182     userConnection->remove(name, Vbid(0));
183 }
184 
185 TEST_P(WithMetaTest, MB36321_DeleteWithMetaRefuseUserXattrs) {
186     TESTAPP_SKIP_IF_UNSUPPORTED(cb::mcbp::ClientOpcode::DelWithMeta);
187     if (::testing::get<1>(GetParam()) == XattrSupport::No ||
188         ::testing::get<2>(GetParam()) == ClientJSONSupport::No) {
189         return;
190     }
191 
192     adminConnection->executeInBucket(bucketName, [](auto& connection) {
193         const auto setParam = BinprotSetParamCommand(
194                 cb::mcbp::request::SetParamPayload::Type::Flush,
195                 "allow_sanitize_value_in_deletion",
196                 "false");
197         const auto resp = BinprotMutationResponse(connection.execute(setParam));
198         ASSERT_EQ(cb::mcbp::Status::Success, resp.getStatus());
199     });
200 
201     cb::xattr::Blob blob;
202     blob.set("user", R"({"band":"Steel Panther"})");
203     auto xattrValue = blob.finalize();
204 
205     document.value.clear();
206     std::copy(xattrValue.begin(),
207               xattrValue.end(),
208               std::back_inserter(document.value));
209     document.value.append(R"({"Bug":"MB-36321"})");
210     using cb::mcbp::Datatype;
211     document.info.datatype =
212             Datatype(uint8_t(Datatype::Xattr) | uint8_t(Datatype::JSON));
213 
214     BinprotDelWithMetaCommand cmd(document, Vbid(0), 0, 0, 1, 0);
215     const auto rsp = BinprotMutationResponse(userConnection->execute(cmd));
216     ASSERT_FALSE(rsp.isSuccess()) << rsp.getStatus();
217     auto error = nlohmann::json::parse(rsp.getDataString());
218 
219     ASSERT_EQ(
220             "It is only possible to specify Xattrs as a value to "
221             "DeleteWithMeta",
222             error["error"]["context"]);
223 }
224 
225 TEST_P(WithMetaTest, MB36321_DeleteWithMetaAllowSystemXattrs) {
226     TESTAPP_SKIP_IF_UNSUPPORTED(cb::mcbp::ClientOpcode::DelWithMeta);
227     if (::testing::get<1>(GetParam()) == XattrSupport::No ||
228         ::testing::get<2>(GetParam()) == ClientJSONSupport::No) {
229         return;
230     }
231 
232     cb::xattr::Blob blob;
233     blob.set("_sys", R"({"author":"bubba"})");
234     auto xattrValue = blob.finalize();
235 
236     document.value.clear();
237     std::copy(xattrValue.begin(),
238               xattrValue.end(),
239               std::back_inserter(document.value));
240     document.info.datatype = cb::mcbp::Datatype::Xattr;
241 
242     BinprotDelWithMetaCommand cmd(document, Vbid(0), 0, 0, 1, 0);
243     const auto rsp = BinprotMutationResponse(userConnection->execute(cmd));
244     ASSERT_TRUE(rsp.isSuccess()) << rsp.getStatus();
245 
246     // The system xattr should be there
247     auto sresp = subdoc(cb::mcbp::ClientOpcode::SubdocGet,
248                         name,
249                         "_sys.author",
250                         {},
251                         SUBDOC_FLAG_XATTR_PATH,
252                         cb::mcbp::subdoc::doc_flag::AccessDeleted);
253     EXPECT_TRUE(sresp.isSuccess()) << sresp.getStatus();
254 }
255 
256 void WithMetaTest::testDeleteWithMetaAcceptsUserXattrs(bool allowValuePruning,
257                                                        bool compressed) {
258     TESTAPP_SKIP_IF_UNSUPPORTED(cb::mcbp::ClientOpcode::DelWithMeta);
259     if (::testing::get<1>(GetParam()) == XattrSupport::No ||
260         ::testing::get<2>(GetParam()) == ClientJSONSupport::No) {
261         return;
262     }
263 
264     adminConnection->executeInBucket(bucketName, [&](auto& connection) {
265         const auto setParam = BinprotSetParamCommand(
266                 cb::mcbp::request::SetParamPayload::Type::Flush,
267                 "allow_sanitize_value_in_deletion",
268                 allowValuePruning ? "true" : "false");
269         const auto resp = BinprotMutationResponse(connection.execute(setParam));
270         ASSERT_EQ(cb::mcbp::Status::Success, resp.getStatus());
271     });
272 
273     // Value with User/Sys Xattrs and no Body
274     cb::xattr::Blob blob;
275     blob.set("user", R"({"a":"b"})");
276     blob.set("_sys", R"({"c":"d"})");
277     const auto xattrs = blob.finalize();
278     document.value.clear();
279     std::copy(xattrs.begin(), xattrs.end(), std::back_inserter(document.value));
280     using Datatype = cb::mcbp::Datatype;
281     document.info.datatype = Datatype::Xattr;
282 
283     if (compressed) {
284         document.compress();
285     }
286 
287     BinprotDelWithMetaCommand delWithMeta(
288             document, Vbid(0), 0, 0 /*delTime*/, 1 /*seqno*/, 0 /*opCas*/);
289     const auto resp =
290             BinprotMutationResponse(userConnection->execute(delWithMeta));
291     const auto expectedStatus = compressed && ::testing::get<3>(GetParam()) ==
292                                                         ClientSnappySupport::No
293                                         ? cb::mcbp::Status::Einval
294                                         : cb::mcbp::Status::Success;
295     EXPECT_EQ(expectedStatus, resp.getStatus());
296 }
297 
298 TEST_P(WithMetaTest, DeleteWithMetaAcceptsUserXattrs) {
299     testDeleteWithMetaAcceptsUserXattrs(false);
300 }
301 
302 TEST_P(WithMetaTest, DeleteWithMetaAcceptsUserXattrs_AllowValuePruning) {
303     testDeleteWithMetaAcceptsUserXattrs(true);
304 }
305 
306 TEST_P(WithMetaTest, DeleteWithMetaAcceptsUserXattrs_Compressed) {
307     testDeleteWithMetaAcceptsUserXattrs(false, true);
308 }
309 
310 void WithMetaTest::testDeleteWithMetaRejectsBody(bool allowValuePruning,
311                                                  bool dtXattr) {
312     TESTAPP_SKIP_IF_UNSUPPORTED(cb::mcbp::ClientOpcode::DelWithMeta);
313     if (::testing::get<1>(GetParam()) == XattrSupport::No ||
314         ::testing::get<2>(GetParam()) == ClientJSONSupport::No) {
315         return;
316     }
317 
318     adminConnection->executeInBucket(bucketName, [&](auto& connection) {
319         const auto setParam = BinprotSetParamCommand(
320                 cb::mcbp::request::SetParamPayload::Type::Flush,
321                 "allow_sanitize_value_in_deletion",
322                 allowValuePruning ? "true" : "false");
323         const auto resp = BinprotMutationResponse(connection.execute(setParam));
324         ASSERT_EQ(cb::mcbp::Status::Success, resp.getStatus());
325     });
326 
327     // Value with User/Sys Xattrs and Body
328     if (dtXattr) {
329         cb::xattr::Blob blob;
330         blob.set("user", R"({"a":"b"})");
331         blob.set("_sys", R"({"c":"d"})");
332         const auto xattrs = blob.finalize();
333         document.value.clear();
334         std::copy(xattrs.begin(),
335                   xattrs.end(),
336                   std::back_inserter(document.value));
337         document.value.append("body");
338         document.info.datatype = cb::mcbp::Datatype::Xattr;
339     } else {
340         document.value = "body";
341         document.info.datatype = cb::mcbp::Datatype::Raw;
342     }
343 
344     BinprotDelWithMetaCommand delWithMeta(
345             document, Vbid(0), 0, 0 /*delTime*/, 1 /*seqno*/, 0 /*opCas*/);
346     const auto resp =
347             BinprotMutationResponse(userConnection->execute(delWithMeta));
348     if (allowValuePruning) {
349         ASSERT_EQ(cb::mcbp::Status::Success, resp.getStatus());
350     } else {
351         ASSERT_EQ(cb::mcbp::Status::Einval, resp.getStatus());
352         ASSERT_TRUE(
353                 resp.getDataString().find("only possible to specify Xattrs") !=
354                 std::string::npos);
355     }
356 }
357 
358 TEST_P(WithMetaTest, DeleteWithMetaRejectsBody_AllowValuePruning) {
359     testDeleteWithMetaRejectsBody(true, false);
360 }
361 
362 TEST_P(WithMetaTest, DeleteWithMetaRejectsBody) {
363     testDeleteWithMetaRejectsBody(false, false);
364 }
365 
366 TEST_P(WithMetaTest, DeleteWithMetaRejectsBody_AllowValuePruning_DTXattr) {
367     testDeleteWithMetaRejectsBody(true, true);
368 }
369 
370 TEST_P(WithMetaTest, DeleteWithMetaRejectsBody_DTXattr) {
371     testDeleteWithMetaRejectsBody(false, true);
372 }