mirror of https://gitee.com/bigwinds/arangodb
Feature/maskings (#8006)
This commit is contained in:
parent
6d9e1b7fcf
commit
84802fdc0f
|
@ -0,0 +1,541 @@
|
|||
Arangodump Data Maskings
|
||||
========================
|
||||
|
||||
*--maskings path-of-config*
|
||||
|
||||
It is possible to mask certain fields for a dump. A JSON configuration file is
|
||||
used to define which fields should be masked and how.
|
||||
|
||||
The general structure of the config file looks like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"collection-name": {
|
||||
"type": MASKING_TYPE
|
||||
"maskings" : [
|
||||
MASKING1,
|
||||
MASKING2,
|
||||
...
|
||||
]
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Using `"*"` as collection name defines a default behavior for collections not
|
||||
listed explicitly.
|
||||
|
||||
Masking Types
|
||||
-------------
|
||||
|
||||
`type` is a string describing how to mask the given collection.
|
||||
Possible values are:
|
||||
|
||||
- `"exclude"`: the collection is ignored completely and not even the structure data
|
||||
is dumped.
|
||||
|
||||
- `"structure"`: only the collection structure is dumped, but no data at all
|
||||
|
||||
- `"masked"`: the collection structure and all data is dumped. However, the data
|
||||
is subject to obfuscation defined in the attribute `maskings`.
|
||||
|
||||
- `"full"`: the collection structure and all data is dumped. No masking is
|
||||
applied to this collection at all.
|
||||
|
||||
**Example**
|
||||
|
||||
```json
|
||||
{
|
||||
"private": {
|
||||
"type": "exclude"
|
||||
},
|
||||
|
||||
"log": {
|
||||
"type": "structure"
|
||||
},
|
||||
|
||||
"person": {
|
||||
"type": "masked",
|
||||
"maskings": [
|
||||
{
|
||||
"path": "name",
|
||||
"type": "xifyFront",
|
||||
"unmaskedLength": 2
|
||||
},
|
||||
{
|
||||
"path": ".security_id",
|
||||
"type": "xifyFront",
|
||||
"unmaskedLength": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In the example the collection _private_ is completely ignored. Only the
|
||||
structure of the collection _log_ is dumped, but not the data itself.
|
||||
The collection _person_ is dumped completely but with the _name_ field masked
|
||||
if it occurs on the top-level. It also masks fields with the name "security_id"
|
||||
anywhere in the document. See below for a complete description of the parameters
|
||||
of [type "xifyFront"](#xify-front).
|
||||
|
||||
### Masking vs. dump-data option
|
||||
|
||||
*arangodump* also supports a very coarse masking with the option
|
||||
`--dump-data false`. This basically removes all data from the dump.
|
||||
|
||||
You can either use `--masking` or `--dump-data false`, but not both.
|
||||
|
||||
### Masking vs. include-collection option
|
||||
|
||||
*arangodump* also supports a very coarse masking with the option
|
||||
`--include-collection`. This will restrict the collections that are
|
||||
dumped to the ones explicitly listed.
|
||||
|
||||
It is possible to combine `--masking` and `--include-collection`.
|
||||
This will take the intersection of exportable collections.
|
||||
|
||||
Path
|
||||
----
|
||||
|
||||
If the path starts with a `.` then it is considered to match any path
|
||||
ending in `name`. For example, `.name` will match the attribute name
|
||||
`name` all leaf attributes in the document. Leaf attributes are
|
||||
attributes whose value is `null` or of data type `string`, `number`,
|
||||
`bool` and `array` (see below). `name` will only match leaf attributes
|
||||
at top level. `person.name` will match the attribute `name` of a leaf
|
||||
in the top-level object `person`.
|
||||
|
||||
If you have an attribute name that contains a dot, you need to quote the
|
||||
name with either a tick or a backtick. For example:
|
||||
|
||||
"path": "´name.with.dots´"
|
||||
|
||||
or
|
||||
|
||||
"path": "`name.with.dots`"
|
||||
|
||||
If the attribute value is an array the masking is applied to all the
|
||||
array elements individually.
|
||||
|
||||
**Example**
|
||||
|
||||
The following configuration will replace the value of the "name"
|
||||
attribute with an "XXXX"-masked string:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "xifyFront",
|
||||
"path": ".name",
|
||||
"unmaskedLength": 2
|
||||
}
|
||||
```
|
||||
|
||||
The document:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "top-level-name",
|
||||
"age": 42,
|
||||
"nicknames" : [ { "name": "hugo" }, "egon" ],
|
||||
"other": {
|
||||
"name": [ "emil", { "secret": "superman" } ]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
… will be changed as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "xxxxxxxxxxxxme",
|
||||
"age": 42,
|
||||
"nicknames" : [ { "name": "xxgo" }, "egon" ],
|
||||
"other": {
|
||||
"name": [ "xxil", { "secret": "superman" } ]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The values `"egon"` and `"superman"` are not replaced, because they
|
||||
are not contained in an attribute value of which the attribute name is
|
||||
`name`.
|
||||
|
||||
### Nested objects and arrays
|
||||
|
||||
If you specify a path and the attribute value is an array then the
|
||||
masking decision is applied to each element of the array as if this
|
||||
was the value of the attribute.
|
||||
|
||||
If the attribute value is an object, then the attribute is not masked.
|
||||
Instead the nested object is checked further for leaf attributes.
|
||||
|
||||
**Example**
|
||||
|
||||
Masking `email` will convert:
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"email" : "email address"
|
||||
}
|
||||
```
|
||||
|
||||
… into:
|
||||
|
||||
```json
|
||||
{
|
||||
"email" : "xxil xxxxxxss"
|
||||
}
|
||||
```
|
||||
|
||||
because `email` is a leaf attribute. The document:
|
||||
|
||||
```json
|
||||
{
|
||||
"email" : [
|
||||
"address one",
|
||||
"address two"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
… will be converted into:
|
||||
|
||||
```json
|
||||
{
|
||||
"email" : [
|
||||
"xxxxxss xne",
|
||||
"xxxxxss xwo"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
… because the array is "unfolded". The document:
|
||||
|
||||
```json
|
||||
{
|
||||
"email" : {
|
||||
"address" : "email address"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
… will not be changed because `email` is not a leaf attribute.
|
||||
|
||||
|
||||
Masking Functions
|
||||
-----------------
|
||||
|
||||
{% hint 'info' %}
|
||||
The following masking functions are only available in the
|
||||
[**Enterprise Edition**](https://www.arangodb.com/why-arangodb/arangodb-enterprise/)
|
||||
{% endhint %}
|
||||
|
||||
- xify front
|
||||
- zip
|
||||
- datetime
|
||||
- integral number
|
||||
- decimal number
|
||||
- credit card number
|
||||
- phone number
|
||||
- email address
|
||||
|
||||
The function:
|
||||
|
||||
- random string
|
||||
|
||||
… is available on Community Edition and in the Enterprise Edition.
|
||||
|
||||
|
||||
### Random string
|
||||
|
||||
```json
|
||||
{
|
||||
"path": ".name",
|
||||
"type": "randomString"
|
||||
}
|
||||
```
|
||||
|
||||
This masking type will replace all values of attributes with key
|
||||
`name` with an anonymized string. It is not guaranteed that the string
|
||||
will be of the same length.
|
||||
|
||||
A hash of the original string is computed. If the original string is
|
||||
shorter then the hash will be used. This will result in a longer
|
||||
replacement string. If the string is longer than the hash then
|
||||
characters will be repeated as many times as needed to reach the full
|
||||
original string length.
|
||||
|
||||
**Example**
|
||||
|
||||
Masking name as above, the document:
|
||||
|
||||
```json
|
||||
{
|
||||
"_key" : "38937",
|
||||
"_id" : "examplecollection/38937",
|
||||
"_rev" : "_YFaGG1u--_",
|
||||
"name" : [
|
||||
"My Name",
|
||||
{
|
||||
"other" : "Hallo Name"
|
||||
},
|
||||
[
|
||||
"Name One",
|
||||
"Name Two"
|
||||
],
|
||||
true,
|
||||
false,
|
||||
null,
|
||||
1.0,
|
||||
1234,
|
||||
"This is a very long name"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
… will be converted into
|
||||
|
||||
```json
|
||||
{
|
||||
"_key": "38937",
|
||||
"_id": "examplecollection/38937",
|
||||
"_rev": "_YFaGG1u--_",
|
||||
"name": [
|
||||
"+y5OQiYmp/o=",
|
||||
{
|
||||
"other": "Hallo Name"
|
||||
},
|
||||
[
|
||||
"ihCTrlsKKdk=",
|
||||
"yo/55hfla0U="
|
||||
],
|
||||
true,
|
||||
false,
|
||||
null,
|
||||
1.0,
|
||||
1234,
|
||||
"hwjAfNe5BGw=hwjAfNe5BGw="
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Xify front
|
||||
|
||||
This masking type replaces the front characters with `x` and
|
||||
blanks. Alphanumeric characters, `_` and `-` are replaced by `x`,
|
||||
everything else is replaced by a blank.
|
||||
|
||||
```json
|
||||
{
|
||||
"path": ".name",
|
||||
"type": "xifyFront",
|
||||
"unmaskedLength": 2
|
||||
}
|
||||
```
|
||||
|
||||
This will mask all alphanumeric characters of a word except the last
|
||||
two characters. Words of length 1 and 2 are unmasked. If the
|
||||
attribute value is not a string the result will be `xxxx`.
|
||||
|
||||
"This is a test!Do you agree?"
|
||||
|
||||
… will become
|
||||
|
||||
"xxis is a xxst Do xou xxxee "
|
||||
|
||||
There is a catch. If you have an index on the attribute the masking
|
||||
might distort the index efficiency or even cause errors in case of a
|
||||
unique index.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "xifyFront",
|
||||
"path": ".name",
|
||||
"unmaskedLength": 2,
|
||||
"hash": true
|
||||
}
|
||||
```
|
||||
|
||||
This will add a hash at the end of the string.
|
||||
|
||||
"This is a test!Do you agree?"
|
||||
|
||||
… will become
|
||||
|
||||
"xxis is a xxst Do xou xxxee NAATm8c9hVQ="
|
||||
|
||||
Note that the hash is based on a random secrect that is different for
|
||||
each run. This avoids dictionary attacks which can be used to guess
|
||||
values based pre-computations on dictionaries.
|
||||
|
||||
If you need reproducible results, i.e. hashes that do not change between
|
||||
different runs of *arangodump*, you need to specify a secret as seed,
|
||||
a number which must not be `0`.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "xifyFront",
|
||||
"path": ".name",
|
||||
"unmaskedLength": 2,
|
||||
"hash": true,
|
||||
"seed": 246781478647
|
||||
}
|
||||
```
|
||||
|
||||
### Zip
|
||||
|
||||
This masking type replaces a zip code with a random one. If the
|
||||
attribute value is not a string then the default value of `"12345"` is
|
||||
used as no zip is known. You can change the default value, see below.
|
||||
|
||||
```json
|
||||
{
|
||||
"path": ".code",
|
||||
"type": "zip",
|
||||
}
|
||||
```
|
||||
|
||||
This will replace a real zip code with a random one. It uses the following
|
||||
rule: If a character of the original zip code is a digit it will be replaced
|
||||
by a random digit. If a character of the original zip code is a letter it
|
||||
will be replaced by a random letter keeping the case.
|
||||
|
||||
```json
|
||||
{
|
||||
"path": ".code",
|
||||
"type": "zip",
|
||||
"default": "abcdef"
|
||||
}
|
||||
```
|
||||
|
||||
**Example**
|
||||
|
||||
If the original zip code is:
|
||||
|
||||
50674
|
||||
|
||||
… it will be replaced by e.g.:
|
||||
|
||||
98146
|
||||
|
||||
If the original zip code is:
|
||||
|
||||
SA34-EA
|
||||
|
||||
… it will be replaced by e.g.:
|
||||
|
||||
OW91-JI
|
||||
|
||||
Note that this will generate random zip code. Therefore there is a
|
||||
chance generate the same zip code value multiple times, which can
|
||||
cause unique constraint violations if a unique index is or will be
|
||||
used on the zip code attribute.
|
||||
|
||||
### Datetime
|
||||
|
||||
This masking type replaces the value of the attribute with a random
|
||||
date.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "datetime",
|
||||
"begin" : "2019-01-01",
|
||||
"end": "2019-12-31",
|
||||
"output": "%yyyy-%mm-%dd",
|
||||
}
|
||||
```
|
||||
|
||||
`begin` and `end` are in ISO8601 format.
|
||||
|
||||
The `output` format is described in
|
||||
[DATE_FORMAT](../../../AQL/Functions/Date.html#dateformat).
|
||||
|
||||
### Integral number
|
||||
|
||||
This masking type replaces the value of the attribute with a random
|
||||
integral number. It will replace the value even if it is a string,
|
||||
boolean, or false.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "integer",
|
||||
"lower" : -100,
|
||||
"upper": 100
|
||||
}
|
||||
```
|
||||
|
||||
### Decimal number
|
||||
|
||||
This masking type replaces the value of the attribute with a random
|
||||
decimal. It will replace the value even if it is a string, boolean,
|
||||
or false.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "float",
|
||||
"lower" : -0.3,
|
||||
"upper": 0.3
|
||||
}
|
||||
```
|
||||
|
||||
By default, the decimal has a scale of 2. I.e. it has at most 2
|
||||
decimal digits. The definition:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "float",
|
||||
"lower" : -0.3,
|
||||
"upper": 0.3,
|
||||
"scale": 3
|
||||
}
|
||||
```
|
||||
|
||||
… will generate numbers with at most 3 decimal digits.
|
||||
|
||||
### Credit card number
|
||||
|
||||
This masking type replaces the value of the attribute with a random
|
||||
credit card number.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "creditCard",
|
||||
}
|
||||
```
|
||||
|
||||
See [Luhn](https://en.wikipedia.org/wiki/Luhn_algorithm) for details.
|
||||
|
||||
### Phone number
|
||||
|
||||
This masking type replaces a phone number with a random one. If the
|
||||
attribute value is not a string it is replaced by the string
|
||||
`"+1234567890"`.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "phone",
|
||||
"default": "+4912345123456789"
|
||||
}
|
||||
```
|
||||
|
||||
This will replace an existing phone number with a random one. It uses
|
||||
the following rule: If a character of the original number is a digit
|
||||
it will be replaced by a random digit. If it is a letter it is replaced
|
||||
by a letter. All other characters are unchanged.
|
||||
|
||||
```json
|
||||
{ "type": "zip",
|
||||
"default": "+4912345123456789"
|
||||
}
|
||||
```
|
||||
|
||||
If the attribute value is not a string use the value of default
|
||||
`"+4912345123456789"`.
|
||||
|
||||
### Email address
|
||||
|
||||
This masking type takes an email address, computes a hash value and
|
||||
split it into three equal parts `AAAA`, `BBBB`, and `CCCC`. The
|
||||
resulting email address is `AAAA.BBBB@CCCC.invalid`.
|
|
@ -29,6 +29,15 @@
|
|||
* License Name: Boost Software License 1.0
|
||||
* License Id: BSL-1.0
|
||||
|
||||
### CreditCardGenerator 2016
|
||||
|
||||
* Name: CreditCardGenerator
|
||||
* Version: 1.8.1
|
||||
* Project Home: https://github.com/stormdark/CreditCardGenerator
|
||||
* License: https://raw.githubusercontent.com/stormdark/CreditCardGenerator/master/LICENSE
|
||||
* License Name: MIT License
|
||||
* License Id: MIT
|
||||
|
||||
### Curl 7.50.3
|
||||
|
||||
* Name: Curl
|
||||
|
|
|
@ -964,7 +964,7 @@ AqlValue addOrSubtractIsoDurationFromTimestamp(Query* query, tp_sys_clock_ms con
|
|||
year_month_day ymd{floor<days>(tp)};
|
||||
auto day_time = make_time(tp - sys_days(ymd));
|
||||
std::smatch duration_parts;
|
||||
if (!basics::regex_isoDuration(duration, duration_parts)) {
|
||||
if (!basics::regexIsoDuration(duration, duration_parts)) {
|
||||
if (isSubtract) {
|
||||
::registerWarning(query, "DATE_SUBTRACT", TRI_ERROR_QUERY_INVALID_DATE_VALUE);
|
||||
} else {
|
||||
|
@ -1037,7 +1037,7 @@ bool parameterToTimePoint(Query* query, transaction::Methods* trx,
|
|||
tp = tp_sys_clock_ms(milliseconds(value.toInt64(trx)));
|
||||
} else {
|
||||
std::string const dateVal = value.slice().copyString();
|
||||
if (!basics::parse_dateTime(dateVal, tp)) {
|
||||
if (!basics::parseDateTime(dateVal, tp)) {
|
||||
::registerWarning(query, AFN, TRI_ERROR_QUERY_INVALID_DATE_VALUE);
|
||||
return false;
|
||||
}
|
||||
|
@ -3500,7 +3500,7 @@ AqlValue Functions::IsDatestring(arangodb::aql::Query*, transaction::Methods*,
|
|||
|
||||
if (value.isString()) {
|
||||
tp_sys_clock_ms tp; // unused
|
||||
isValid = basics::parse_dateTime(value.slice().copyString(), tp);
|
||||
isValid = basics::parseDateTime(value.slice().copyString(), tp);
|
||||
}
|
||||
|
||||
return AqlValue(AqlValueHintBool(isValid));
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
#include "Basics/StaticStrings.h"
|
||||
#include "Basics/StringUtils.h"
|
||||
#include "Basics/VelocyPackHelper.h"
|
||||
#include "Maskings/Maskings.h"
|
||||
#include "ProgramOptions/ProgramOptions.h"
|
||||
#include "Random/RandomGenerator.h"
|
||||
#include "Shell/ClientFeature.h"
|
||||
|
@ -223,6 +224,29 @@ bool isIgnoredHiddenEnterpriseCollection(arangodb::DumpFeature::Options const& o
|
|||
return false;
|
||||
}
|
||||
|
||||
arangodb::Result dumpJsonObjects(arangodb::DumpFeature::JobData& jobData,
|
||||
arangodb::ManagedDirectory::File& file,
|
||||
arangodb::basics::StringBuffer const& body) {
|
||||
arangodb::basics::StringBuffer masked(1, false);
|
||||
arangodb::basics::StringBuffer const* result = &body;
|
||||
|
||||
if (jobData.maskings != nullptr) {
|
||||
jobData.maskings->mask(jobData.name, body, masked);
|
||||
result = &masked;
|
||||
}
|
||||
|
||||
file.write(result->c_str(), result->length());
|
||||
|
||||
if (file.status().fail()) {
|
||||
return {TRI_ERROR_CANNOT_WRITE_FILE, std::string("cannot write file '") + file.path() +
|
||||
"': " + file.status().errorMessage()};
|
||||
}
|
||||
|
||||
jobData.stats.totalWritten += static_cast<uint64_t>(result->length());
|
||||
|
||||
return {TRI_ERROR_NO_ERROR};
|
||||
}
|
||||
|
||||
/// @brief dump the actual data from an individual collection
|
||||
arangodb::Result dumpCollection(arangodb::httpclient::SimpleHttpClient& client,
|
||||
arangodb::DumpFeature::JobData& jobData,
|
||||
|
@ -298,13 +322,10 @@ arangodb::Result dumpCollection(arangodb::httpclient::SimpleHttpClient& client,
|
|||
|
||||
// now actually write retrieved data to dump file
|
||||
arangodb::basics::StringBuffer const& body = response->getBody();
|
||||
file.write(body.c_str(), body.length());
|
||||
if (file.status().fail()) {
|
||||
return {TRI_ERROR_CANNOT_WRITE_FILE,
|
||||
std::string("cannot write file '") + file.path() +
|
||||
"': " + file.status().errorMessage()};
|
||||
} else {
|
||||
jobData.stats.totalWritten += (uint64_t)body.length();
|
||||
arangodb::Result result = dumpJsonObjects(jobData, file, body);
|
||||
|
||||
if (result.fail()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!checkMore || fromTick == 0) {
|
||||
|
@ -393,6 +414,21 @@ arangodb::Result processJob(arangodb::httpclient::SimpleHttpClient& client,
|
|||
|
||||
arangodb::Result result{TRI_ERROR_NO_ERROR};
|
||||
|
||||
bool dumpStructure = true;
|
||||
|
||||
if (dumpStructure && jobData.maskings != nullptr) {
|
||||
dumpStructure = jobData.maskings->shouldDumpStructure(jobData.name);
|
||||
}
|
||||
|
||||
if (!dumpStructure) {
|
||||
if (jobData.options.progress) {
|
||||
LOG_TOPIC(INFO, arangodb::Logger::DUMP)
|
||||
<< "# Dumping collection '" << jobData.name << "'...";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// prep hex string of collection name
|
||||
std::string const hexString(arangodb::rest::SslInterface::sslMD5(jobData.name));
|
||||
|
||||
|
@ -435,8 +471,14 @@ arangodb::Result processJob(arangodb::httpclient::SimpleHttpClient& client,
|
|||
}
|
||||
}
|
||||
|
||||
if (result.ok() && jobData.options.dumpData) {
|
||||
// save the actual data
|
||||
if (result.ok()) {
|
||||
bool dumpData = jobData.options.dumpData;
|
||||
|
||||
if (dumpData && jobData.maskings != nullptr) {
|
||||
dumpData = jobData.maskings->shouldDumpData(jobData.name);
|
||||
}
|
||||
|
||||
// always create the file so that arangorestore does not complain
|
||||
auto file = jobData.directory.writableFile(jobData.name + "_" + hexString +
|
||||
".data.json",
|
||||
true);
|
||||
|
@ -444,12 +486,15 @@ arangodb::Result processJob(arangodb::httpclient::SimpleHttpClient& client,
|
|||
return ::fileError(file.get(), true);
|
||||
}
|
||||
|
||||
if (dumpData) {
|
||||
// save the actual data
|
||||
if (jobData.options.clusterMode) {
|
||||
result = ::handleCollectionCluster(client, jobData, *file);
|
||||
} else {
|
||||
result = ::handleCollection(client, jobData, *file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -467,10 +512,11 @@ void handleJobResult(std::unique_ptr<arangodb::DumpFeature::JobData>&& jobData,
|
|||
namespace arangodb {
|
||||
|
||||
DumpFeature::JobData::JobData(ManagedDirectory& dir, DumpFeature& feat,
|
||||
Options const& opts, Stats& stat, VPackSlice const& info,
|
||||
Options const& opts, maskings::Maskings* maskings,
|
||||
Stats& stat, VPackSlice const& info,
|
||||
uint64_t const batch, std::string const& c,
|
||||
std::string const& n, std::string const& t)
|
||||
: directory{dir}, feature{feat}, options{opts}, stats{stat}, collectionInfo{info}, batchId{batch}, cid{c}, name{n}, type{t} {}
|
||||
: directory{dir}, feature{feat}, options{opts}, maskings{maskings}, stats{stat}, collectionInfo{info}, batchId{batch}, cid{c}, name{n}, type{t} {}
|
||||
|
||||
DumpFeature::DumpFeature(application_features::ApplicationServer& server, int& exitCode)
|
||||
: ApplicationFeature(server, DumpFeature::featureName()),
|
||||
|
@ -543,6 +589,9 @@ void DumpFeature::collectOptions(std::shared_ptr<options::ProgramOptions> option
|
|||
|
||||
options->addOption("--tick-end", "last tick to be included in data dump",
|
||||
new UInt64Parameter(&_options.tickEnd));
|
||||
|
||||
options->addOption("--maskings", "file with maskings definition",
|
||||
new StringParameter(&_options.maskingsFile));
|
||||
}
|
||||
|
||||
void DumpFeature::validateOptions(std::shared_ptr<options::ProgramOptions> options) {
|
||||
|
@ -697,8 +746,9 @@ Result DumpFeature::runDump(httpclient::SimpleHttpClient& client, std::string co
|
|||
|
||||
// queue job to actually dump collection
|
||||
auto jobData =
|
||||
std::make_unique<JobData>(*_directory, *this, _options, _stats, collection,
|
||||
batchId, std::to_string(cid), name, collectionType);
|
||||
std::make_unique<JobData>(*_directory, *this, _options, _maskings.get(),
|
||||
_stats, collection, batchId,
|
||||
std::to_string(cid), name, collectionType);
|
||||
_clientTaskQueue.queueJob(std::move(jobData));
|
||||
}
|
||||
|
||||
|
@ -833,7 +883,8 @@ Result DumpFeature::runClusterDump(httpclient::SimpleHttpClient& client,
|
|||
}
|
||||
|
||||
// queue job to actually dump collection
|
||||
auto jobData = std::make_unique<JobData>(*_directory, *this, _options, _stats, collection,
|
||||
auto jobData = std::make_unique<JobData>(*_directory, *this, _options,
|
||||
_maskings.get(), _stats, collection,
|
||||
0 /* batchId */, std::to_string(cid),
|
||||
name, "" /* collectionType */);
|
||||
_clientTaskQueue.queueJob(std::move(jobData));
|
||||
|
@ -936,8 +987,18 @@ void DumpFeature::reportError(Result const& error) {
|
|||
}
|
||||
}
|
||||
|
||||
/// @brief main method to run dump
|
||||
void DumpFeature::start() {
|
||||
if (!_options.maskingsFile.empty()) {
|
||||
maskings::MaskingsResult m = maskings::Maskings::fromFile(_options.maskingsFile);
|
||||
|
||||
if (m.status != maskings::MaskingsResult::VALID) {
|
||||
LOG_TOPIC(FATAL, Logger::CONFIG) << m.message;
|
||||
FATAL_ERROR_EXIT();
|
||||
}
|
||||
|
||||
_maskings = std::move(m.maskings);
|
||||
}
|
||||
|
||||
_exitCode = EXIT_SUCCESS;
|
||||
|
||||
// generate a fake client id that we sent to the server
|
||||
|
|
|
@ -25,16 +25,20 @@
|
|||
#define ARANGODB_DUMP_DUMP_FEATURE_H 1
|
||||
|
||||
#include "ApplicationFeatures/ApplicationFeature.h"
|
||||
|
||||
#include "Basics/Mutex.h"
|
||||
#include "Utils/ClientManager.h"
|
||||
#include "Utils/ClientTaskQueue.h"
|
||||
|
||||
namespace arangodb {
|
||||
|
||||
namespace httpclient {
|
||||
class SimpleHttpResult;
|
||||
}
|
||||
|
||||
namespace maskings {
|
||||
class Maskings;
|
||||
}
|
||||
|
||||
class ManagedDirectory;
|
||||
|
||||
class DumpFeature : public application_features::ApplicationFeature {
|
||||
|
@ -62,6 +66,7 @@ class DumpFeature : public application_features::ApplicationFeature {
|
|||
struct Options {
|
||||
std::vector<std::string> collections{};
|
||||
std::string outputPath{};
|
||||
std::string maskingsFile{};
|
||||
uint64_t initialChunkSize{1024 * 1024 * 8};
|
||||
uint64_t maxChunkSize{1024 * 1024 * 64};
|
||||
uint32_t threadCount{2};
|
||||
|
@ -85,13 +90,14 @@ class DumpFeature : public application_features::ApplicationFeature {
|
|||
|
||||
/// @brief Stores all necessary data to dump a single collection or shard
|
||||
struct JobData {
|
||||
JobData(ManagedDirectory&, DumpFeature&, Options const&, Stats&,
|
||||
VPackSlice const&, uint64_t const, std::string const&,
|
||||
std::string const&, std::string const&);
|
||||
JobData(ManagedDirectory&, DumpFeature&, Options const&,
|
||||
maskings::Maskings* maskings, Stats&, VPackSlice const&, uint64_t const,
|
||||
std::string const&, std::string const&, std::string const&);
|
||||
|
||||
ManagedDirectory& directory;
|
||||
DumpFeature& feature;
|
||||
Options const& options;
|
||||
maskings::Maskings* maskings;
|
||||
Stats& stats;
|
||||
|
||||
VPackSlice const collectionInfo;
|
||||
|
@ -110,6 +116,7 @@ class DumpFeature : public application_features::ApplicationFeature {
|
|||
Stats _stats;
|
||||
Mutex _workerErrorLock;
|
||||
std::queue<Result> _workerErrors;
|
||||
std::unique_ptr<maskings::Maskings> _maskings;
|
||||
|
||||
Result runDump(httpclient::SimpleHttpClient& client, std::string const& dbName);
|
||||
Result runClusterDump(httpclient::SimpleHttpClient& client, std::string const& dbName);
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include "Dump/DumpFeature.h"
|
||||
#include "Logger/Logger.h"
|
||||
#include "Logger/LoggerFeature.h"
|
||||
#include "Maskings/AttributeMasking.h"
|
||||
#include "ProgramOptions/ProgramOptions.h"
|
||||
#include "Random/RandomFeature.h"
|
||||
#include "Shell/ClientFeature.h"
|
||||
|
@ -41,6 +42,7 @@
|
|||
|
||||
#ifdef USE_ENTERPRISE
|
||||
#include "Enterprise/Encryption/EncryptionFeature.h"
|
||||
#include "Enterprise/Maskings/AttributeMaskingEE.h"
|
||||
#endif
|
||||
|
||||
using namespace arangodb;
|
||||
|
@ -52,6 +54,12 @@ int main(int argc, char* argv[]) {
|
|||
ArangoGlobalContext context(argc, argv, BIN_DIRECTORY);
|
||||
context.installHup();
|
||||
|
||||
maskings::InstallMaskings();
|
||||
|
||||
#ifdef USE_ENTERPRISE
|
||||
maskings::InstallMaskingsEE();
|
||||
#endif
|
||||
|
||||
std::shared_ptr<options::ProgramOptions> options(
|
||||
new options::ProgramOptions(argv[0], "Usage: arangodump [<options>]",
|
||||
"For more information use:", BIN_DIRECTORY));
|
||||
|
|
|
@ -107,6 +107,12 @@ class ConfigBuilder {
|
|||
this.config['create-database'] = 'false';
|
||||
}
|
||||
}
|
||||
setMaskings(dir) {
|
||||
if (this.type !== 'dump') {
|
||||
throw '"maskings" is not supported for binary: ' + this.type;
|
||||
}
|
||||
this.config['maskings'] = fs.join(TOP_DIR, "tests/js/common/test-data/maskings", dir);
|
||||
}
|
||||
activateEncryption() { this.config['encription.keyfile'] = fs.join(this.rootDir, 'secret-key'); }
|
||||
setRootDir(dir) { this.rootDir = dir; }
|
||||
restrictToCollection(collection) {
|
||||
|
|
|
@ -2,34 +2,36 @@
|
|||
/* global print */
|
||||
'use strict';
|
||||
|
||||
// //////////////////////////////////////////////////////////////////////////////
|
||||
// / DISCLAIMER
|
||||
// /
|
||||
// / Copyright 2016 ArangoDB GmbH, Cologne, Germany
|
||||
// / Copyright 2014 triagens 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 Max Neunhoeffer
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2016-2019 ArangoDB GmbH, Cologne, Germany
|
||||
// Copyright 2014 triagens 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 Max Neunhoeffer
|
||||
// //////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const functionsDocumentation = {
|
||||
'dump': 'dump tests',
|
||||
'dump_authentication': 'dump tests with authentication',
|
||||
'dump_encrypted': 'encrypted dump tests',
|
||||
'dump_authentication': 'dump tests with authentication'
|
||||
'dump_maskings': 'masked dump tests'
|
||||
};
|
||||
|
||||
const optionsDocumentation = [
|
||||
' - `skipEncrypted` : if set to true the encryption tests are skipped'
|
||||
];
|
||||
|
@ -48,16 +50,18 @@ const RESET = require('internal').COLORS.COLOR_RESET;
|
|||
|
||||
const testPaths = {
|
||||
'dump': [tu.pathForTesting('server/dump')],
|
||||
'dump_authentication': [tu.pathForTesting('server/dump')],
|
||||
'dump_encrypted': [tu.pathForTesting('server/dump')],
|
||||
'dump_authentication': [tu.pathForTesting('server/dump')]
|
||||
'dump_maskings': [tu.pathForTesting('server/dump')]
|
||||
};
|
||||
|
||||
class DumpRestoreHelper {
|
||||
constructor(instanceInfo, options, clientAuth, dumpOptions, which, afterServerStart) {
|
||||
constructor(instanceInfo, options, clientAuth, dumpOptions, restoreOptions, which, afterServerStart) {
|
||||
this.instanceInfo = instanceInfo;
|
||||
this.options = options;
|
||||
this.clientAuth = clientAuth;
|
||||
this.dumpOptions = dumpOptions;
|
||||
this.restoreOptions = restoreOptions;
|
||||
this.which = which;
|
||||
this.fn = afterServerStart(instanceInfo);
|
||||
this.results = {failed: 1};
|
||||
|
@ -66,11 +70,15 @@ class DumpRestoreHelper {
|
|||
this.dumpConfig.setOutputDirectory('dump');
|
||||
this.dumpConfig.setIncludeSystem(true);
|
||||
|
||||
this.restoreConfig = pu.createBaseConfig('restore', this.dumpOptions, this.instanceInfo);
|
||||
if (dumpOptions.hasOwnProperty("maskings")) {
|
||||
this.dumpConfig.setMaskings(dumpOptions.maskings);
|
||||
}
|
||||
|
||||
this.restoreConfig = pu.createBaseConfig('restore', this.restoreOptions, this.instanceInfo);
|
||||
this.restoreConfig.setInputDirectory('dump', true);
|
||||
this.restoreConfig.setIncludeSystem(true);
|
||||
|
||||
this.restoreOldConfig = pu.createBaseConfig('restore', this.dumpOptions, this.instanceInfo);
|
||||
this.restoreOldConfig = pu.createBaseConfig('restore', this.restoreOptions, this.instanceInfo);
|
||||
this.restoreOldConfig.setInputDirectory('dump', true);
|
||||
this.restoreOldConfig.setIncludeSystem(true);
|
||||
this.restoreOldConfig.setDatabase('_system');
|
||||
|
@ -81,8 +89,8 @@ class DumpRestoreHelper {
|
|||
this.restoreOldConfig.activateEncryption();
|
||||
}
|
||||
|
||||
this.arangorestore = pu.run.arangoDumpRestoreWithConfig.bind(this, this.restoreConfig, this.dumpOptions, this.instanceInfo.rootDir);
|
||||
this.arangorestoreOld = pu.run.arangoDumpRestoreWithConfig.bind(this, this.restoreOldConfig, this.dumpOptions, this.instanceInfo.rootDir);
|
||||
this.arangorestore = pu.run.arangoDumpRestoreWithConfig.bind(this, this.restoreConfig, this.restoreOptions, this.instanceInfo.rootDir);
|
||||
this.arangorestoreOld = pu.run.arangoDumpRestoreWithConfig.bind(this, this.restoreOldConfig, this.restoreOptions, this.instanceInfo.rootDir);
|
||||
this.arangodump = pu.run.arangoDumpRestoreWithConfig.bind(this, this.dumpConfig, this.dumpOptions, this.instanceInfo.rootDir);
|
||||
}
|
||||
|
||||
|
@ -225,10 +233,7 @@ function getClusterStrings(options)
|
|||
}
|
||||
}
|
||||
|
||||
// //////////////////////////////////////////////////////////////////////////////
|
||||
// / @brief TEST: dump
|
||||
// //////////////////////////////////////////////////////////////////////////////
|
||||
function dump_backend (options, serverAuthInfo, clientAuth, dumpOptions, which, tstFiles, afterServerStart) {
|
||||
function dump_backend (options, serverAuthInfo, clientAuth, dumpOptions, restoreOptions, which, tstFiles, afterServerStart) {
|
||||
print(CYAN + which + ' tests...' + RESET);
|
||||
|
||||
let instanceInfo = pu.startInstance('tcp', options, serverAuthInfo, which);
|
||||
|
@ -243,7 +248,7 @@ function dump_backend (options, serverAuthInfo, clientAuth, dumpOptions, which,
|
|||
};
|
||||
return rc;
|
||||
}
|
||||
const helper = new DumpRestoreHelper(instanceInfo, options, clientAuth, dumpOptions, which, afterServerStart);
|
||||
const helper = new DumpRestoreHelper(instanceInfo, options, clientAuth, dumpOptions, restoreOptions, which, afterServerStart);
|
||||
|
||||
const setupFile = tu.makePathUnix(fs.join(testPaths[which][0], tstFiles.dumpSetup));
|
||||
const testFile = tu.makePathUnix(fs.join(testPaths[which][0], tstFiles.dumpAgain));
|
||||
|
@ -267,6 +272,7 @@ function dump_backend (options, serverAuthInfo, clientAuth, dumpOptions, which,
|
|||
}
|
||||
}
|
||||
|
||||
if (tstFiles.hasOwnProperty("foxxTest")) {
|
||||
const foxxTestFile = tu.makePathUnix(fs.join(testPaths[which][0], tstFiles.foxxTest));
|
||||
if (!helper.restoreFoxxComplete('UnitTestsDumpFoxxComplete') ||
|
||||
!helper.testFoxxComplete(foxxTestFile, 'UnitTestsDumpFoxxComplete') ||
|
||||
|
@ -276,12 +282,11 @@ function dump_backend (options, serverAuthInfo, clientAuth, dumpOptions, which,
|
|||
!helper.testFoxxAppsBundle(foxxTestFile, 'UnitTestsDumpFoxxBundleApps')) {
|
||||
return helper.extractResults();
|
||||
}
|
||||
}
|
||||
|
||||
return helper.extractResults();
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function dump (options) {
|
||||
let c = getClusterStrings(options);
|
||||
let tstFiles = {
|
||||
|
@ -292,7 +297,7 @@ function dump (options) {
|
|||
foxxTest: 'check-foxx.js'
|
||||
};
|
||||
|
||||
return dump_backend(options, {}, {}, options, 'dump', tstFiles, function(){});
|
||||
return dump_backend(options, {}, {}, options, options, 'dump', tstFiles, function(){});
|
||||
}
|
||||
|
||||
function dumpAuthentication (options) {
|
||||
|
@ -332,7 +337,7 @@ function dumpAuthentication (options) {
|
|||
foxxTest: 'check-foxx.js'
|
||||
};
|
||||
|
||||
return dump_backend(options, serverAuthInfo, clientAuth, dumpAuthOpts, 'dump_authentication', tstFiles, function(){});
|
||||
return dump_backend(options, serverAuthInfo, clientAuth, dumpAuthOpts, dumpAuthOpts, 'dump_authentication', tstFiles, function(){});
|
||||
}
|
||||
|
||||
function dumpEncrypted (options) {
|
||||
|
@ -373,20 +378,57 @@ function dumpEncrypted (options) {
|
|||
foxxTest: 'check-foxx.js'
|
||||
};
|
||||
|
||||
return dump_backend(options, {}, {}, dumpOptions, 'dump_encrypted', tstFiles, afterServerStart);
|
||||
return dump_backend(options, {}, {}, dumpOptions, dumpOptions, 'dump_encrypted', tstFiles, afterServerStart);
|
||||
}
|
||||
|
||||
function dumpMaskings (options) {
|
||||
// test is only meaningful in the enterprise version
|
||||
let skip = true;
|
||||
if (global.ARANGODB_CLIENT_VERSION) {
|
||||
let version = global.ARANGODB_CLIENT_VERSION(true);
|
||||
if (version.hasOwnProperty('enterprise-version')) {
|
||||
skip = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (skip) {
|
||||
print('skipping dump_maskings test');
|
||||
return {
|
||||
dump_maskings: {
|
||||
status: true,
|
||||
skipped: true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let tstFiles = {
|
||||
dumpSetup: 'dump-maskings-setup.js',
|
||||
dumpAgain: 'dump-maskings.js',
|
||||
dumpTearDown: 'dump-teardown.js'
|
||||
};
|
||||
|
||||
let dumpMaskingsOpts = {
|
||||
maskings: 'maskings1.json'
|
||||
};
|
||||
|
||||
_.defaults(dumpMaskingsOpts, options);
|
||||
|
||||
return dump_backend(options, {}, {}, dumpMaskingsOpts, options, 'dump_maskings', tstFiles, function(){});
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
exports.setup = function (testFns, defaultFns, opts, fnDocs, optionsDoc, allTestPaths) {
|
||||
Object.assign(allTestPaths, testPaths);
|
||||
testFns['dump'] = dump;
|
||||
defaultFns.push('dump');
|
||||
|
||||
testFns['dump_authentication'] = dumpAuthentication;
|
||||
defaultFns.push('dump_authentication');
|
||||
|
||||
testFns['dump_encrypted'] = dumpEncrypted;
|
||||
defaultFns.push('dump_encrypted');
|
||||
|
||||
testFns['dump_authentication'] = dumpAuthentication;
|
||||
defaultFns.push('dump_authentication');
|
||||
testFns['dump_maskings'] = dumpMaskings;
|
||||
defaultFns.push('dump_maskings');
|
||||
|
||||
for (var attrname in functionsDocumentation) { fnDocs[attrname] = functionsDocumentation[attrname]; }
|
||||
for (var i = 0; i < optionsDocumentation.length; i++) { optionsDoc.push(optionsDocumentation[i]); }
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// DISCLAIMER
|
||||
///
|
||||
/// Copyright 2014-2016 ArangoDB GmbH, Cologne, Germany
|
||||
/// Copyright 2014-2019 ArangoDB GmbH, Cologne, Germany
|
||||
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -25,9 +25,10 @@
|
|||
#ifndef ARANGODB_BASICS_UTF8HELPER_H
|
||||
#define ARANGODB_BASICS_UTF8HELPER_H 1
|
||||
|
||||
#include <velocypack/StringRef.h>
|
||||
#include "Basics/Common.h"
|
||||
|
||||
#include <velocypack/StringRef.h>
|
||||
|
||||
#include <unicode/coll.h>
|
||||
#include <unicode/regex.h>
|
||||
#include <unicode/ustring.h>
|
||||
|
@ -40,10 +41,6 @@ class Utf8Helper {
|
|||
Utf8Helper& operator=(Utf8Helper const&) = delete;
|
||||
|
||||
public:
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief a default helper
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static Utf8Helper DefaultUtf8Helper;
|
||||
|
||||
public:
|
||||
|
@ -153,6 +150,26 @@ class Utf8Helper {
|
|||
char const* replacement, size_t replacementLength,
|
||||
bool partial, bool& error);
|
||||
|
||||
// append an UTF8 to a string. This will append 1 to 4 bytes.
|
||||
static void appendUtf8Character(std::string& result, uint32_t ch) {
|
||||
if (ch <= 0x7f) {
|
||||
result.push_back((uint8_t)ch);
|
||||
} else {
|
||||
if (ch <= 0x7ff) {
|
||||
result.push_back((uint8_t)((ch >> 6) | 0xc0));
|
||||
} else {
|
||||
if (ch <= 0xffff) {
|
||||
result.push_back((uint8_t)((ch >> 12) | 0xe0));
|
||||
} else {
|
||||
result.push_back((uint8_t)((ch >> 18) | 0xf0));
|
||||
result.push_back((uint8_t)(((ch >> 12) & 0x3f) | 0x80));
|
||||
}
|
||||
result.push_back((uint8_t)(((ch >> 6) & 0x3f) | 0x80));
|
||||
}
|
||||
result.push_back((uint8_t)((ch & 0x3f) | 0x80));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Collator* _coll;
|
||||
};
|
||||
|
|
|
@ -21,16 +21,503 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "Basics/datetime.h"
|
||||
#include <date/date.h>
|
||||
#include "Basics/NumberUtils.h"
|
||||
#include "Logger/Logger.h"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <date/date.h>
|
||||
#include <date/iso_week.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <regex>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
using namespace date;
|
||||
using namespace std::chrono;
|
||||
|
||||
std::string tail(std::string const& source, size_t const length) {
|
||||
if (length >= source.size()) {
|
||||
return source;
|
||||
}
|
||||
return source.substr(source.size() - length);
|
||||
} // tail
|
||||
|
||||
typedef void (*format_func_t)(std::string& wrk, arangodb::tp_sys_clock_ms const&);
|
||||
std::unordered_map<std::string, format_func_t> dateMap;
|
||||
auto const unixEpoch = date::sys_seconds{std::chrono::seconds{0}};
|
||||
|
||||
std::vector<std::string> const monthNames = {"January", "February", "March",
|
||||
"April", "May", "June",
|
||||
"July", "August", "September",
|
||||
"October", "November", "December"};
|
||||
|
||||
std::vector<std::string> const monthNamesShort = {"Jan", "Feb", "Mar", "Apr",
|
||||
"May", "Jun", "Jul", "Aug",
|
||||
"Sep", "Oct", "Nov", "Dec"};
|
||||
|
||||
std::vector<std::string> const weekDayNames = {"Sunday", "Monday",
|
||||
"Tuesday", "Wednesday",
|
||||
"Thursday", "Friday",
|
||||
"Saturday"};
|
||||
|
||||
std::vector<std::string> const weekDayNamesShort = {"Sun", "Mon", "Tue", "Wed",
|
||||
"Thu", "Fri", "Sat"};
|
||||
|
||||
std::
|
||||
vector<std::pair<std::string, format_func_t>> const sortedDateMap = {{"%&",
|
||||
[](std::string& wrk,
|
||||
arangodb::tp_sys_clock_ms const& tp) {
|
||||
}}, // Allow for literal "m" after "%m" ("%mm" ->
|
||||
// %m%&m)
|
||||
// zero-pad 4 digit years to length of 6 and add "+"
|
||||
// prefix, keep negative as-is
|
||||
{"%yyy"
|
||||
"yyy",
|
||||
[](std::string& wrk, arangodb::tp_sys_clock_ms const& tp) {
|
||||
auto ymd = year_month_day(
|
||||
floor<date::days>(tp));
|
||||
auto yearnum = static_cast<int>(
|
||||
ymd.year());
|
||||
if (yearnum < 0) {
|
||||
if (yearnum > -10) {
|
||||
wrk.append(
|
||||
"-00000");
|
||||
} else if (yearnum > -100) {
|
||||
wrk.append(
|
||||
"-0000");
|
||||
} else if (yearnum > -1000) {
|
||||
wrk.append(
|
||||
"-000");
|
||||
} else if (yearnum > -10000) {
|
||||
wrk.append(
|
||||
"-00");
|
||||
} else if (yearnum > -100000) {
|
||||
wrk.append(
|
||||
"-0");
|
||||
} else {
|
||||
wrk.append(
|
||||
"-");
|
||||
}
|
||||
wrk.append(std::to_string(
|
||||
abs(yearnum)));
|
||||
return;
|
||||
}
|
||||
|
||||
TRI_ASSERT(yearnum >= 0);
|
||||
|
||||
if (yearnum > 99999) {
|
||||
// intentionally nothing
|
||||
} else if (yearnum > 9999) {
|
||||
wrk.append(
|
||||
"+0");
|
||||
} else if (yearnum > 999) {
|
||||
wrk.append(
|
||||
"+00");
|
||||
} else if (yearnum > 99) {
|
||||
wrk.append(
|
||||
"+000");
|
||||
} else if (yearnum > 9) {
|
||||
wrk.append(
|
||||
"+0000");
|
||||
} else {
|
||||
wrk.append(
|
||||
"+00000");
|
||||
}
|
||||
wrk.append(std::to_string(yearnum));
|
||||
}},
|
||||
{"%mmm"
|
||||
"m",
|
||||
[](std::string& wrk, arangodb::tp_sys_clock_ms const& tp) {
|
||||
auto ymd = year_month_day(
|
||||
floor<date::days>(tp));
|
||||
wrk.append(
|
||||
::monthNames[static_cast<unsigned>(ymd.month()) - 1]);
|
||||
}},
|
||||
{"%yyy"
|
||||
"y",
|
||||
[](std::string& wrk, arangodb::tp_sys_clock_ms const& tp) {
|
||||
auto ymd = year_month_day(
|
||||
floor<date::days>(tp));
|
||||
auto yearnum = static_cast<int>(
|
||||
ymd.year());
|
||||
if (yearnum < 0) {
|
||||
if (yearnum > -10) {
|
||||
wrk.append(
|
||||
"-000");
|
||||
} else if (yearnum > -100) {
|
||||
wrk.append(
|
||||
"-00");
|
||||
} else if (yearnum > -1000) {
|
||||
wrk.append(
|
||||
"-0");
|
||||
} else {
|
||||
wrk.append(
|
||||
"-");
|
||||
}
|
||||
wrk.append(std::to_string(
|
||||
abs(yearnum)));
|
||||
} else {
|
||||
TRI_ASSERT(yearnum >= 0);
|
||||
if (yearnum < 9) {
|
||||
wrk.append(
|
||||
"000");
|
||||
wrk.append(std::to_string(yearnum));
|
||||
} else if (yearnum < 99) {
|
||||
wrk.append(
|
||||
"00");
|
||||
wrk.append(std::to_string(yearnum));
|
||||
} else if (yearnum < 999) {
|
||||
wrk.append(
|
||||
"0");
|
||||
wrk.append(std::to_string(yearnum));
|
||||
} else {
|
||||
std::string yearstr(
|
||||
std::to_string(yearnum));
|
||||
wrk.append(tail(yearstr, 4));
|
||||
}
|
||||
}
|
||||
}},
|
||||
|
||||
{"%www"
|
||||
"w",
|
||||
[](std::string& wrk, arangodb::tp_sys_clock_ms const& tp) {
|
||||
weekday wd{floor<date::days>(tp)};
|
||||
wrk.append(
|
||||
::weekDayNames[static_cast<unsigned>(wd)]);
|
||||
}},
|
||||
|
||||
{"%mm"
|
||||
"m",
|
||||
[](std::string& wrk, arangodb::tp_sys_clock_ms const& tp) {
|
||||
auto ymd = year_month_day(
|
||||
floor<date::days>(tp));
|
||||
wrk.append(
|
||||
::monthNamesShort[static_cast<unsigned>(ymd.month()) - 1]);
|
||||
}},
|
||||
{"%ww"
|
||||
"w",
|
||||
[](std::string& wrk, arangodb::tp_sys_clock_ms const& tp) {
|
||||
weekday wd{floor<date::days>(tp)};
|
||||
wrk.append(
|
||||
weekDayNamesShort[static_cast<unsigned>(wd)]);
|
||||
}},
|
||||
{"%ff"
|
||||
"f",
|
||||
[](std::string& wrk, arangodb::tp_sys_clock_ms const& tp) {
|
||||
auto day_time = make_time(
|
||||
tp - floor<date::days>(tp));
|
||||
uint64_t millis =
|
||||
day_time
|
||||
.subseconds()
|
||||
.count();
|
||||
if (millis < 10) {
|
||||
wrk.append(
|
||||
"00");
|
||||
} else if (millis < 100) {
|
||||
wrk.append(
|
||||
"0");
|
||||
}
|
||||
wrk.append(std::to_string(millis));
|
||||
}},
|
||||
{"%xx"
|
||||
"x",
|
||||
[](std::string& wrk, arangodb::tp_sys_clock_ms const& tp) {
|
||||
auto ymd = year_month_day(
|
||||
floor<date::days>(tp));
|
||||
auto yyyy =
|
||||
year{ymd.year()};
|
||||
// we construct the date with the first day in the
|
||||
// year:
|
||||
auto firstDayInYear =
|
||||
yyyy / jan /
|
||||
day{0};
|
||||
uint64_t daysSinceFirst =
|
||||
duration_cast<date::days>(
|
||||
tp - sys_days(firstDayInYear))
|
||||
.count();
|
||||
if (daysSinceFirst < 10) {
|
||||
wrk.append(
|
||||
"00");
|
||||
} else if (daysSinceFirst < 100) {
|
||||
wrk.append(
|
||||
"0");
|
||||
}
|
||||
wrk.append(std::to_string(daysSinceFirst));
|
||||
}},
|
||||
|
||||
// there"s no really sensible way to handle negative
|
||||
// years, but better not drop the sign
|
||||
{"%yy",
|
||||
[](std::string& wrk, arangodb::tp_sys_clock_ms const& tp) {
|
||||
auto ymd = year_month_day(
|
||||
floor<date::days>(tp));
|
||||
auto yearnum = static_cast<int>(
|
||||
ymd.year());
|
||||
if (yearnum < 10 &&
|
||||
yearnum > -10) {
|
||||
wrk.append(
|
||||
"0");
|
||||
wrk.append(std::to_string(
|
||||
abs(yearnum)));
|
||||
} else {
|
||||
std::string yearstr(std::to_string(
|
||||
abs(yearnum)));
|
||||
wrk.append(tail(yearstr, 2));
|
||||
}
|
||||
}},
|
||||
{"%mm",
|
||||
[](std::string& wrk, arangodb::tp_sys_clock_ms const& tp) {
|
||||
auto ymd = year_month_day(
|
||||
floor<date::days>(tp));
|
||||
auto month = static_cast<unsigned>(
|
||||
ymd.month());
|
||||
if (month < 10) {
|
||||
wrk.append(
|
||||
"0");
|
||||
}
|
||||
wrk.append(std::to_string(month));
|
||||
}},
|
||||
{"%dd",
|
||||
[](std::string& wrk, arangodb::tp_sys_clock_ms const& tp) {
|
||||
auto ymd = year_month_day(
|
||||
floor<date::days>(tp));
|
||||
auto day = static_cast<unsigned>(
|
||||
ymd.day());
|
||||
if (day < 10) {
|
||||
wrk.append(
|
||||
"0");
|
||||
}
|
||||
wrk.append(std::to_string(day));
|
||||
}},
|
||||
{"%hh",
|
||||
[](std::string& wrk, arangodb::tp_sys_clock_ms const& tp) {
|
||||
auto day_time = make_time(
|
||||
tp - floor<date::days>(tp));
|
||||
uint64_t hours =
|
||||
day_time
|
||||
.hours()
|
||||
.count();
|
||||
if (hours < 10) {
|
||||
wrk.append(
|
||||
"0");
|
||||
}
|
||||
wrk.append(std::to_string(hours));
|
||||
}},
|
||||
{"%ii",
|
||||
[](std::string& wrk, arangodb::tp_sys_clock_ms const& tp) {
|
||||
auto day_time = make_time(
|
||||
tp - floor<date::days>(tp));
|
||||
uint64_t minutes =
|
||||
day_time
|
||||
.minutes()
|
||||
.count();
|
||||
if (minutes < 10) {
|
||||
wrk.append(
|
||||
"0");
|
||||
}
|
||||
wrk.append(std::to_string(minutes));
|
||||
}},
|
||||
{"%ss",
|
||||
[](std::string& wrk, arangodb::tp_sys_clock_ms const& tp) {
|
||||
auto day_time = make_time(
|
||||
tp - floor<date::days>(tp));
|
||||
uint64_t seconds =
|
||||
day_time
|
||||
.seconds()
|
||||
.count();
|
||||
if (seconds < 10) {
|
||||
wrk.append(
|
||||
"0");
|
||||
}
|
||||
wrk.append(std::to_string(seconds));
|
||||
}},
|
||||
{"%kk",
|
||||
[](std::string& wrk, arangodb::tp_sys_clock_ms const& tp) {
|
||||
iso_week::year_weeknum_weekday yww{
|
||||
floor<date::days>(tp)};
|
||||
uint64_t isoWeek =
|
||||
static_cast<unsigned>(
|
||||
yww.weeknum());
|
||||
if (isoWeek < 10) {
|
||||
wrk.append(
|
||||
"0");
|
||||
}
|
||||
wrk.append(std::to_string(isoWeek));
|
||||
}},
|
||||
|
||||
{"%t",
|
||||
[](std::string& wrk, arangodb::tp_sys_clock_ms const& tp) {
|
||||
auto diffDuration =
|
||||
tp - unixEpoch;
|
||||
auto diff =
|
||||
duration_cast<duration<double, std::milli>>(
|
||||
diffDuration)
|
||||
.count();
|
||||
wrk.append(std::to_string(static_cast<int64_t>(
|
||||
std::round(diff))));
|
||||
}},
|
||||
{"%z",
|
||||
[](std::string& wrk,
|
||||
arangodb::tp_sys_clock_ms const& tp) {
|
||||
std::string formatted = format(
|
||||
"%FT%TZ",
|
||||
floor<milliseconds>(tp));
|
||||
wrk.append(formatted);
|
||||
}},
|
||||
{"%w",
|
||||
[](std::string& wrk,
|
||||
arangodb::tp_sys_clock_ms const& tp) {
|
||||
weekday wd{floor<date::days>(tp)};
|
||||
wrk.append(std::to_string(
|
||||
static_cast<unsigned>(wd)));
|
||||
}},
|
||||
{"%y",
|
||||
[](std::string& wrk,
|
||||
arangodb::tp_sys_clock_ms const& tp) {
|
||||
auto ymd = year_month_day(
|
||||
floor<date::days>(tp));
|
||||
wrk.append(std::to_string(static_cast<int>(
|
||||
ymd.year())));
|
||||
}},
|
||||
{"%m",
|
||||
[](std::string& wrk,
|
||||
arangodb::tp_sys_clock_ms const& tp) {
|
||||
auto ymd = year_month_day(
|
||||
floor<date::days>(tp));
|
||||
wrk.append(std::to_string(static_cast<unsigned>(
|
||||
ymd.month())));
|
||||
}},
|
||||
{"%d",
|
||||
[](std::string& wrk,
|
||||
arangodb::tp_sys_clock_ms const& tp) {
|
||||
auto ymd = year_month_day(
|
||||
floor<date::days>(tp));
|
||||
wrk.append(std::to_string(static_cast<unsigned>(
|
||||
ymd.day())));
|
||||
}},
|
||||
{"%h",
|
||||
[](std::string& wrk,
|
||||
arangodb::tp_sys_clock_ms const& tp) {
|
||||
auto day_time = make_time(
|
||||
tp - floor<date::days>(tp));
|
||||
uint64_t hours =
|
||||
day_time
|
||||
.hours()
|
||||
.count();
|
||||
wrk.append(std::to_string(hours));
|
||||
}},
|
||||
{"%i",
|
||||
[](std::string& wrk,
|
||||
arangodb::tp_sys_clock_ms const& tp) {
|
||||
auto day_time = make_time(
|
||||
tp - floor<date::days>(tp));
|
||||
uint64_t minutes =
|
||||
day_time
|
||||
.minutes()
|
||||
.count();
|
||||
wrk.append(std::to_string(minutes));
|
||||
}},
|
||||
{"%s",
|
||||
[](std::string& wrk,
|
||||
arangodb::tp_sys_clock_ms const& tp) {
|
||||
auto day_time = make_time(
|
||||
tp - floor<date::days>(tp));
|
||||
uint64_t seconds =
|
||||
day_time
|
||||
.seconds()
|
||||
.count();
|
||||
wrk.append(std::to_string(seconds));
|
||||
}},
|
||||
{"%f",
|
||||
[](std::string& wrk,
|
||||
arangodb::tp_sys_clock_ms const& tp) {
|
||||
auto day_time = make_time(
|
||||
tp - floor<date::days>(tp));
|
||||
uint64_t millis =
|
||||
day_time
|
||||
.subseconds()
|
||||
.count();
|
||||
wrk.append(std::to_string(millis));
|
||||
}},
|
||||
{"%x",
|
||||
[](std::string& wrk,
|
||||
arangodb::tp_sys_clock_ms const& tp) {
|
||||
auto ymd = year_month_day(
|
||||
floor<date::days>(tp));
|
||||
auto yyyy =
|
||||
year{ymd.year()};
|
||||
// We construct the date with the first day in the
|
||||
// year:
|
||||
auto firstDayInYear =
|
||||
yyyy / jan /
|
||||
day{0};
|
||||
uint64_t daysSinceFirst =
|
||||
duration_cast<date::days>(
|
||||
tp - sys_days(firstDayInYear))
|
||||
.count();
|
||||
wrk.append(std::to_string(daysSinceFirst));
|
||||
}},
|
||||
{"%k",
|
||||
[](std::string& wrk,
|
||||
arangodb::tp_sys_clock_ms const& tp) {
|
||||
iso_week::year_weeknum_weekday yww{
|
||||
floor<date::days>(tp)};
|
||||
uint64_t isoWeek =
|
||||
static_cast<unsigned>(
|
||||
yww.weeknum());
|
||||
wrk.append(std::to_string(isoWeek));
|
||||
}},
|
||||
{"%l",
|
||||
[](std::string& wrk,
|
||||
arangodb::tp_sys_clock_ms const& tp) {
|
||||
year_month_day ymd{
|
||||
floor<date::days>(tp)};
|
||||
if (ymd.year()
|
||||
.is_leap()) {
|
||||
wrk.append(
|
||||
"1");
|
||||
} else {
|
||||
wrk.append(
|
||||
"0");
|
||||
}
|
||||
}},
|
||||
{"%q",
|
||||
[](std::string& wrk,
|
||||
arangodb::tp_sys_clock_ms const& tp) {
|
||||
year_month_day ymd{
|
||||
floor<date::days>(tp)};
|
||||
month m = ymd.month();
|
||||
uint64_t part = static_cast<uint64_t>(
|
||||
ceil(unsigned(m) / 3.0f));
|
||||
TRI_ASSERT(part <= 4);
|
||||
wrk.append(std::to_string(part));
|
||||
}},
|
||||
{"%a",
|
||||
[](std::string& wrk,
|
||||
arangodb::tp_sys_clock_ms const& tp) {
|
||||
auto ymd = year_month_day{
|
||||
floor<date::days>(tp)};
|
||||
auto lastMonthDay =
|
||||
ymd.year() /
|
||||
ymd.month() / last;
|
||||
wrk.append(std::to_string(static_cast<unsigned>(
|
||||
lastMonthDay
|
||||
.day())));
|
||||
}},
|
||||
{"%%",
|
||||
[](std::string& wrk,
|
||||
arangodb::tp_sys_clock_ms const& tp) {
|
||||
wrk.append(
|
||||
"%");
|
||||
}},
|
||||
{"%", [](std::string& wrk,
|
||||
arangodb::tp_sys_clock_ms const& tp) {
|
||||
}}};
|
||||
|
||||
// will be populated by DateRegexInitializer
|
||||
std::regex dateFormatRegex;
|
||||
|
||||
std::regex const iso8601Regex(
|
||||
"(\\+|\\-)?\\d+(\\-\\d{1,2}(\\-\\d{1,2})?)?" // YY[YY]-MM-DD
|
||||
"("
|
||||
|
@ -92,9 +579,64 @@ std::regex const durationRegex(
|
|||
"P((\\d+)Y)?((\\d+)M)?((\\d+)W)?((\\d+)D)?(T((\\d+)H)?((\\d+)M)?((\\d+)(\\."
|
||||
"(\\d{1,3}))?S)?)?");
|
||||
|
||||
struct DateRegexInitializer {
|
||||
DateRegexInitializer() {
|
||||
std::string myregex;
|
||||
|
||||
dateMap.reserve(sortedDateMap.size());
|
||||
std::for_each(sortedDateMap.begin(), sortedDateMap.end(),
|
||||
[&myregex](std::pair<std::string const&, format_func_t> const& p) {
|
||||
(myregex.length() > 0) ? myregex += "|" + p.first : myregex = p.first;
|
||||
dateMap.insert(std::make_pair(p.first, p.second));
|
||||
});
|
||||
dateFormatRegex = std::regex(myregex);
|
||||
}
|
||||
};
|
||||
|
||||
std::string executeDateFormatRegex(std::string const& search,
|
||||
arangodb::tp_sys_clock_ms const& tp) {
|
||||
std::string s;
|
||||
|
||||
auto first = search.begin();
|
||||
auto last = search.end();
|
||||
typename std::smatch::difference_type positionOfLastMatch = 0;
|
||||
auto endOfLastMatch = first;
|
||||
|
||||
auto callback = [&tp, &endOfLastMatch, &positionOfLastMatch, &s](std::smatch const& match) {
|
||||
auto positionOfThisMatch = match.position(0);
|
||||
auto diff = positionOfThisMatch - positionOfLastMatch;
|
||||
|
||||
auto startOfThisMatch = endOfLastMatch;
|
||||
std::advance(startOfThisMatch, diff);
|
||||
|
||||
s.append(endOfLastMatch, startOfThisMatch);
|
||||
auto got = dateMap.find(match.str(0));
|
||||
if (got != dateMap.end()) {
|
||||
got->second(s, tp);
|
||||
}
|
||||
auto lengthOfMatch = match.length(0);
|
||||
|
||||
positionOfLastMatch = positionOfThisMatch + lengthOfMatch;
|
||||
|
||||
endOfLastMatch = startOfThisMatch;
|
||||
std::advance(endOfLastMatch, lengthOfMatch);
|
||||
};
|
||||
|
||||
std::regex_iterator<std::string::const_iterator> end;
|
||||
std::regex_iterator<std::string::const_iterator> begin(first, last, dateFormatRegex);
|
||||
std::for_each(begin, end, callback);
|
||||
|
||||
s.append(endOfLastMatch, last);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
// populates dateFormatRegex
|
||||
static DateRegexInitializer const initializer;
|
||||
|
||||
} // namespace
|
||||
|
||||
bool arangodb::basics::parse_dateTime(std::string const& dateTimeIn, tp_sys_clock_ms& date_tp) {
|
||||
bool arangodb::basics::parseDateTime(std::string const& dateTimeIn, arangodb::tp_sys_clock_ms& date_tp) {
|
||||
using namespace date;
|
||||
using namespace std::chrono;
|
||||
|
||||
|
@ -233,7 +775,7 @@ bool arangodb::basics::parse_dateTime(std::string const& dateTimeIn, tp_sys_cloc
|
|||
return true;
|
||||
}
|
||||
|
||||
bool arangodb::basics::regex_isoDuration(std::string const& isoDuration,
|
||||
bool arangodb::basics::regexIsoDuration(std::string const& isoDuration,
|
||||
std::smatch& durationParts) {
|
||||
if (isoDuration.length() <= 1) {
|
||||
return false;
|
||||
|
@ -245,3 +787,9 @@ bool arangodb::basics::regex_isoDuration(std::string const& isoDuration,
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string arangodb::basics::formatDate(std::string const& formatString,
|
||||
arangodb::tp_sys_clock_ms const& dateValue) {
|
||||
return ::executeDateFormatRegex(formatString, dateValue);
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
#ifndef ARANGODB_BASICS_DATETIME_H
|
||||
#define ARANGODB_BASICS_DATETIME_H 1
|
||||
|
||||
#include "Basics/Common.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <regex>
|
||||
|
||||
|
@ -32,9 +34,15 @@ using tp_sys_clock_ms =
|
|||
std::chrono::time_point<std::chrono::system_clock, std::chrono::milliseconds>;
|
||||
|
||||
namespace basics {
|
||||
bool parse_dateTime(std::string const& dateTime, tp_sys_clock_ms& date_tp);
|
||||
bool parseDateTime(std::string const& dateTime,
|
||||
tp_sys_clock_ms& date_tp);
|
||||
|
||||
bool regex_isoDuration(std::string const& isoDuration, std::smatch& durationParts);
|
||||
bool regexIsoDuration(std::string const& isoDuration,
|
||||
std::smatch& durationParts);
|
||||
|
||||
/// @brief formats a date(time) value according to formatString
|
||||
std::string formatDate(std::string const& formatString,
|
||||
tp_sys_clock_ms const& dateValue);
|
||||
} // namespace basics
|
||||
} // namespace arangodb
|
||||
|
||||
|
|
|
@ -231,6 +231,11 @@ add_library(${LIB_ARANGO} STATIC
|
|||
Logger/LoggerBufferFeature.cpp
|
||||
Logger/LoggerFeature.cpp
|
||||
Logger/LoggerStream.cpp
|
||||
Maskings/AttributeMasking.cpp
|
||||
Maskings/Collection.cpp
|
||||
Maskings/Maskings.cpp
|
||||
Maskings/Path.cpp
|
||||
Maskings/RandomStringMask.cpp
|
||||
ProgramOptions/Option.cpp
|
||||
ProgramOptions/ProgramOptions.cpp
|
||||
ProgramOptions/Section.cpp
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// 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 "AttributeMasking.h"
|
||||
|
||||
#include "Basics/StringUtils.h"
|
||||
#include "Logger/Logger.h"
|
||||
#include "Maskings/RandomStringMask.h"
|
||||
|
||||
using namespace arangodb;
|
||||
using namespace arangodb::maskings;
|
||||
|
||||
void arangodb::maskings::InstallMaskings() {
|
||||
AttributeMasking::installMasking("randomString", RandomStringMask::create);
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, ParseResult<AttributeMasking> (*)(Path, Maskings*, VPackSlice const&)> AttributeMasking::_maskings;
|
||||
|
||||
ParseResult<AttributeMasking> AttributeMasking::parse(Maskings* maskings,
|
||||
VPackSlice const& def) {
|
||||
if (!def.isObject()) {
|
||||
return ParseResult<AttributeMasking>(
|
||||
ParseResult<AttributeMasking>::PARSE_FAILED,
|
||||
"expecting an object for collection definition");
|
||||
}
|
||||
|
||||
std::string path = "";
|
||||
std::string type = "";
|
||||
|
||||
for (auto const& entry : VPackObjectIterator(def, false)) {
|
||||
std::string key = entry.key.copyString();
|
||||
|
||||
if (key == "type") {
|
||||
if (!entry.value.isString()) {
|
||||
return ParseResult<AttributeMasking>(ParseResult<AttributeMasking>::ILLEGAL_PARAMETER,
|
||||
"type must be a string");
|
||||
}
|
||||
|
||||
type = entry.value.copyString();
|
||||
} else if (key == "path") {
|
||||
if (!entry.value.isString()) {
|
||||
return ParseResult<AttributeMasking>(ParseResult<AttributeMasking>::ILLEGAL_PARAMETER,
|
||||
"path must be a string");
|
||||
}
|
||||
|
||||
path = entry.value.copyString();
|
||||
}
|
||||
}
|
||||
|
||||
if (path.empty()) {
|
||||
return ParseResult<AttributeMasking>(ParseResult<AttributeMasking>::ILLEGAL_PARAMETER,
|
||||
"path must not be empty");
|
||||
}
|
||||
|
||||
ParseResult<Path> ap = Path::parse(path);
|
||||
|
||||
if (ap.status != ParseResult<Path>::VALID) {
|
||||
return ParseResult<AttributeMasking>(
|
||||
(ParseResult<AttributeMasking>::StatusCode)(int)ap.status, ap.message);
|
||||
}
|
||||
|
||||
auto const& it = _maskings.find(type);
|
||||
|
||||
if (it == _maskings.end()) {
|
||||
return ParseResult<AttributeMasking>(
|
||||
ParseResult<AttributeMasking>::UNKNOWN_TYPE,
|
||||
"unknown attribute masking type '" + type + "'");
|
||||
}
|
||||
|
||||
return it->second(ap.result, maskings, def);
|
||||
}
|
||||
|
||||
bool AttributeMasking::match(std::vector<std::string> const& path) const {
|
||||
return _path.match(path);
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// 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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef ARANGODB_MASKINGS_ATTRIBUTE_MASKING_H
|
||||
#define ARANGODB_MASKINGS_ATTRIBUTE_MASKING_H 1
|
||||
|
||||
#include "Basics/Common.h"
|
||||
|
||||
#include <velocypack/Builder.h>
|
||||
#include <velocypack/Iterator.h>
|
||||
#include <velocypack/Parser.h>
|
||||
#include <velocypack/Slice.h>
|
||||
#include <velocypack/velocypack-aliases.h>
|
||||
|
||||
#include "Maskings/MaskingFunction.h"
|
||||
#include "Maskings/ParseResult.h"
|
||||
#include "Maskings/Path.h"
|
||||
|
||||
namespace arangodb {
|
||||
namespace maskings {
|
||||
void InstallMaskings();
|
||||
|
||||
class AttributeMasking {
|
||||
public:
|
||||
static ParseResult<AttributeMasking> parse(Maskings*, VPackSlice const&);
|
||||
static void installMasking(std::string const& name, ParseResult<AttributeMasking> (* func)(Path, Maskings*, VPackSlice const&)) {
|
||||
_maskings[name] = func;
|
||||
}
|
||||
|
||||
public:
|
||||
AttributeMasking() = default;
|
||||
|
||||
AttributeMasking(Path const& path, MaskingFunction* func) : _path(path) {
|
||||
_func.reset(func);
|
||||
}
|
||||
|
||||
bool match(std::vector<std::string> const&) const;
|
||||
|
||||
MaskingFunction* func() const { return _func.get(); }
|
||||
|
||||
private:
|
||||
static std::unordered_map<std::string, ParseResult<AttributeMasking> (*)(Path, Maskings*, VPackSlice const&)> _maskings;
|
||||
|
||||
private:
|
||||
Path _path;
|
||||
std::shared_ptr<MaskingFunction> _func;
|
||||
};
|
||||
} // namespace maskings
|
||||
} // namespace arangodb
|
||||
|
||||
#endif
|
|
@ -0,0 +1,99 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// 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 "Collection.h"
|
||||
|
||||
#include "Logger/Logger.h"
|
||||
|
||||
using namespace arangodb;
|
||||
using namespace arangodb::maskings;
|
||||
|
||||
ParseResult<Collection> Collection::parse(Maskings* maskings, VPackSlice const& def) {
|
||||
if (!def.isObject()) {
|
||||
return ParseResult<Collection>(
|
||||
ParseResult<Collection>::PARSE_FAILED,
|
||||
"expecting an object for collection definition");
|
||||
}
|
||||
|
||||
std::string type = "";
|
||||
std::vector<AttributeMasking> attributes;
|
||||
|
||||
for (auto const& entry : VPackObjectIterator(def, false)) {
|
||||
std::string key = entry.key.copyString();
|
||||
|
||||
if (key == "type") {
|
||||
if (!entry.value.isString()) {
|
||||
return ParseResult<Collection>(
|
||||
ParseResult<Collection>::ILLEGAL_PARAMETER,
|
||||
"expecting a string for collection type");
|
||||
}
|
||||
|
||||
type = entry.value.copyString();
|
||||
} else if (key == "maskings") {
|
||||
if (!entry.value.isArray()) {
|
||||
return ParseResult<Collection>(
|
||||
ParseResult<Collection>::ILLEGAL_PARAMETER,
|
||||
"expecting an array for collection maskings");
|
||||
}
|
||||
|
||||
for (auto const& mask : VPackArrayIterator(entry.value)) {
|
||||
ParseResult<AttributeMasking> am = AttributeMasking::parse(maskings, mask);
|
||||
|
||||
if (am.status != ParseResult<AttributeMasking>::VALID) {
|
||||
return ParseResult<Collection>((ParseResult<Collection>::StatusCode)(
|
||||
int)am.status,
|
||||
am.message);
|
||||
}
|
||||
|
||||
attributes.push_back(am.result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CollectionSelection selection = CollectionSelection::FULL;
|
||||
|
||||
if (type == "full") {
|
||||
selection = CollectionSelection::FULL;
|
||||
} else if (type == "exclude") {
|
||||
selection = CollectionSelection::EXCLUDE;
|
||||
} else if (type == "masked") {
|
||||
selection = CollectionSelection::MASKED;
|
||||
} else if (type == "structure") {
|
||||
selection = CollectionSelection::STRUCTURE;
|
||||
} else {
|
||||
return ParseResult<Collection>(ParseResult<Collection>::UNKNOWN_TYPE,
|
||||
"found unknown collection type '" + type +
|
||||
"'");
|
||||
}
|
||||
|
||||
return ParseResult<Collection>(Collection(selection, attributes));
|
||||
}
|
||||
|
||||
MaskingFunction* Collection::masking(std::vector<std::string> const& path) {
|
||||
for (auto const& m : _maskings) {
|
||||
if (m.match(path)) {
|
||||
return m.func();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// 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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef ARANGODB_MASKINGS_COLLECTION_H
|
||||
#define ARANGODB_MASKINGS_COLLECTION_H 1
|
||||
|
||||
#include "Basics/Common.h"
|
||||
|
||||
#include <velocypack/Builder.h>
|
||||
#include <velocypack/Iterator.h>
|
||||
#include <velocypack/Parser.h>
|
||||
#include <velocypack/Slice.h>
|
||||
#include <velocypack/velocypack-aliases.h>
|
||||
|
||||
#include "Maskings/AttributeMasking.h"
|
||||
#include "Maskings/CollectionFilter.h"
|
||||
#include "Maskings/CollectionSelection.h"
|
||||
#include "Maskings/ParseResult.h"
|
||||
|
||||
namespace arangodb {
|
||||
namespace maskings {
|
||||
class Collection {
|
||||
public:
|
||||
static ParseResult<Collection> parse(Maskings* maskings, VPackSlice const&);
|
||||
|
||||
public:
|
||||
Collection() {}
|
||||
|
||||
Collection(CollectionSelection selection, std::vector<AttributeMasking> const& maskings)
|
||||
: _selection(selection), _maskings(maskings) {}
|
||||
|
||||
CollectionSelection selection() const noexcept { return _selection; }
|
||||
|
||||
MaskingFunction* masking(std::vector<std::string> const& path);
|
||||
|
||||
private:
|
||||
CollectionSelection _selection;
|
||||
// LATER: CollectionFilter _filter;
|
||||
std::vector<AttributeMasking> _maskings;
|
||||
};
|
||||
} // namespace maskings
|
||||
} // namespace arangodb
|
||||
|
||||
#endif
|
|
@ -0,0 +1,34 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// 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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef ARANGODB_MASKINGS_COLLECTION_FILTER_H
|
||||
#define ARANGODB_MASKINGS_COLLECTION_FILTER_H 1
|
||||
|
||||
#include "Basics/Common.h"
|
||||
|
||||
namespace arangodb {
|
||||
namespace maskings {
|
||||
class CollectionFilter {};
|
||||
} // namespace maskings
|
||||
} // namespace arangodb
|
||||
|
||||
#endif
|
|
@ -0,0 +1,34 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// 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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef ARANGODB_MASKINGS_COLLECTION_SELECTION_H
|
||||
#define ARANGODB_MASKINGS_COLLECTION_SELECTION_H 1
|
||||
|
||||
#include "Basics/Common.h"
|
||||
|
||||
namespace arangodb {
|
||||
namespace maskings {
|
||||
enum class CollectionSelection { FULL, MASKED, EXCLUDE, STRUCTURE };
|
||||
} // namespace maskings
|
||||
} // namespace arangodb
|
||||
|
||||
#endif
|
|
@ -0,0 +1,62 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// 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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef ARANGODB_MASKINGS_MASKING_FUNCTION_H
|
||||
#define ARANGODB_MASKINGS_MASKING_FUNCTION_H 1
|
||||
|
||||
#include "Basics/Common.h"
|
||||
|
||||
#include "Basics/Utf8Helper.h"
|
||||
|
||||
#include <velocypack/Builder.h>
|
||||
#include <velocypack/Iterator.h>
|
||||
#include <velocypack/Parser.h>
|
||||
#include <velocypack/Slice.h>
|
||||
#include <velocypack/velocypack-aliases.h>
|
||||
|
||||
namespace arangodb {
|
||||
namespace maskings {
|
||||
class Maskings;
|
||||
|
||||
class MaskingFunction {
|
||||
public:
|
||||
static bool isNameChar(UChar32 ch) {
|
||||
return u_isalpha(ch) || u_isdigit(ch) || ch == U'_' || ch == U'-';
|
||||
}
|
||||
|
||||
public:
|
||||
explicit MaskingFunction(Maskings* maskings) : _maskings(maskings) {}
|
||||
virtual ~MaskingFunction() {}
|
||||
|
||||
public:
|
||||
virtual VPackValue mask(bool, std::string& buffer) const = 0;
|
||||
virtual VPackValue mask(std::string const&, std::string& buffer) const = 0;
|
||||
virtual VPackValue mask(int64_t, std::string& buffer) const = 0;
|
||||
virtual VPackValue mask(double, std::string& buffer) const = 0;
|
||||
|
||||
protected:
|
||||
Maskings* _maskings;
|
||||
};
|
||||
} // namespace maskings
|
||||
} // namespace arangodb
|
||||
|
||||
#endif
|
|
@ -0,0 +1,358 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// 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 <iostream>
|
||||
|
||||
#include "Basics/FileUtils.h"
|
||||
#include "Logger/Logger.h"
|
||||
#include "Random/RandomGenerator.h"
|
||||
|
||||
#include <velocypack/Iterator.h>
|
||||
#include <velocypack/Parser.h>
|
||||
#include <velocypack/velocypack-aliases.h>
|
||||
|
||||
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(DEBUG, Logger::CONFIG) << msg;
|
||||
|
||||
return MaskingsResult(MaskingsResult::CANNOT_READ_FILE, msg);
|
||||
}
|
||||
|
||||
LOG_TOPIC(DEBUG, Logger::CONFIG) << "found maskings file '" << filename;
|
||||
|
||||
if (definition.empty()) {
|
||||
std::string msg = "maskings file '" + filename + "' is empty";
|
||||
LOG_TOPIC(DEBUG, Logger::CONFIG) << msg;
|
||||
return MaskingsResult(MaskingsResult::CANNOT_READ_FILE, msg);
|
||||
}
|
||||
|
||||
std::unique_ptr<Maskings> maskings(new Maskings{});
|
||||
|
||||
maskings.get()->_randomSeed = RandomGenerator::interval(UINT64_MAX);
|
||||
|
||||
try {
|
||||
std::shared_ptr<VPackBuilder> parsed = velocypack::Parser::fromJson(definition);
|
||||
|
||||
ParseResult<Maskings> res = maskings->parse(parsed->slice());
|
||||
|
||||
if (res.status != ParseResult<Maskings>::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(DEBUG, Logger::CONFIG) << msg << ". file content: " << definition;
|
||||
|
||||
return MaskingsResult(MaskingsResult::CANNOT_PARSE_FILE, msg);
|
||||
}
|
||||
}
|
||||
|
||||
ParseResult<Maskings> Maskings::parse(VPackSlice const& def) {
|
||||
if (!def.isObject()) {
|
||||
return ParseResult<Maskings>(ParseResult<Maskings>::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(TRACE, Logger::CONFIG) << "default masking";
|
||||
|
||||
if (_hasDefaultCollection) {
|
||||
return ParseResult<Maskings>(ParseResult<Maskings>::DUPLICATE_COLLECTION,
|
||||
"duplicate default entry");
|
||||
}
|
||||
} else {
|
||||
LOG_TOPIC(TRACE, Logger::CONFIG) << "masking collection '" << key << "'";
|
||||
|
||||
if (_collections.find(key) != _collections.end()) {
|
||||
return ParseResult<Maskings>(ParseResult<Maskings>::DUPLICATE_COLLECTION,
|
||||
"duplicate collection entry '" + key + "'");
|
||||
}
|
||||
}
|
||||
|
||||
ParseResult<Collection> c = Collection::parse(this, entry.value);
|
||||
|
||||
if (c.status != ParseResult<Collection>::VALID) {
|
||||
return ParseResult<Maskings>((ParseResult<Maskings>::StatusCode)(int)c.status,
|
||||
c.message);
|
||||
}
|
||||
|
||||
if (key == "*") {
|
||||
_hasDefaultCollection = true;
|
||||
_defaultCollection = c.result;
|
||||
} else {
|
||||
_collections[key] = c.result;
|
||||
}
|
||||
}
|
||||
|
||||
return ParseResult<Maskings>(ParseResult<Maskings>::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<std::string>& path,
|
||||
std::string& buffer, VPackSlice const& data) {
|
||||
static std::string xxxx("xxxx");
|
||||
|
||||
if (path.size() == 1) {
|
||||
if (path[0] == "_key" || path[0] == "_id" || path[0] == "_rev" ||
|
||||
path[0] == "_from" || path[0] == "_to") {
|
||||
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<std::string>& 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<std::string>& 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<std::string> 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<VPackBuilder> builder = VPackParser::fromJson(q, p - q);
|
||||
|
||||
addMasked(*collection, result, builder->slice());
|
||||
|
||||
while (p < e && (*p == '\n' || *p == '\r')) {
|
||||
++p;
|
||||
}
|
||||
|
||||
q = p;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// 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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef ARANGODB_MASKINGS_MASKINGS_H
|
||||
#define ARANGODB_MASKINGS_MASKINGS_H 1
|
||||
|
||||
#include "Basics/Common.h"
|
||||
|
||||
#include <velocypack/Builder.h>
|
||||
#include <velocypack/Slice.h>
|
||||
#include <velocypack/velocypack-aliases.h>
|
||||
|
||||
#include "Basics/StringBuffer.h"
|
||||
#include "Maskings/Collection.h"
|
||||
#include "Maskings/ParseResult.h"
|
||||
|
||||
namespace arangodb {
|
||||
namespace maskings {
|
||||
class Maskings;
|
||||
|
||||
struct MaskingsResult {
|
||||
enum StatusCode : int {
|
||||
VALID,
|
||||
CANNOT_PARSE_FILE,
|
||||
CANNOT_READ_FILE,
|
||||
ILLEGAL_DEFINITION
|
||||
};
|
||||
|
||||
MaskingsResult(StatusCode s, std::string const& m)
|
||||
: status(s), message(m), maskings(nullptr) {}
|
||||
explicit MaskingsResult(std::unique_ptr<Maskings>&& m)
|
||||
: status(StatusCode::VALID), maskings(std::move(m)) {}
|
||||
|
||||
StatusCode status;
|
||||
std::string message;
|
||||
std::unique_ptr<Maskings> maskings;
|
||||
};
|
||||
|
||||
class Maskings {
|
||||
public:
|
||||
static MaskingsResult fromFile(std::string const&);
|
||||
|
||||
public:
|
||||
bool shouldDumpStructure(std::string const& name);
|
||||
bool shouldDumpData(std::string const& name);
|
||||
void mask(std::string const& name, basics::StringBuffer const& data,
|
||||
basics::StringBuffer& result);
|
||||
|
||||
uint64_t randomSeed() const noexcept { return _randomSeed; }
|
||||
|
||||
private:
|
||||
ParseResult<Maskings> parse(VPackSlice const&);
|
||||
VPackValue maskedItem(Collection& collection, std::vector<std::string>& path,
|
||||
std::string& buffer, VPackSlice const& data);
|
||||
void addMaskedArray(Collection& collection, VPackBuilder& builder,
|
||||
std::vector<std::string>& path, VPackSlice const& data);
|
||||
void addMaskedObject(Collection& collection, VPackBuilder& builder,
|
||||
std::vector<std::string>& path, VPackSlice const& data);
|
||||
void addMasked(Collection& collection, VPackBuilder& builder, VPackSlice const& data);
|
||||
void addMasked(Collection& collection, basics::StringBuffer&, VPackSlice const& data);
|
||||
|
||||
private:
|
||||
std::map<std::string, Collection> _collections;
|
||||
bool _hasDefaultCollection = false;
|
||||
Collection _defaultCollection;
|
||||
uint64_t _randomSeed = 0;
|
||||
};
|
||||
|
||||
} // namespace maskings
|
||||
} // namespace arangodb
|
||||
|
||||
#endif
|
|
@ -0,0 +1,51 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// 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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef ARANGODB_MASKINGS_PARSE_RESULT_H
|
||||
#define ARANGODB_MASKINGS_PARSE_RESULT_H
|
||||
|
||||
#include "Basics/Common.h"
|
||||
|
||||
template <typename T>
|
||||
struct ParseResult {
|
||||
enum StatusCode : int {
|
||||
VALID,
|
||||
PARSE_FAILED,
|
||||
DUPLICATE_COLLECTION,
|
||||
UNKNOWN_TYPE,
|
||||
ILLEGAL_PARAMETER
|
||||
};
|
||||
|
||||
ParseResult(StatusCode status) : status(status) {}
|
||||
|
||||
ParseResult(StatusCode status, std::string message)
|
||||
: status(status), message(message), result(T()) {}
|
||||
|
||||
ParseResult(T&& result)
|
||||
: status(StatusCode::VALID), result(std::move(result)) {}
|
||||
|
||||
StatusCode status;
|
||||
std::string message;
|
||||
T result;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,149 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// 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 "Collection.h"
|
||||
|
||||
#include "Basics/StringUtils.h"
|
||||
#include "Basics/Utf8Helper.h"
|
||||
#include "Logger/Logger.h"
|
||||
|
||||
using namespace arangodb;
|
||||
using namespace arangodb::maskings;
|
||||
|
||||
ParseResult<Path> Path::parse(std::string const& def) {
|
||||
if (def.empty()) {
|
||||
return ParseResult<Path>(ParseResult<Path>::ILLEGAL_PARAMETER,
|
||||
"path must not be empty");
|
||||
}
|
||||
|
||||
bool wildcard = false;
|
||||
|
||||
if (def[0] == '.') {
|
||||
wildcard = true;
|
||||
}
|
||||
|
||||
uint8_t const* p = reinterpret_cast<uint8_t const*>(def.c_str());
|
||||
int32_t off = 0;
|
||||
int32_t len = def.size();
|
||||
UChar32 ch;
|
||||
|
||||
if (wildcard) {
|
||||
U8_NEXT(p, off, len, ch);
|
||||
}
|
||||
|
||||
std::vector<std::string> components;
|
||||
std::string buffer;
|
||||
|
||||
while (off < len) {
|
||||
U8_NEXT(p, off, len, ch);
|
||||
|
||||
if (ch < 0) {
|
||||
return ParseResult<Path>(ParseResult<Path>::ILLEGAL_PARAMETER,
|
||||
"path '" + def + "' contains illegal UTF-8");
|
||||
} else if (ch == 46) {
|
||||
if (buffer.size() == 0) {
|
||||
return ParseResult<Path>(ParseResult<Path>::ILLEGAL_PARAMETER,
|
||||
"path '" + def +
|
||||
"' contains an empty component");
|
||||
}
|
||||
|
||||
components.push_back(buffer);
|
||||
buffer.clear();
|
||||
} else if (ch == 96 || ch == 180) { // windows does not like U'`' and U'´'
|
||||
UChar32 quote = ch;
|
||||
U8_NEXT(p, off, len, ch);
|
||||
|
||||
if (ch < 0) {
|
||||
return ParseResult<Path>(ParseResult<Path>::ILLEGAL_PARAMETER,
|
||||
"path '" + def + "' contains illegal UTF-8");
|
||||
}
|
||||
|
||||
while (off < len && ch != quote) {
|
||||
basics::Utf8Helper::appendUtf8Character(buffer, ch);
|
||||
U8_NEXT(p, off, len, ch);
|
||||
|
||||
if (ch < 0) {
|
||||
return ParseResult<Path>(ParseResult<Path>::ILLEGAL_PARAMETER,
|
||||
"path '" + def + "' contains illegal UTF-8");
|
||||
}
|
||||
}
|
||||
|
||||
if (ch != quote) {
|
||||
return ParseResult<Path>(ParseResult<Path>::ILLEGAL_PARAMETER,
|
||||
"path '" + def +
|
||||
"' contains an unbalanced quote");
|
||||
}
|
||||
|
||||
U8_NEXT(p, off, len, ch);
|
||||
|
||||
if (ch < 0) {
|
||||
return ParseResult<Path>(ParseResult<Path>::ILLEGAL_PARAMETER,
|
||||
"path '" + def + "' contains illegal UTF-8");
|
||||
}
|
||||
} else {
|
||||
basics::Utf8Helper::appendUtf8Character(buffer, ch);
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer.size() == 0) {
|
||||
return ParseResult<Path>(ParseResult<Path>::ILLEGAL_PARAMETER,
|
||||
"path '" + def + "' contains an empty component");
|
||||
}
|
||||
|
||||
components.push_back(buffer);
|
||||
|
||||
if (components.empty()) {
|
||||
return ParseResult<Path>(ParseResult<Path>::ILLEGAL_PARAMETER,
|
||||
"path '" + def + "' contains no component");
|
||||
}
|
||||
|
||||
return ParseResult<Path>(Path(wildcard, components));
|
||||
}
|
||||
|
||||
bool Path::match(std::vector<std::string> const& path) const {
|
||||
size_t cs = _components.size();
|
||||
size_t ps = path.size();
|
||||
|
||||
if (!_wildcard) {
|
||||
if (ps != cs) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ps < cs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t pi = ps;
|
||||
size_t ci = cs;
|
||||
|
||||
while (0 < ci) {
|
||||
if (path[pi - 1] != _components[ci - 1]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
--pi;
|
||||
--ci;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// 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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef ARANGODB_MASKINGS_PATH_H
|
||||
#define ARANGODB_MASKINGS_PATH_H 1
|
||||
|
||||
#include "Basics/Common.h"
|
||||
|
||||
#include "Maskings/ParseResult.h"
|
||||
|
||||
namespace arangodb {
|
||||
namespace maskings {
|
||||
class Path {
|
||||
public:
|
||||
static ParseResult<Path> parse(std::string const&);
|
||||
|
||||
public:
|
||||
Path() : _wildcard(false) {}
|
||||
|
||||
Path(bool wildcard, std::vector<std::string> const& components)
|
||||
: _wildcard(wildcard), _components(components) {}
|
||||
|
||||
bool match(std::vector<std::string> const& path) const;
|
||||
|
||||
private:
|
||||
bool _wildcard;
|
||||
std::vector<std::string> _components;
|
||||
};
|
||||
} // namespace maskings
|
||||
} // namespace arangodb
|
||||
|
||||
#endif
|
|
@ -0,0 +1,73 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// 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 "RandomStringMask.h"
|
||||
|
||||
#include "Basics/StringUtils.h"
|
||||
#include "Basics/fasthash.h"
|
||||
#include "Maskings/Maskings.h"
|
||||
|
||||
static std::string const xxxx("xxxx");
|
||||
|
||||
using namespace arangodb;
|
||||
using namespace arangodb::maskings;
|
||||
|
||||
ParseResult<AttributeMasking> RandomStringMask::create(Path path, Maskings* maskings,
|
||||
VPackSlice const&) {
|
||||
return ParseResult<AttributeMasking>(AttributeMasking(path, new RandomStringMask(maskings)));
|
||||
}
|
||||
|
||||
VPackValue RandomStringMask::mask(bool value, std::string&) const {
|
||||
return VPackValue(value);
|
||||
}
|
||||
|
||||
VPackValue RandomStringMask::mask(std::string const& data, std::string& buffer) const {
|
||||
uint64_t len = data.size();
|
||||
uint64_t hash;
|
||||
|
||||
hash = fasthash64(data.c_str(), data.size(), _maskings->randomSeed());
|
||||
|
||||
std::string hash64 = basics::StringUtils::encodeBase64(
|
||||
std::string((char const*)&hash, sizeof(decltype(hash))));
|
||||
|
||||
buffer.clear();
|
||||
buffer.reserve(len);
|
||||
buffer.append(hash64);
|
||||
|
||||
if (buffer.size() < len) {
|
||||
while (buffer.size() < len) {
|
||||
buffer.append(hash64);
|
||||
}
|
||||
|
||||
buffer.resize(len);
|
||||
}
|
||||
|
||||
return VPackValue(buffer);
|
||||
}
|
||||
|
||||
VPackValue RandomStringMask::mask(int64_t value, std::string&) const {
|
||||
return VPackValue(value);
|
||||
}
|
||||
|
||||
VPackValue RandomStringMask::mask(double value, std::string&) const {
|
||||
return VPackValue(value);
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// 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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef ARANGODB_MASKINGS_ATTRIBUTE_RANDOM_STRING_MASK_H
|
||||
#define ARANGODB_MASKINGS_ATTRIBUTE_RANDOM_STRING_MASK_H 1
|
||||
|
||||
#include "Maskings/AttributeMasking.h"
|
||||
#include "Maskings/MaskingFunction.h"
|
||||
#include "Maskings/ParseResult.h"
|
||||
|
||||
namespace arangodb {
|
||||
namespace maskings {
|
||||
class RandomStringMask : public MaskingFunction {
|
||||
public:
|
||||
static ParseResult<AttributeMasking> create(Path, Maskings*, VPackSlice const& def);
|
||||
|
||||
public:
|
||||
VPackValue mask(bool, std::string& buffer) const override;
|
||||
VPackValue mask(std::string const& data, std::string& buffer) const override;
|
||||
VPackValue mask(int64_t, std::string& buffer) const override;
|
||||
VPackValue mask(double, std::string& buffer) const override;
|
||||
|
||||
private:
|
||||
explicit RandomStringMask(Maskings* maskings) : MaskingFunction(maskings) {}
|
||||
};
|
||||
} // namespace maskings
|
||||
} // namespace arangodb
|
||||
|
||||
#endif
|
|
@ -38,9 +38,15 @@ UniformCharacter::UniformCharacter(std::string const& characters)
|
|||
UniformCharacter::UniformCharacter(size_t length, std::string const& characters)
|
||||
: _length(length), _characters(characters) {}
|
||||
|
||||
std::string UniformCharacter::random() { return random(_length); }
|
||||
char UniformCharacter::randomChar() const {
|
||||
size_t r = RandomGenerator::interval((uint32_t)(_characters.size() - 1));
|
||||
|
||||
std::string UniformCharacter::random(size_t length) {
|
||||
return _characters[r];
|
||||
}
|
||||
|
||||
std::string UniformCharacter::random() const { return random(_length); }
|
||||
|
||||
std::string UniformCharacter::random(size_t length) const {
|
||||
std::string buffer;
|
||||
buffer.reserve(length);
|
||||
|
||||
|
|
|
@ -38,11 +38,12 @@ class UniformCharacter {
|
|||
UniformCharacter(size_t length, std::string const& characters);
|
||||
|
||||
public:
|
||||
std::string random();
|
||||
std::string random(size_t length);
|
||||
std::string random() const;
|
||||
std::string random(size_t length) const;
|
||||
char randomChar() const;
|
||||
|
||||
private:
|
||||
size_t _length;
|
||||
size_t const _length;
|
||||
std::string const _characters;
|
||||
};
|
||||
} // namespace arangodb
|
||||
|
|
|
@ -38,7 +38,7 @@ SCENARIO("testing", "[datetime]") {
|
|||
|
||||
for (auto const& dateTime : datesToTest) {
|
||||
GIVEN(dateTime) {
|
||||
bool ret = parse_dateTime(dateTime, tp);
|
||||
bool ret = parseDateTime(dateTime, tp);
|
||||
|
||||
THEN(dateTime) { REQUIRE(ret == true); }
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ SCENARIO("testing", "[datetime]") {
|
|||
|
||||
for (auto const& dateTime : datesToFail) {
|
||||
GIVEN(dateTime) {
|
||||
bool ret = parse_dateTime(dateTime, tp);
|
||||
bool ret = parseDateTime(dateTime, tp);
|
||||
|
||||
THEN(dateTime) { REQUIRE(ret == false); }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
{ "maskings1": {
|
||||
"type": "masked",
|
||||
"maskings": [
|
||||
{
|
||||
"path": "´name´",
|
||||
"type": "xifyFront",
|
||||
"unmaskedLength": 1
|
||||
},
|
||||
{
|
||||
"path": ".`name`",
|
||||
"type": "xifyFront",
|
||||
"unmaskedLength": 2
|
||||
},
|
||||
{
|
||||
"path": "email",
|
||||
"type": "xifyFront",
|
||||
"unmaskedLength": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
"maskings2": {
|
||||
"type": "masked",
|
||||
"maskings": [
|
||||
{
|
||||
"path": "random",
|
||||
"type": "randomString"
|
||||
},
|
||||
{
|
||||
"path": "zip",
|
||||
"type": "zip"
|
||||
},
|
||||
{
|
||||
"path": "date",
|
||||
"type": "date",
|
||||
"begin": "1900-01-01",
|
||||
"end": "2017-12-31",
|
||||
"format": "%yyyy %mm %dd"
|
||||
},
|
||||
{
|
||||
"path": "integer",
|
||||
"type": "integer",
|
||||
"lower": -10,
|
||||
"upper": 10
|
||||
},
|
||||
{
|
||||
"path": "decimal",
|
||||
"type": "decimal",
|
||||
"lower": -10,
|
||||
"upper": 10,
|
||||
"scale": 2
|
||||
},
|
||||
{
|
||||
"path": "ccard",
|
||||
"type": "creditCard"
|
||||
},
|
||||
{
|
||||
"path": "phone",
|
||||
"type": "phone"
|
||||
},
|
||||
{
|
||||
"path": "email",
|
||||
"type": "email"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*jshint globalstrict:false, strict:false, maxlen:4000, unused:false */
|
||||
/*global arango */
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// @brief tests for dump/reload
|
||||
//
|
||||
// @file
|
||||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2019 ArangoDB GmbH, Cologne, Germany
|
||||
// Copyright 2010-2012 triagens 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
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
var db = require("@arangodb").db;
|
||||
var i, c;
|
||||
|
||||
try {
|
||||
db._dropDatabase("UnitTestsDumpSrc");
|
||||
} catch (err1) {
|
||||
}
|
||||
|
||||
db._createDatabase("UnitTestsDumpSrc");
|
||||
db._useDatabase("UnitTestsDumpSrc");
|
||||
|
||||
db._create("maskings1");
|
||||
|
||||
db.maskings1.save({
|
||||
_key: "1",
|
||||
|
||||
name: "Hallo World! This is a t0st a top-level",
|
||||
|
||||
blub: {
|
||||
name: "Hallo World! This is a t0st in a sub-object",
|
||||
},
|
||||
|
||||
email: [
|
||||
"testing arrays",
|
||||
"this is another one",
|
||||
{ something: "something else" },
|
||||
{ email: "within a subject" },
|
||||
{ name: [ "emails within a subject", "as list" ] }
|
||||
],
|
||||
|
||||
sub: {
|
||||
name: "this is a name leaf attribute",
|
||||
email: [ "in this case as list", "with more than one entry" ]
|
||||
}
|
||||
});
|
||||
|
||||
db._create("maskings2");
|
||||
|
||||
db.maskings2.save({
|
||||
_key: "2",
|
||||
|
||||
random: "a",
|
||||
zip: "12345",
|
||||
date: "2018-01-01",
|
||||
integer: 100,
|
||||
decimal: 100.12,
|
||||
ccard: "1234 1234 1234 1234",
|
||||
phone: "abcd 1234",
|
||||
email: "me@you.here"
|
||||
});
|
||||
})();
|
||||
|
||||
return {
|
||||
status: true
|
||||
};
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
/*jshint globalstrict:false, strict:false, maxlen:4000 */
|
||||
/*global assertEqual, assertTrue, assertFalse, assertNotNull */
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// @brief tests for dump/reload
|
||||
//
|
||||
// @file
|
||||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2019 ArangoDB GmbH, Cologne, Germany
|
||||
// Copyright 2010-2012 triagens 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
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var internal = require("internal");
|
||||
var jsunity = require("jsunity");
|
||||
let users = require("@arangodb/users");
|
||||
|
||||
function dumpMaskingSuite () {
|
||||
'use strict';
|
||||
var db = internal.db;
|
||||
|
||||
return {
|
||||
setUp : function () {
|
||||
},
|
||||
|
||||
tearDown : function () {
|
||||
},
|
||||
|
||||
testGeneral : function () {
|
||||
var c = db._collection("maskings1");
|
||||
var d = c.document("1");
|
||||
|
||||
assertNotNull(d, "document '1' was restored");
|
||||
assertEqual(d.name, "xxxxo xxxxd xxxs xs a xxxt a xxxxxxxxl");
|
||||
assertEqual(d.blub.name, "xxxlo xxxld xxis is a xxst in a xxxxxxxxct");
|
||||
assertEqual(d.email.length, 5);
|
||||
assertEqual(d.email[0], "xxxxing xxxays");
|
||||
assertEqual(d.email[1], "xhis is xxxxher one");
|
||||
assertEqual(d.email[2].something, "something else");
|
||||
assertEqual(d.email[3].email, "within a subject");
|
||||
assertEqual(d.email[4].name.length, 2);
|
||||
assertEqual(d.email[4].name[0], "xxxxls xxxxin a xxxxxct");
|
||||
assertEqual(d.email[4].name[1], "as xxst");
|
||||
assertEqual(d.sub.name, "xxis is a xxme xxaf xxxxxxxte");
|
||||
assertEqual(d.sub.email.length, 2);
|
||||
assertEqual(d.sub.email[0], "in this case as list");
|
||||
assertEqual(d.sub.email[1], "with more than one entry");
|
||||
},
|
||||
|
||||
testRandomString : function () {
|
||||
var c = db._collection("maskings2");
|
||||
var d = c.document("2");
|
||||
|
||||
assertFalse(d.random === "a");
|
||||
assertFalse(d.zip === "12345");
|
||||
assertFalse(d.date === "2018-01-01");
|
||||
assertFalse(d.integer === 100);
|
||||
assertFalse(d.ccard === "1234 1234 1234 1234");
|
||||
assertFalse(d.phone === "abcd 1234");
|
||||
assertFalse(d.emil === "me@you.here");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
jsunity.run(dumpMaskingSuite);
|
||||
|
||||
return jsunity.done();
|
Loading…
Reference in New Issue