//////////////////////////////////////////////////////////////////////////////// /// DISCLAIMER /// /// Copyright 2018 ArangoDB GmbH, Cologne, Germany /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// /// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @author Frank Celler //////////////////////////////////////////////////////////////////////////////// #include "Maskings.h" #include #include "Basics/FileUtils.h" #include "Logger/Logger.h" #include "Random/RandomGenerator.h" #include #include #include using namespace arangodb; using namespace arangodb::maskings; MaskingsResult Maskings::fromFile(std::string const& filename) { std::string definition; try { definition = basics::FileUtils::slurp(filename); } catch (std::exception const& e) { std::string msg = "cannot read maskings file '" + filename + "': " + e.what(); LOG_TOPIC("379fe", DEBUG, Logger::CONFIG) << msg; return MaskingsResult(MaskingsResult::CANNOT_READ_FILE, msg); } LOG_TOPIC("fe73b", DEBUG, Logger::CONFIG) << "found maskings file '" << filename; if (definition.empty()) { std::string msg = "maskings file '" + filename + "' is empty"; LOG_TOPIC("5018d", DEBUG, Logger::CONFIG) << msg; return MaskingsResult(MaskingsResult::CANNOT_READ_FILE, msg); } std::unique_ptr maskings(new Maskings{}); maskings.get()->_randomSeed = RandomGenerator::interval(UINT64_MAX); try { std::shared_ptr parsed = velocypack::Parser::fromJson(definition); ParseResult res = maskings->parse(parsed->slice()); if (res.status != ParseResult::VALID) { return MaskingsResult(MaskingsResult::ILLEGAL_DEFINITION, res.message); } return MaskingsResult(std::move(maskings)); } catch (velocypack::Exception const& e) { std::string msg = "cannot parse maskings file '" + filename + "': " + e.what(); LOG_TOPIC("5cb4c", DEBUG, Logger::CONFIG) << msg << ". file content: " << definition; return MaskingsResult(MaskingsResult::CANNOT_PARSE_FILE, msg); } } ParseResult Maskings::parse(VPackSlice const& def) { if (!def.isObject()) { return ParseResult(ParseResult::DUPLICATE_COLLECTION, "expecting an object for masking definition"); } for (auto const& entry : VPackObjectIterator(def, false)) { std::string key = entry.key.copyString(); if (key == "*") { LOG_TOPIC("b0d99", TRACE, Logger::CONFIG) << "default masking"; if (_hasDefaultCollection) { return ParseResult(ParseResult::DUPLICATE_COLLECTION, "duplicate default entry"); } } else { LOG_TOPIC("f5aac", TRACE, Logger::CONFIG) << "masking collection '" << key << "'"; if (_collections.find(key) != _collections.end()) { return ParseResult(ParseResult::DUPLICATE_COLLECTION, "duplicate collection entry '" + key + "'"); } } ParseResult c = Collection::parse(this, entry.value); if (c.status != ParseResult::VALID) { return ParseResult((ParseResult::StatusCode)(int)c.status, c.message); } if (key == "*") { _hasDefaultCollection = true; _defaultCollection = c.result; } else { _collections[key] = c.result; } } return ParseResult(ParseResult::VALID); } bool Maskings::shouldDumpStructure(std::string const& name) { CollectionSelection select = CollectionSelection::EXCLUDE; auto const itr = _collections.find(name); if (itr == _collections.end()) { if (_hasDefaultCollection) { select = _defaultCollection.selection(); } } else { select = itr->second.selection(); } switch (select) { case CollectionSelection::FULL: return true; case CollectionSelection::MASKED: return true; case CollectionSelection::EXCLUDE: return false; case CollectionSelection::STRUCTURE: return true; } // should not get here. however, compiler warns about it TRI_ASSERT(false); return false; } bool Maskings::shouldDumpData(std::string const& name) { CollectionSelection select = CollectionSelection::EXCLUDE; auto const itr = _collections.find(name); if (itr == _collections.end()) { if (_hasDefaultCollection) { select = _defaultCollection.selection(); } } else { select = itr->second.selection(); } switch (select) { case CollectionSelection::FULL: return true; case CollectionSelection::MASKED: return true; case CollectionSelection::EXCLUDE: return false; case CollectionSelection::STRUCTURE: return false; } // should not get here. however, compiler warns about it TRI_ASSERT(false); return false; } VPackValue Maskings::maskedItem(Collection& collection, std::vector& path, std::string& buffer, VPackSlice const& data) { static std::string xxxx("xxxx"); if (path.size() == 1 && path[0].size() >= 1 && path[0][0] == '_') { if (data.isString()) { velocypack::ValueLength length; char const* c = data.getString(length); buffer = std::string(c, length); return VPackValue(buffer); } else if (data.isInteger()) { return VPackValue(data.getInt()); } } MaskingFunction* func = collection.masking(path); if (func == nullptr) { if (data.isBool()) { return VPackValue(data.getBool()); } else if (data.isString()) { velocypack::ValueLength length; char const* c = data.getString(length); buffer = std::string(c, length); return VPackValue(buffer); } else if (data.isInteger()) { return VPackValue(data.getInt()); } else if (data.isDouble()) { return VPackValue(data.getDouble()); } else { return VPackValue(VPackValueType::Null); } } else { if (data.isBool()) { return func->mask(data.getBool(), buffer); } else if (data.isString()) { velocypack::ValueLength length; char const* c = data.getString(length); return func->mask(std::string(c, length), buffer); } else if (data.isInteger()) { return func->mask(data.getInt(), buffer); } else if (data.isDouble()) { return func->mask(data.getDouble(), buffer); } else { return VPackValue(VPackValueType::Null); } } return VPackValue(xxxx); } void Maskings::addMaskedArray(Collection& collection, VPackBuilder& builder, std::vector& path, VPackSlice const& data) { for (auto const& entry : VPackArrayIterator(data)) { if (entry.isObject()) { VPackObjectBuilder ob(&builder); addMaskedObject(collection, builder, path, entry); } else if (entry.isArray()) { VPackArrayBuilder ap(&builder); addMaskedArray(collection, builder, path, entry); } else { std::string buffer; builder.add(maskedItem(collection, path, buffer, entry)); } } } void Maskings::addMaskedObject(Collection& collection, VPackBuilder& builder, std::vector& path, VPackSlice const& data) { for (auto const& entry : VPackObjectIterator(data, false)) { std::string key = entry.key.copyString(); VPackSlice const& value = entry.value; path.push_back(key); if (value.isObject()) { VPackObjectBuilder ob(&builder, key); addMaskedObject(collection, builder, path, value); } else if (value.isArray()) { VPackArrayBuilder ap(&builder, key); addMaskedArray(collection, builder, path, value); } else { std::string buffer; builder.add(key, maskedItem(collection, path, buffer, value)); } path.pop_back(); } } void Maskings::addMasked(Collection& collection, VPackBuilder& builder, VPackSlice const& data) { if (!data.isObject()) { return; } std::vector path; std::string dataStr("data"); VPackObjectBuilder ob(&builder, dataStr); addMaskedObject(collection, builder, path, data); } void Maskings::addMasked(Collection& collection, basics::StringBuffer& data, VPackSlice const& slice) { if (!slice.isObject()) { return; } velocypack::StringRef dataStrRef("data"); VPackBuilder builder; { VPackObjectBuilder ob(&builder); for (auto const& entry : VPackObjectIterator(slice, false)) { velocypack::StringRef key = entry.key.stringRef(); if (key.equals(dataStrRef)) { addMasked(collection, builder, entry.value); } else { builder.add(key, entry.value); } } } std::string masked = builder.toJson(); data.appendText(masked); data.appendText("\n"); } void Maskings::mask(std::string const& name, basics::StringBuffer const& data, basics::StringBuffer& result) { result.clear(); Collection* collection; auto const itr = _collections.find(name); if (itr == _collections.end()) { if (_hasDefaultCollection) { collection = &_defaultCollection; } else { result.copy(data); return; } } else { collection = &(itr->second); } if (collection->selection() == CollectionSelection::FULL) { result.copy(data); return; } result.reserve(data.length()); char const* p = data.c_str(); char const* e = p + data.length(); char const* q = p; while (p < e) { while (p < e && (*p != '\n' && *p != '\r')) { ++p; } std::shared_ptr builder = VPackParser::fromJson(q, p - q); addMasked(*collection, result, builder->slice()); while (p < e && (*p == '\n' || *p == '\r')) { ++p; } q = p; } }