Compare commits

...

9 Commits

Author SHA1 Message Date
Guillaume Hivert fd8a49e71c ARSN-69 Switch all JS files to TS files 2022-02-23 16:43:02 +01:00
Guillaume Hivert c4b9071048 ARSN-84 Fix coverage by using Istanbul and Jest
Jest coverage is disfunctionning because it's not able to cover code in
spawned subprocesses. Istanbul can, so the final process is to launch
nyc and jest together. Jest emit some coverage and nyc is getting the
coverage and its own coverage to emit the proper final coverage files.
2022-02-18 18:04:23 +01:00
Guillaume Hivert 3bdae8814a ARSN-84 Correct Jest configuration for test suites and coverage
Thanks to files renaming, we can follow as much as we can the jest
default configurations. The options are gone, and we're specifying only
the maxWorkers (because the test suite is linear, and bugs if we're
running it in parallel) and the collect coverage files.
The coverage script itself is joined into one command instead of three
to leverage the Jest builtin coverage.
2022-02-18 18:04:18 +01:00
Guillaume Hivert 3a7d9b7525 ARSN-84 Rename all test files from [name].js to [name].spec.js
In order to simplify jest configuration, we have to remane the files to
follow the jest convention (to have a .spec.js extension for test
files).
2022-02-18 18:04:14 +01:00
Guillaume Hivert 97cca52179 ARSN-84 Fix Jest timeout for long HealthProbeServer 2022-02-18 18:04:08 +01:00
Guillaume Hivert 93f4ae2c7e ARSN-84 Fix Jest bug in _arsenalError
You can check out the bug at
https://github.com/facebook/jest/issues/2549.
The bug in inherent to jest and is a known bug since years, because jest
is switching the VM from node to a custom VM from jest. Jest injects
its own set of globals. The Error provided by Jest is different from
the Error provided by node and the test `err instanceof Error` is false.
Error:
```
 Expected value to be equal to:
      true
 Received:
      false
```
2022-02-18 18:04:03 +01:00
Guillaume Hivert 2f326fdee4 ARSN-84 Fix redis commands in functional tests
The switch from mocha to Jest introduces some tests bugs.
As far as we can tell, jest is quicker than mocha, creating some
weird behaviour: some commands send to redis (with ioredis)
are working, and some aren’t. Our conclusion is that redis needs
to queue requests offline to avoid micro-disconnections from
redis in development. Otherwise, we got the following error:
```
  - StatsModel class › should correctly record a new request by default
one increment

    assert.ifError(received, expected)

    Expected value ifError to:
      null
    Received:
      [Error: Stream isn't writeable and enableOfflineQueue options is
false]

    Message:
      ifError got unwanted exception: Stream isn't writeable and
enableOfflineQueue options is false
```
Switching enableOfflineQueue to true makes the test suite to
success.
2022-02-18 18:03:58 +01:00
Guillaume Hivert 824dc63d54 ARSN-84 Fix linting with correct indentation and trailing commas 2022-02-18 18:03:52 +01:00
Guillaume Hivert 1135a2e42c ARSN-84 Introduce TypeScript, Jest and reconfigure ESLint
Introduces TypeScript with proper tsconfig.json with sane defaults,
add Jest as a test runner as a mocha replacement to have the
TS compiling on the fly and allowing mixed sources TS/JS in the
sources (and replacing the before and after of mocha with beforAll
and afterAll of Jest), and adding some ESLint configuration to make
ESLint happy.
2022-02-18 18:03:37 +01:00
297 changed files with 8483 additions and 5943 deletions

3
.gitignore vendored
View File

@ -13,3 +13,6 @@ node_modules/
# Coverage
coverage/
.nyc_output/
# TypeScript
build/

4
.prettierrc Normal file
View File

@ -0,0 +1,4 @@
{
"tabWidth": 4,
"singleQuote": true
}

197
index.js
View File

@ -1,197 +0,0 @@
module.exports = {
auth: require('./lib/auth/auth'),
constants: require('./lib/constants'),
db: require('./lib/db'),
errors: require('./lib/errors.js'),
errorUtils: require('./lib/errorUtils'),
shuffle: require('./lib/shuffle'),
stringHash: require('./lib/stringHash'),
ipCheck: require('./lib/ipCheck'),
jsutil: require('./lib/jsutil'),
https: {
ciphers: require('./lib/https/ciphers.js'),
dhparam: require('./lib/https/dh2048.js'),
},
algorithms: {
list: require('./lib/algos/list/exportAlgos'),
listTools: {
DelimiterTools: require('./lib/algos/list/tools'),
},
cache: {
LRUCache: require('./lib/algos/cache/LRUCache'),
},
stream: {
MergeStream: require('./lib/algos/stream/MergeStream'),
},
SortedSet: require('./lib/algos/set/SortedSet'),
},
policies: {
evaluators: require('./lib/policyEvaluator/evaluator.js'),
validateUserPolicy: require('./lib/policy/policyValidator')
.validateUserPolicy,
evaluatePrincipal: require('./lib/policyEvaluator/principal'),
RequestContext: require('./lib/policyEvaluator/RequestContext.js'),
requestUtils: require('./lib/policyEvaluator/requestUtils'),
actionMaps: require('./lib/policyEvaluator/utils/actionMaps'),
},
Clustering: require('./lib/Clustering'),
testing: {
matrix: require('./lib/testing/matrix.js'),
},
versioning: {
VersioningConstants: require('./lib/versioning/constants.js')
.VersioningConstants,
Version: require('./lib/versioning/Version.js').Version,
VersionID: require('./lib/versioning/VersionID.js'),
},
network: {
http: {
server: require('./lib/network/http/server'),
},
rpc: require('./lib/network/rpc/rpc'),
level: require('./lib/network/rpc/level-net'),
rest: {
RESTServer: require('./lib/network/rest/RESTServer'),
RESTClient: require('./lib/network/rest/RESTClient'),
},
RoundRobin: require('./lib/network/RoundRobin'),
probe: {
ProbeServer: require('./lib/network/probe/ProbeServer'),
HealthProbeServer:
require('./lib/network/probe/HealthProbeServer.js'),
Utils: require('./lib/network/probe/Utils.js'),
},
kmip: require('./lib/network/kmip'),
kmipClient: require('./lib/network/kmip/Client'),
},
s3routes: {
routes: require('./lib/s3routes/routes'),
routesUtils: require('./lib/s3routes/routesUtils'),
},
s3middleware: {
userMetadata: require('./lib/s3middleware/userMetadata'),
convertToXml: require('./lib/s3middleware/convertToXml'),
escapeForXml: require('./lib/s3middleware/escapeForXml'),
objectLegalHold: require('./lib/s3middleware/objectLegalHold'),
tagging: require('./lib/s3middleware/tagging'),
checkDateModifiedHeaders:
require('./lib/s3middleware/validateConditionalHeaders')
.checkDateModifiedHeaders,
validateConditionalHeaders:
require('./lib/s3middleware/validateConditionalHeaders')
.validateConditionalHeaders,
MD5Sum: require('./lib/s3middleware/MD5Sum'),
NullStream: require('./lib/s3middleware/nullStream'),
objectUtils: require('./lib/s3middleware/objectUtils'),
azureHelper: {
mpuUtils:
require('./lib/s3middleware/azureHelpers/mpuUtils'),
ResultsCollector:
require('./lib/s3middleware/azureHelpers/ResultsCollector'),
SubStreamInterface:
require('./lib/s3middleware/azureHelpers/SubStreamInterface'),
},
prepareStream: require('./lib/s3middleware/prepareStream'),
processMpuParts: require('./lib/s3middleware/processMpuParts'),
retention: require('./lib/s3middleware/objectRetention'),
lifecycleHelpers: require('./lib/s3middleware/lifecycleHelpers'),
},
storage: {
metadata: {
MetadataWrapper: require('./lib/storage/metadata/MetadataWrapper'),
bucketclient: {
BucketClientInterface:
require('./lib/storage/metadata/bucketclient/' +
'BucketClientInterface'),
LogConsumer:
require('./lib/storage/metadata/bucketclient/LogConsumer'),
},
file: {
BucketFileInterface:
require('./lib/storage/metadata/file/BucketFileInterface'),
MetadataFileServer:
require('./lib/storage/metadata/file/MetadataFileServer'),
MetadataFileClient:
require('./lib/storage/metadata/file/MetadataFileClient'),
},
inMemory: {
metastore:
require('./lib/storage/metadata/in_memory/metastore'),
metadata: require('./lib/storage/metadata/in_memory/metadata'),
bucketUtilities:
require('./lib/storage/metadata/in_memory/bucket_utilities'),
},
mongoclient: {
MongoClientInterface:
require('./lib/storage/metadata/mongoclient/' +
'MongoClientInterface'),
LogConsumer:
require('./lib/storage/metadata/mongoclient/LogConsumer'),
},
proxy: {
Server: require('./lib/storage/metadata/proxy/Server'),
},
},
data: {
DataWrapper: require('./lib/storage/data/DataWrapper'),
MultipleBackendGateway:
require('./lib/storage/data/MultipleBackendGateway'),
parseLC: require('./lib/storage/data/LocationConstraintParser'),
file: {
DataFileStore:
require('./lib/storage/data/file/DataFileStore'),
DataFileInterface:
require('./lib/storage/data/file/DataFileInterface'),
},
external: {
AwsClient: require('./lib/storage/data/external/AwsClient'),
AzureClient: require('./lib/storage/data/external/AzureClient'),
GcpClient: require('./lib/storage/data/external/GcpClient'),
GCP: require('./lib/storage/data/external/GCP/GcpService'),
GcpUtils: require('./lib/storage/data/external/GCP/GcpUtils'),
GcpSigner: require('./lib/storage/data/external/GCP/GcpSigner'),
PfsClient: require('./lib/storage/data/external/PfsClient'),
backendUtils: require('./lib/storage/data/external/utils'),
},
inMemory: {
datastore: require('./lib/storage/data/in_memory/datastore'),
},
},
utils: require('./lib/storage/utils'),
},
models: {
BackendInfo: require('./lib/models/BackendInfo'),
BucketInfo: require('./lib/models/BucketInfo'),
BucketAzureInfo: require('./lib/models/BucketAzureInfo'),
ObjectMD: require('./lib/models/ObjectMD'),
ObjectMDLocation: require('./lib/models/ObjectMDLocation'),
ObjectMDAzureInfo: require('./lib/models/ObjectMDAzureInfo'),
ARN: require('./lib/models/ARN'),
WebsiteConfiguration: require('./lib/models/WebsiteConfiguration'),
ReplicationConfiguration:
require('./lib/models/ReplicationConfiguration'),
LifecycleConfiguration:
require('./lib/models/LifecycleConfiguration'),
LifecycleRule: require('./lib/models/LifecycleRule'),
BucketPolicy: require('./lib/models/BucketPolicy'),
ObjectLockConfiguration:
require('./lib/models/ObjectLockConfiguration'),
NotificationConfiguration:
require('./lib/models/NotificationConfiguration'),
},
metrics: {
StatsClient: require('./lib/metrics/StatsClient'),
StatsModel: require('./lib/metrics/StatsModel'),
RedisClient: require('./lib/metrics/RedisClient'),
ZenkoMetrics: require('./lib/metrics/ZenkoMetrics'),
},
pensieve: {
credentialUtils: require('./lib/executables/pensieveCreds/utils'),
},
stream: {
readJSONStreamObject: require('./lib/stream/readJSONStreamObject'),
},
patches: {
locationConstraints: require('./lib/patches/locationConstraints'),
},
};

192
index.ts Normal file
View File

@ -0,0 +1,192 @@
export * as auth from './lib/auth/auth'
export { default as constants } from './lib/constants'
export { default as errors } from './lib/errors'
export { default as shuffle } from './lib/shuffle'
export { default as stringHash } from './lib/stringHash'
export * as db from './lib/db'
export * as errorUtils from './lib/errorUtils'
export * as ipCheck from './lib/ipCheck'
export * as jsutil from './lib/jsutil'
export * as https from './lib/https'
// algorithms: {
// list: require('./lib/algos/list/exportAlgos'),
// listTools: {
// DelimiterTools: require('./lib/algos/list/tools'),
// },
// cache: {
// LRUCache: require('./lib/algos/cache/LRUCache'),
// },
// stream: {
// MergeStream: require('./lib/algos/stream/MergeStream'),
// },
// SortedSet: require('./lib/algos/set/SortedSet'),
// },
// policies: {
// evaluators: require('./lib/policyEvaluator/evaluator.js'),
// validateUserPolicy: require('./lib/policy/policyValidator')
// .validateUserPolicy,
// evaluatePrincipal: require('./lib/policyEvaluator/principal'),
// RequestContext: require('./lib/policyEvaluator/RequestContext.js'),
// requestUtils: require('./lib/policyEvaluator/requestUtils'),
// actionMaps: require('./lib/policyEvaluator/utils/actionMaps'),
// },
// Clustering: require('./lib/Clustering'),
// testing: {
// matrix: require('./lib/testing/matrix.js'),
// },
// versioning: {
// VersioningConstants: require('./lib/versioning/constants.js')
// .VersioningConstants,
// Version: require('./lib/versioning/Version.js').Version,
// VersionID: require('./lib/versioning/VersionID.js'),
// },
// network: {
// http: {
// server: require('./lib/network/http/server'),
// },
// rpc: require('./lib/network/rpc/rpc'),
// level: require('./lib/network/rpc/level-net'),
// rest: {
// RESTServer: require('./lib/network/rest/RESTServer'),
// RESTClient: require('./lib/network/rest/RESTClient'),
// },
// RoundRobin: require('./lib/network/RoundRobin'),
// probe: {
// ProbeServer: require('./lib/network/probe/ProbeServer'),
// HealthProbeServer:
// require('./lib/network/probe/HealthProbeServer.js'),
// Utils: require('./lib/network/probe/Utils.js'),
// },
// kmip: require('./lib/network/kmip'),
// kmipClient: require('./lib/network/kmip/Client'),
// },
// s3routes: {
// routes: require('./lib/s3routes/routes'),
// routesUtils: require('./lib/s3routes/routesUtils'),
// },
// s3middleware: {
// userMetadata: require('./lib/s3middleware/userMetadata'),
// convertToXml: require('./lib/s3middleware/convertToXml'),
// escapeForXml: require('./lib/s3middleware/escapeForXml'),
// objectLegalHold: require('./lib/s3middleware/objectLegalHold'),
// tagging: require('./lib/s3middleware/tagging'),
// checkDateModifiedHeaders:
// require('./lib/s3middleware/validateConditionalHeaders')
// .checkDateModifiedHeaders,
// validateConditionalHeaders:
// require('./lib/s3middleware/validateConditionalHeaders')
// .validateConditionalHeaders,
// MD5Sum: require('./lib/s3middleware/MD5Sum'),
// NullStream: require('./lib/s3middleware/nullStream'),
// objectUtils: require('./lib/s3middleware/objectUtils'),
// azureHelper: {
// mpuUtils:
// require('./lib/s3middleware/azureHelpers/mpuUtils'),
// ResultsCollector:
// require('./lib/s3middleware/azureHelpers/ResultsCollector'),
// SubStreamInterface:
// require('./lib/s3middleware/azureHelpers/SubStreamInterface'),
// },
// prepareStream: require('./lib/s3middleware/prepareStream'),
// processMpuParts: require('./lib/s3middleware/processMpuParts'),
// retention: require('./lib/s3middleware/objectRetention'),
// lifecycleHelpers: require('./lib/s3middleware/lifecycleHelpers'),
// },
// storage: {
// metadata: {
// MetadataWrapper: require('./lib/storage/metadata/MetadataWrapper'),
// bucketclient: {
// BucketClientInterface:
// require('./lib/storage/metadata/bucketclient/' +
// 'BucketClientInterface'),
// LogConsumer:
// require('./lib/storage/metadata/bucketclient/LogConsumer'),
// },
// file: {
// BucketFileInterface:
// require('./lib/storage/metadata/file/BucketFileInterface'),
// MetadataFileServer:
// require('./lib/storage/metadata/file/MetadataFileServer'),
// MetadataFileClient:
// require('./lib/storage/metadata/file/MetadataFileClient'),
// },
// inMemory: {
// metastore:
// require('./lib/storage/metadata/in_memory/metastore'),
// metadata: require('./lib/storage/metadata/in_memory/metadata'),
// bucketUtilities:
// require('./lib/storage/metadata/in_memory/bucket_utilities'),
// },
// mongoclient: {
// MongoClientInterface:
// require('./lib/storage/metadata/mongoclient/' +
// 'MongoClientInterface'),
// LogConsumer:
// require('./lib/storage/metadata/mongoclient/LogConsumer'),
// },
// proxy: {
// Server: require('./lib/storage/metadata/proxy/Server'),
// },
// },
// data: {
// DataWrapper: require('./lib/storage/data/DataWrapper'),
// MultipleBackendGateway:
// require('./lib/storage/data/MultipleBackendGateway'),
// parseLC: require('./lib/storage/data/LocationConstraintParser'),
// file: {
// DataFileStore:
// require('./lib/storage/data/file/DataFileStore'),
// DataFileInterface:
// require('./lib/storage/data/file/DataFileInterface'),
// },
// external: {
// AwsClient: require('./lib/storage/data/external/AwsClient'),
// AzureClient: require('./lib/storage/data/external/AzureClient'),
// GcpClient: require('./lib/storage/data/external/GcpClient'),
// GCP: require('./lib/storage/data/external/GCP/GcpService'),
// GcpUtils: require('./lib/storage/data/external/GCP/GcpUtils'),
// GcpSigner: require('./lib/storage/data/external/GCP/GcpSigner'),
// PfsClient: require('./lib/storage/data/external/PfsClient'),
// backendUtils: require('./lib/storage/data/external/utils'),
// },
// inMemory: {
// datastore: require('./lib/storage/data/in_memory/datastore'),
// },
// },
// utils: require('./lib/storage/utils'),
// },
// models: {
// BackendInfo: require('./lib/models/BackendInfo'),
// BucketInfo: require('./lib/models/BucketInfo'),
// BucketAzureInfo: require('./lib/models/BucketAzureInfo'),
// ObjectMD: require('./lib/models/ObjectMD'),
// ObjectMDLocation: require('./lib/models/ObjectMDLocation'),
// ObjectMDAzureInfo: require('./lib/models/ObjectMDAzureInfo'),
// ARN: require('./lib/models/ARN'),
// WebsiteConfiguration: require('./lib/models/WebsiteConfiguration'),
// ReplicationConfiguration:
// require('./lib/models/ReplicationConfiguration'),
// LifecycleConfiguration:
// require('./lib/models/LifecycleConfiguration'),
// LifecycleRule: require('./lib/models/LifecycleRule'),
// BucketPolicy: require('./lib/models/BucketPolicy'),
// ObjectLockConfiguration:
// require('./lib/models/ObjectLockConfiguration'),
// NotificationConfiguration:
// require('./lib/models/NotificationConfiguration'),
// },
// metrics: {
// StatsClient: require('./lib/metrics/StatsClient'),
// StatsModel: require('./lib/metrics/StatsModel'),
// RedisClient: require('./lib/metrics/RedisClient'),
// ZenkoMetrics: require('./lib/metrics/ZenkoMetrics'),
// },
// pensieve: {
// credentialUtils: require('./lib/executables/pensieveCreds/utils'),
// },
// stream: {
// readJSONStreamObject: require('./lib/stream/readJSONStreamObject'),
// },
// patches: {
// locationConstraints: require('./lib/patches/locationConstraints'),
// },

View File

@ -1,8 +1,6 @@
'use strict'; // eslint-disable-line
import * as cluster from 'cluster';
const cluster = require('cluster');
class Clustering {
export default class Clustering {
/**
* Constructor
*
@ -259,5 +257,3 @@ class Clustering {
});
}
}
module.exports = Clustering;

View File

@ -1,4 +1,4 @@
const assert = require('assert');
import assert from 'assert';
/**
* @class
@ -6,13 +6,19 @@ const assert = require('assert');
* number of items and a Least Recently Used (LRU) strategy for
* eviction.
*/
class LRUCache {
export default class LRUCache {
_maxEntries;
_entryMap;
_entryCount;
_lruTail;
_lruHead;
/**
* @constructor
* @param {number} maxEntries - maximum number of entries kept in
* @param maxEntries - maximum number of entries kept in
* the cache
*/
constructor(maxEntries) {
constructor(maxEntries: number) {
assert(maxEntries >= 1);
this._maxEntries = maxEntries;
this.clear();
@ -22,12 +28,12 @@ class LRUCache {
* Add or update the value associated to a key in the cache,
* making it the most recently accessed for eviction purpose.
*
* @param {string} key - key to add
* @param {object} value - associated value (can be of any type)
* @return {boolean} true if the cache contained an entry with
* @param key - key to add
* @param value - associated value (can be of any type)
* @return true if the cache contained an entry with
* this key, false if it did not
*/
add(key, value) {
add(key: string, value): boolean {
let entry = this._entryMap[key];
if (entry) {
entry.value = value;
@ -54,12 +60,12 @@ class LRUCache {
* Get the value associated to a key in the cache, making it the
* most recently accessed for eviction purpose.
*
* @param {string} key - key of which to fetch the associated value
* @return {object|undefined} - returns the associated value if
* @param key - key of which to fetch the associated value
* @return returns the associated value if
* exists in the cache, or undefined if not found - either if the
* key was never added or if it has been evicted from the cache.
*/
get(key) {
get(key: string) {
const entry = this._entryMap[key];
if (entry) {
// make the entry the most recently used by re-pushing it
@ -74,12 +80,12 @@ class LRUCache {
/**
* Remove an entry from the cache if exists
*
* @param {string} key - key to remove
* @return {boolean} true if an entry has been removed, false if
* @param key - key to remove
* @return true if an entry has been removed, false if
* there was no entry with this key in the cache - either if the
* key was never added or if it has been evicted from the cache.
*/
remove(key) {
remove(key: string): boolean {
const entry = this._entryMap[key];
if (entry) {
this._removeEntry(entry);
@ -91,16 +97,14 @@ class LRUCache {
/**
* Get the current number of cached entries
*
* @return {number} current number of cached entries
* @return current number of cached entries
*/
count() {
count(): number {
return this._entryCount;
}
/**
* Remove all entries from the cache
*
* @return {undefined}
*/
clear() {
this._entryMap = {};
@ -113,8 +117,7 @@ class LRUCache {
* Push an entry to the front of the LRU list, making it the most
* recently accessed
*
* @param {object} entry - entry to push
* @return {undefined}
* @param entry - entry to push
*/
_lruPushEntry(entry) {
/* eslint-disable no-param-reassign */
@ -133,8 +136,7 @@ class LRUCache {
/**
* Remove an entry from the LRU list
*
* @param {object} entry - entry to remove
* @return {undefined}
* @param entry - entry to remove
*/
_lruRemoveEntry(entry) {
/* eslint-disable no-param-reassign */
@ -154,8 +156,7 @@ class LRUCache {
/**
* Helper function to remove an existing entry from the cache
*
* @param {object} entry - cache entry to remove
* @return {undefined}
* @param entry - cache entry to remove
*/
_removeEntry(entry) {
this._lruRemoveEntry(entry);
@ -163,5 +164,3 @@ class LRUCache {
this._entryCount -= 1;
}
}
module.exports = LRUCache;

View File

@ -1,7 +1,4 @@
'use strict'; // eslint-disable-line strict
const { FILTER_SKIP, SKIP_NONE } = require('./tools');
import { FILTER_SKIP, SKIP_NONE } from './tools';
// Use a heuristic to amortize the cost of JSON
// serialization/deserialization only on largest metadata where the
// potential for size reduction is high, considering the bulk of the
@ -22,7 +19,12 @@ const TRIM_METADATA_MIN_BLOB_SIZE = 10000;
/**
* Base class of listing extensions.
*/
class Extension {
export default class Extension {
parameters;
logger;
res?: any[];
keys: number;
/**
* This takes a list of parameters and a logger as the inputs.
* Derivatives should have their own format regarding parameters.
@ -51,14 +53,14 @@ class Extension {
* heavy unused fields, or left untouched (depending on size
* heuristics)
*/
trimMetadata(value) {
let ret = undefined;
trimMetadata(value: string): string {
let ret: any = undefined;
if (value.length >= TRIM_METADATA_MIN_BLOB_SIZE) {
try {
ret = JSON.parse(value);
delete ret.location;
ret = JSON.stringify(ret);
} catch (e) {
} catch (e: any) {
// Prefer returning an unfiltered data rather than
// stopping the service in case of parsing failure.
// The risk of this approach is a potential
@ -66,7 +68,8 @@ class Extension {
// used by repd.
this.logger.warn(
'Could not parse Object Metadata while listing',
{ err: e.toString() });
{ err: e.toString() }
);
}
}
return ret || value;
@ -96,7 +99,7 @@ class Extension {
* = 0: entry is accepted but not included (skipping)
* < 0: entry is not accepted, listing should finish
*/
filter(entry) {
filter(entry): number {
return entry ? FILTER_SKIP : FILTER_SKIP;
}
@ -105,20 +108,18 @@ class Extension {
* because it is skipping a range of delimited keys or a range of specific
* version when doing master version listing.
*
* @return {string} - the insight: a common prefix or a master key,
* @return the insight: a common prefix or a master key,
* or SKIP_NONE if there is no insight
*/
skipping() {
skipping(): string | undefined {
return SKIP_NONE;
}
/**
* Get the listing resutls. Format depends on derivatives' specific logic.
* @return {Array} - The listed elements
* @return The listed elements
*/
result() {
return this.res;
}
}
module.exports.default = Extension;

View File

@ -1,9 +1,12 @@
'use strict'; // eslint-disable-line strict
const { inc, checkLimit, listingParamsMasterKeysV0ToV1,
FILTER_END, FILTER_ACCEPT } = require('./tools');
import {
inc,
checkLimit,
listingParamsMasterKeysV0ToV1,
FILTER_END,
FILTER_ACCEPT,
} from './tools';
const DEFAULT_MAX_KEYS = 1000;
const VSConst = require('../../versioning/constants').VersioningConstants;
import { VersioningConstants as VSConst } from '../../versioning/constants';
const { DbPrefixes, BucketVersioningKeyFormat } = VSConst;
function numberDefault(num, defaultNum) {
@ -14,7 +17,22 @@ function numberDefault(num, defaultNum) {
/**
* Class for the MultipartUploads extension
*/
class MultipartUploads {
export class MultipartUploads {
params
vFormat
CommonPrefixes
Uploads
IsTruncated
NextKeyMarker
NextUploadIdMarker
prefixLength
queryPrefixLength
keys
maxKeys
delimiter
splitter
logger
/**
* Constructor of the extension
* Init and check parameters
@ -39,22 +57,26 @@ class MultipartUploads {
this.splitter = params.splitter;
this.logger = logger;
Object.assign(this, {
[BucketVersioningKeyFormat.v0]: {
genMDParams: this.genMDParamsV0,
getObjectKey: this.getObjectKeyV0,
},
[BucketVersioningKeyFormat.v1]: {
genMDParams: this.genMDParamsV1,
getObjectKey: this.getObjectKeyV1,
},
}[this.vFormat]);
Object.assign(
this,
{
[BucketVersioningKeyFormat.v0]: {
genMDParams: this.genMDParamsV0,
getObjectKey: this.getObjectKeyV0,
},
[BucketVersioningKeyFormat.v1]: {
genMDParams: this.genMDParamsV1,
getObjectKey: this.getObjectKeyV1,
},
}[this.vFormat]
);
}
genMDParamsV0() {
const params = {};
if (this.params.keyMarker) {
params.gt = `overview${this.params.splitter}` +
params.gt =
`overview${this.params.splitter}` +
`${this.params.keyMarker}${this.params.splitter}`;
if (this.params.uploadIdMarker) {
params.gt += `${this.params.uploadIdMarker}`;
@ -147,14 +169,20 @@ class MultipartUploads {
if (this.delimiter) {
const mpuPrefixSlice = `overview${this.splitter}`.length;
const mpuKey = key.slice(mpuPrefixSlice);
const commonPrefixIndex = mpuKey.indexOf(this.delimiter,
this.queryPrefixLength);
const commonPrefixIndex = mpuKey.indexOf(
this.delimiter,
this.queryPrefixLength
);
if (commonPrefixIndex === -1) {
this.addUpload(value);
} else {
this.addCommonPrefix(mpuKey.substring(0,
commonPrefixIndex + this.delimiter.length));
this.addCommonPrefix(
mpuKey.substring(
0,
commonPrefixIndex + this.delimiter.length
)
);
}
} else {
this.addUpload(value);
@ -182,7 +210,3 @@ class MultipartUploads {
};
}
}
module.exports = {
MultipartUploads,
};

View File

@ -1,14 +1,17 @@
'use strict'; // eslint-disable-line strict
import Extension from './Extension';
import { checkLimit, FILTER_END, FILTER_ACCEPT, FILTER_SKIP } from './tools';
const Extension = require('./Extension').default;
const { checkLimit, FILTER_END, FILTER_ACCEPT, FILTER_SKIP } = require('./tools');
const DEFAULT_MAX_KEYS = 10000;
/**
* Class of an extension doing the simple listing
*/
class List extends Extension {
export class List extends Extension {
maxKeys: number;
filterKey;
filterKeyStartsWith;
res: any[];
/**
* Constructor
* Set the logger and the res
@ -30,15 +33,17 @@ class List extends Extension {
}
genMDParams() {
const params = this.parameters ? {
gt: this.parameters.gt,
gte: this.parameters.gte || this.parameters.start,
lt: this.parameters.lt,
lte: this.parameters.lte || this.parameters.end,
keys: this.parameters.keys,
values: this.parameters.values,
} : {};
Object.keys(params).forEach(key => {
const params = this.parameters
? {
gt: this.parameters.gt,
gte: this.parameters.gte || this.parameters.start,
lt: this.parameters.lt,
lte: this.parameters.lte || this.parameters.end,
keys: this.parameters.keys,
values: this.parameters.values,
}
: {};
Object.keys(params).forEach((key) => {
if (params[key] === null || params[key] === undefined) {
delete params[key];
}
@ -53,29 +58,30 @@ class List extends Extension {
*
* @return {Boolean} Returns true if matches, else false.
*/
customFilter(value) {
let _value;
customFilter(value: string): boolean {
let _value: any;
try {
_value = JSON.parse(value);
} catch (e) {
} catch (e: any) {
// Prefer returning an unfiltered data rather than
// stopping the service in case of parsing failure.
// The risk of this approach is a potential
// reproduction of MD-692, where too much memory is
// used by repd.
this.logger.warn(
'Could not parse Object Metadata while listing',
{ err: e.toString() });
this.logger.warn('Could not parse Object Metadata while listing', {
err: e.toString(),
});
return false;
}
if (_value.customAttributes !== undefined) {
for (const key of Object.keys(_value.customAttributes)) {
if (this.filterKey !== undefined &&
key === this.filterKey) {
if (this.filterKey !== undefined && key === this.filterKey) {
return true;
}
if (this.filterKeyStartsWith !== undefined &&
key.startsWith(this.filterKeyStartsWith)) {
if (
this.filterKeyStartsWith !== undefined &&
key.startsWith(this.filterKeyStartsWith)
) {
return true;
}
}
@ -90,15 +96,17 @@ class List extends Extension {
* @return {number} - > 0 : continue listing
* < 0 : listing done
*/
filter(elem) {
filter(elem): number {
// Check first in case of maxkeys <= 0
if (this.keys >= this.maxKeys) {
return FILTER_END;
}
if ((this.filterKey !== undefined ||
this.filterKeyStartsWith !== undefined) &&
if (
(this.filterKey !== undefined ||
this.filterKeyStartsWith !== undefined) &&
typeof elem === 'object' &&
!this.customFilter(elem.value)) {
!this.customFilter(elem.value)
) {
return FILTER_SKIP;
}
if (typeof elem === 'object') {
@ -121,7 +129,3 @@ class List extends Extension {
return this.res;
}
}
module.exports = {
List,
};

View File

@ -1,9 +1,12 @@
'use strict'; // eslint-disable-line strict
const Extension = require('./Extension').default;
const { inc, listingParamsMasterKeysV0ToV1,
FILTER_END, FILTER_ACCEPT, FILTER_SKIP } = require('./tools');
const VSConst = require('../../versioning/constants').VersioningConstants;
import Extension from './Extension';
import {
inc,
listingParamsMasterKeysV0ToV1,
FILTER_END,
FILTER_ACCEPT,
FILTER_SKIP,
} from './tools';
import { VersioningConstants as VSConst } from '../../versioning/constants';
const { DbPrefixes, BucketVersioningKeyFormat } = VSConst;
/**
@ -14,7 +17,11 @@ const { DbPrefixes, BucketVersioningKeyFormat } = VSConst;
* @param {Number} delimiterIndex - 'folder' index in the path
* @return {String} - CommonPrefix
*/
function getCommonPrefix(key, delimiter, delimiterIndex) {
function getCommonPrefix(
key: string,
delimiter: string,
delimiterIndex: number
): string {
return key.substring(0, delimiterIndex + delimiter.length);
}
@ -30,7 +37,25 @@ function getCommonPrefix(key, delimiter, delimiterIndex) {
* @prop {String|undefined} prefix - prefix per amazon format
* @prop {Number} maxKeys - number of keys to list
*/
class Delimiter extends Extension {
export class Delimiter extends Extension {
CommonPrefixes: string[];
Contents: string[];
IsTruncated: boolean;
NextMarker?: string;
keys: number;
delimiter?: string;
prefix?: string;
maxKeys: number;
marker;
startAfter;
continuationToken;
alphabeticalOrder;
vFormat;
NextContinuationToken;
startMarker;
continueMarker;
nextContinueMarker;
/**
* Create a new Delimiter instance
* @constructor
@ -58,6 +83,7 @@ class Delimiter extends Extension {
constructor(parameters, logger, vFormat) {
super(parameters, logger);
// original listing parameters
this.keys = 0;
this.delimiter = parameters.delimiter;
this.prefix = parameters.prefix;
this.marker = parameters.marker;
@ -65,8 +91,9 @@ class Delimiter extends Extension {
this.startAfter = parameters.startAfter;
this.continuationToken = parameters.continuationToken;
this.alphabeticalOrder =
typeof parameters.alphabeticalOrder !== 'undefined' ?
parameters.alphabeticalOrder : true;
typeof parameters.alphabeticalOrder !== 'undefined'
? parameters.alphabeticalOrder
: true;
this.vFormat = vFormat || BucketVersioningKeyFormat.v0;
// results
@ -79,36 +106,44 @@ class Delimiter extends Extension {
this.startMarker = parameters.v2 ? 'startAfter' : 'marker';
this.continueMarker = parameters.v2 ? 'continuationToken' : 'marker';
this.nextContinueMarker = parameters.v2 ?
'NextContinuationToken' : 'NextMarker';
this.nextContinueMarker = parameters.v2
? 'NextContinuationToken'
: 'NextMarker';
if (this.delimiter !== undefined &&
if (
this.delimiter !== undefined &&
this[this.nextContinueMarker] !== undefined &&
this[this.nextContinueMarker].startsWith(this.prefix || '')) {
const nextDelimiterIndex =
this[this.nextContinueMarker].indexOf(this.delimiter,
this.prefix ? this.prefix.length : 0);
this[this.nextContinueMarker] =
this[this.nextContinueMarker].slice(0, nextDelimiterIndex +
this.delimiter.length);
this[this.nextContinueMarker].startsWith(this.prefix || '')
) {
const nextDelimiterIndex = this[this.nextContinueMarker].indexOf(
this.delimiter,
this.prefix ? this.prefix.length : 0
);
this[this.nextContinueMarker] = this[this.nextContinueMarker].slice(
0,
nextDelimiterIndex + this.delimiter.length
);
}
Object.assign(this, {
[BucketVersioningKeyFormat.v0]: {
genMDParams: this.genMDParamsV0,
getObjectKey: this.getObjectKeyV0,
skipping: this.skippingV0,
},
[BucketVersioningKeyFormat.v1]: {
genMDParams: this.genMDParamsV1,
getObjectKey: this.getObjectKeyV1,
skipping: this.skippingV1,
},
}[this.vFormat]);
Object.assign(
this,
{
[BucketVersioningKeyFormat.v0]: {
genMDParams: this.genMDParamsV0,
getObjectKey: this.getObjectKeyV0,
skipping: this.skippingV0,
},
[BucketVersioningKeyFormat.v1]: {
genMDParams: this.genMDParamsV1,
getObjectKey: this.getObjectKeyV1,
skipping: this.skippingV1,
},
}[this.vFormat]
);
}
genMDParamsV0() {
const params = {};
const params: { gte?: string; lt?: string; gt?: string } = {};
if (this.prefix) {
params.gte = this.prefix;
params.lt = inc(this.prefix);
@ -134,7 +169,7 @@ class Delimiter extends Extension {
* final state of the result if it is the case
* @return {Boolean} - indicates if the iteration has to stop
*/
_reachedMaxKeys() {
_reachedMaxKeys(): boolean {
if (this.keys >= this.maxKeys) {
// In cases of maxKeys <= 0 -> IsTruncated = false
this.IsTruncated = this.maxKeys > 0;
@ -151,7 +186,7 @@ class Delimiter extends Extension {
* @param {String} value - The value of the key
* @return {number} - indicates if iteration should continue
*/
addContents(key, value) {
addContents(key: string, value: string): number {
if (this._reachedMaxKeys()) {
return FILTER_END;
}
@ -161,11 +196,11 @@ class Delimiter extends Extension {
return FILTER_ACCEPT;
}
getObjectKeyV0(obj) {
getObjectKeyV0(obj: { key: string }) {
return obj.key;
}
getObjectKeyV1(obj) {
getObjectKeyV1(obj: { key: string }) {
return obj.key.slice(DbPrefixes.Master.length);
}
@ -180,13 +215,15 @@ class Delimiter extends Extension {
* @param {String} obj.value - The value of the element
* @return {number} - indicates if iteration should continue
*/
filter(obj) {
filter(obj: { key: string; value: string }): number {
const key = this.getObjectKey(obj);
const value = obj.value;
if ((this.prefix && !key.startsWith(this.prefix))
|| (this.alphabeticalOrder
&& typeof this[this.nextContinueMarker] === 'string'
&& key <= this[this.nextContinueMarker])) {
if (
(this.prefix && !key.startsWith(this.prefix)) ||
(this.alphabeticalOrder &&
typeof this[this.nextContinueMarker] === 'string' &&
key <= this[this.nextContinueMarker])
) {
return FILTER_SKIP;
}
if (this.delimiter) {
@ -206,10 +243,12 @@ class Delimiter extends Extension {
* @param {Number} index - after prefix starting point
* @return {Boolean} - indicates if iteration should continue
*/
addCommonPrefix(key, index) {
addCommonPrefix(key: string, index: number): boolean {
const commonPrefix = getCommonPrefix(key, this.delimiter, index);
if (this.CommonPrefixes.indexOf(commonPrefix) === -1
&& this[this.nextContinueMarker] !== commonPrefix) {
if (
this.CommonPrefixes.indexOf(commonPrefix) === -1 &&
this[this.nextContinueMarker] !== commonPrefix
) {
if (this._reachedMaxKeys()) {
return FILTER_END;
}
@ -228,7 +267,7 @@ class Delimiter extends Extension {
* @return {string} - the present range (NextMarker) if repd believes
* that it's enough and should move on
*/
skippingV0() {
skippingV0(): string {
return this[this.nextContinueMarker];
}
@ -239,7 +278,7 @@ class Delimiter extends Extension {
* @return {string} - the present range (NextMarker) if repd believes
* that it's enough and should move on
*/
skippingV1() {
skippingV1(): string {
return DbPrefixes.Master + this[this.nextContinueMarker];
}
@ -261,14 +300,17 @@ class Delimiter extends Extension {
Delimiter: this.delimiter,
};
if (this.parameters.v2) {
//
result.NextContinuationToken = this.IsTruncated
? this.NextContinuationToken : undefined;
? this.NextContinuationToken
: undefined;
} else {
result.NextMarker = (this.IsTruncated && this.delimiter)
? this.NextMarker : undefined;
//
result.NextMarker =
this.IsTruncated && this.delimiter
? this.NextMarker
: undefined;
}
return result;
}
}
module.exports = { Delimiter };

View File

@ -1,10 +1,8 @@
'use strict'; // eslint-disable-line strict
const Delimiter = require('./delimiter').Delimiter;
const Version = require('../../versioning/Version').Version;
const VSConst = require('../../versioning/constants').VersioningConstants;
import { Delimiter } from './delimiter';
import { Version } from '../../versioning/Version';
import { VersioningConstants as VSConst } from '../../versioning/constants';
const { BucketVersioningKeyFormat } = VSConst;
const { FILTER_ACCEPT, FILTER_SKIP, SKIP_NONE } = require('./tools');
import { FILTER_ACCEPT, FILTER_SKIP, SKIP_NONE } from './tools';
const VID_SEP = VSConst.VersionId.Separator;
const { DbPrefixes } = VSConst;
@ -13,7 +11,11 @@ const { DbPrefixes } = VSConst;
* Handle object listing with parameters. This extends the base class Delimiter
* to return the raw master versions of existing objects.
*/
class DelimiterMaster extends Delimiter {
export class DelimiterMaster extends Delimiter {
prvKey;
prvPHDKey;
inReplayPrefix;
/**
* Delimiter listing of master versions.
* @param {Object} parameters - listing parameters
@ -34,16 +36,19 @@ class DelimiterMaster extends Delimiter {
this.prvPHDKey = undefined;
this.inReplayPrefix = false;
Object.assign(this, {
[BucketVersioningKeyFormat.v0]: {
filter: this.filterV0,
skipping: this.skippingV0,
},
[BucketVersioningKeyFormat.v1]: {
filter: this.filterV1,
skipping: this.skippingV1,
},
}[this.vFormat]);
Object.assign(
this,
{
[BucketVersioningKeyFormat.v0]: {
filter: this.filterV0,
skipping: this.skippingV0,
},
[BucketVersioningKeyFormat.v1]: {
filter: this.filterV1,
skipping: this.skippingV1,
},
}[this.vFormat]
);
}
/**
@ -58,7 +63,7 @@ class DelimiterMaster extends Delimiter {
* @param {String} obj.value - The value of the element
* @return {number} - indicates if iteration should continue
*/
filterV0(obj) {
filterV0(obj: { key: string; value: string }): number {
let key = obj.key;
const value = obj.value;
@ -70,9 +75,11 @@ class DelimiterMaster extends Delimiter {
/* Skip keys not starting with the prefix or not alphabetically
* ordered. */
if ((this.prefix && !key.startsWith(this.prefix))
|| (typeof this[this.nextContinueMarker] === 'string' &&
key <= this[this.nextContinueMarker])) {
if (
(this.prefix && !key.startsWith(this.prefix)) ||
(typeof this[this.nextContinueMarker] === 'string' &&
key <= this[this.nextContinueMarker])
) {
return FILTER_SKIP;
}
@ -95,9 +102,12 @@ class DelimiterMaster extends Delimiter {
* NextMarker to the common prefix instead of the whole key
* value. (TODO: remove this test once ZENKO-1048 is fixed)
* */
if (key === this.prvKey || key === this[this.nextContinueMarker] ||
if (
key === this.prvKey ||
key === this[this.nextContinueMarker] ||
(this.delimiter &&
key.startsWith(this[this.nextContinueMarker]))) {
key.startsWith(this[this.nextContinueMarker]))
) {
/* master version already filtered */
return FILTER_SKIP;
}
@ -155,7 +165,7 @@ class DelimiterMaster extends Delimiter {
* @param {String} obj.value - The value of the element
* @return {number} - indicates if iteration should continue
*/
filterV1(obj) {
filterV1(obj: { key: string; value: string }): number {
// Filtering master keys in v1 is simply listing the master
// keys, as the state of version keys do not change the
// result, so we can use Delimiter method directly.
@ -167,8 +177,9 @@ class DelimiterMaster extends Delimiter {
// next marker or next continuation token:
// - foo/ : skipping foo/
// - foo : skipping foo.
const index = this[this.nextContinueMarker].
lastIndexOf(this.delimiter);
const index = this[this.nextContinueMarker].lastIndexOf(
this.delimiter
);
if (index === this[this.nextContinueMarker].length - 1) {
return this[this.nextContinueMarker];
}
@ -192,5 +203,3 @@ class DelimiterMaster extends Delimiter {
return DbPrefixes.Master + skipTo;
}
}
module.exports = { DelimiterMaster };

View File

@ -1,10 +1,13 @@
'use strict'; // eslint-disable-line strict
const Delimiter = require('./delimiter').Delimiter;
const Version = require('../../versioning/Version').Version;
const VSConst = require('../../versioning/constants').VersioningConstants;
const { inc, FILTER_END, FILTER_ACCEPT, FILTER_SKIP, SKIP_NONE } =
require('./tools');
import { Delimiter } from './delimiter';
import { Version } from '../../versioning/Version';
import { VersioningConstants as VSConst } from '../../versioning/constants';
import {
inc,
FILTER_END,
FILTER_ACCEPT,
FILTER_SKIP,
SKIP_NONE,
} from './tools';
const VID_SEP = VSConst.VersionId.Separator;
const { DbPrefixes, BucketVersioningKeyFormat } = VSConst;
@ -21,7 +24,16 @@ const { DbPrefixes, BucketVersioningKeyFormat } = VSConst;
* @prop {String|undefined} prefix - prefix per amazon format
* @prop {Number} maxKeys - number of keys to list
*/
class DelimiterVersions extends Delimiter {
export class DelimiterVersions extends Delimiter {
CommonPrefixes: string[];
Contents: string[];
IsTruncated: boolean;
NextMarker?: string;
keys: number;
delimiter?: string;
prefix?: string;
maxKeys: number;
constructor(parameters, logger, vFormat) {
super(parameters, logger, vFormat);
// specific to version listing
@ -35,18 +47,21 @@ class DelimiterVersions extends Delimiter {
this.NextVersionIdMarker = undefined;
this.inReplayPrefix = false;
Object.assign(this, {
[BucketVersioningKeyFormat.v0]: {
genMDParams: this.genMDParamsV0,
filter: this.filterV0,
skipping: this.skippingV0,
},
[BucketVersioningKeyFormat.v1]: {
genMDParams: this.genMDParamsV1,
filter: this.filterV1,
skipping: this.skippingV1,
},
}[this.vFormat]);
Object.assign(
this,
{
[BucketVersioningKeyFormat.v0]: {
genMDParams: this.genMDParamsV0,
filter: this.filterV0,
skipping: this.skippingV0,
},
[BucketVersioningKeyFormat.v1]: {
genMDParams: this.genMDParamsV1,
filter: this.filterV1,
skipping: this.skippingV1,
},
}[this.vFormat]
);
}
genMDParamsV0() {
@ -63,9 +78,10 @@ class DelimiterVersions extends Delimiter {
if (this.parameters.versionIdMarker) {
// versionIdMarker should always come with keyMarker
// but may not be the other way around
params.gt = this.parameters.keyMarker
+ VID_SEP
+ this.parameters.versionIdMarker;
params.gt =
this.parameters.keyMarker +
VID_SEP +
this.parameters.versionIdMarker;
} else {
params.gt = inc(this.parameters.keyMarker + VID_SEP);
}
@ -89,20 +105,27 @@ class DelimiterVersions extends Delimiter {
params[1].lt = inc(DbPrefixes.Version); // stop after the last version key
}
if (this.parameters.keyMarker) {
if (params[1].gte <= DbPrefixes.Version + this.parameters.keyMarker) {
if (
params[1].gte <=
DbPrefixes.Version + this.parameters.keyMarker
) {
delete params[0].gte;
delete params[1].gte;
params[0].gt = DbPrefixes.Master + inc(this.parameters.keyMarker + VID_SEP);
params[0].gt =
DbPrefixes.Master +
inc(this.parameters.keyMarker + VID_SEP);
if (this.parameters.versionIdMarker) {
// versionIdMarker should always come with keyMarker
// but may not be the other way around
params[1].gt = DbPrefixes.Version
+ this.parameters.keyMarker
+ VID_SEP
+ this.parameters.versionIdMarker;
params[1].gt =
DbPrefixes.Version +
this.parameters.keyMarker +
VID_SEP +
this.parameters.versionIdMarker;
} else {
params[1].gt = DbPrefixes.Version
+ inc(this.parameters.keyMarker + VID_SEP);
params[1].gt =
DbPrefixes.Version +
inc(this.parameters.keyMarker + VID_SEP);
}
}
}
@ -120,7 +143,7 @@ class DelimiterVersions extends Delimiter {
* * -1 if master key < version key
* * 1 if master key > version key
*/
compareObjects(masterObj, versionObj) {
compareObjects(masterObj, versionObj): number {
const masterKey = masterObj.key.slice(DbPrefixes.Master.length);
const versionKey = versionObj.key.slice(DbPrefixes.Version.length);
return masterKey < versionKey ? -1 : 1;
@ -136,7 +159,11 @@ class DelimiterVersions extends Delimiter {
* @param {String} obj.value - The value of the key
* @return {Boolean} - indicates if iteration should continue
*/
addContents(obj) {
addContents(obj: {
key: string;
versionId: string;
value: string;
}): boolean {
if (this._reachedMaxKeys()) {
return FILTER_END;
}
@ -163,7 +190,7 @@ class DelimiterVersions extends Delimiter {
* @param {String} obj.value - The value of the element
* @return {number} - indicates if iteration should continue
*/
filterV0(obj) {
filterV0(obj: { key: string; value: string }): number {
if (obj.key.startsWith(DbPrefixes.Replay)) {
this.inReplayPrefix = true;
return FILTER_SKIP;
@ -189,12 +216,14 @@ class DelimiterVersions extends Delimiter {
* @param {String} obj.value - The value of the element
* @return {number} - indicates if iteration should continue
*/
filterV1(obj) {
filterV1(obj: { key: string; value: string }): number {
// this function receives both M and V keys, but their prefix
// length is the same so we can remove their prefix without
// looking at the type of key
return this.filterCommon(obj.key.slice(DbPrefixes.Master.length),
obj.value);
return this.filterCommon(
obj.key.slice(DbPrefixes.Master.length),
obj.value
);
}
filterCommon(key, value) {
@ -207,14 +236,16 @@ class DelimiterVersions extends Delimiter {
if (versionIdIndex < 0) {
nonversionedKey = key;
this.masterKey = key;
this.masterVersionId =
Version.from(value).getVersionId() || 'null';
this.masterVersionId = Version.from(value).getVersionId() || 'null';
versionId = this.masterVersionId;
} else {
nonversionedKey = key.slice(0, versionIdIndex);
versionId = key.slice(versionIdIndex + 1);
// skip a version key if it is the master version
if (this.masterKey === nonversionedKey && this.masterVersionId === versionId) {
if (
this.masterKey === nonversionedKey &&
this.masterVersionId === versionId
) {
return FILTER_SKIP;
}
this.masterKey = undefined;
@ -222,7 +253,10 @@ class DelimiterVersions extends Delimiter {
}
if (this.delimiter) {
const baseIndex = this.prefix ? this.prefix.length : 0;
const delimiterIndex = nonversionedKey.indexOf(this.delimiter, baseIndex);
const delimiterIndex = nonversionedKey.indexOf(
this.delimiter,
baseIndex
);
if (delimiterIndex >= 0) {
return this.addCommonPrefix(nonversionedKey, delimiterIndex);
}
@ -249,8 +283,7 @@ class DelimiterVersions extends Delimiter {
return SKIP_NONE;
}
// skip to the same object key in both M and V range listings
return [DbPrefixes.Master + skipV0,
DbPrefixes.Version + skipV0];
return [DbPrefixes.Master + skipV0, DbPrefixes.Version + skipV0];
}
/**
@ -269,11 +302,10 @@ class DelimiterVersions extends Delimiter {
Versions: this.Contents,
IsTruncated: this.IsTruncated,
NextKeyMarker: this.IsTruncated ? this.NextMarker : undefined,
NextVersionIdMarker: this.IsTruncated ?
this.NextVersionIdMarker : undefined,
NextVersionIdMarker: this.IsTruncated
? this.NextVersionIdMarker
: undefined,
Delimiter: this.delimiter,
};
}
}
module.exports = { DelimiterVersions };

View File

@ -1,21 +1,25 @@
const assert = require('assert');
const { FILTER_END, FILTER_SKIP, SKIP_NONE } = require('./tools');
import assert from 'assert';
import { FILTER_END, FILTER_SKIP, SKIP_NONE } from './tools';
const MAX_STREAK_LENGTH = 100;
/**
* Handle the filtering and the skip mechanism of a listing result.
*/
class Skip {
export class Skip {
extension;
gteParams;
listingEndCb;
skipRangeCb;
streakLength;
/**
* @param {Object} params - skip parameters
* @param {Object} params.extension - delimiter extension used (required)
* @param {String} params.gte - current range gte (greater than or
* equal) used by the client code
*/
constructor(params) {
constructor(params: { extension: any; gte: string }) {
assert(params.extension);
this.extension = params.extension;
@ -47,7 +51,7 @@ class Skip {
* This function calls the listing end or the skip range callbacks if
* needed.
*/
filter(entry) {
filter(entry): void {
assert(this.listingEndCb);
assert(this.skipRangeCb);
@ -56,8 +60,10 @@ class Skip {
if (filteringResult === FILTER_END) {
this.listingEndCb();
} else if (filteringResult === FILTER_SKIP
&& skippingRange !== SKIP_NONE) {
} else if (
filteringResult === FILTER_SKIP &&
skippingRange !== SKIP_NONE
) {
if (++this.streakLength >= MAX_STREAK_LENGTH) {
const newRange = this._inc(skippingRange);
@ -73,7 +79,7 @@ class Skip {
}
}
_inc(str) {
_inc(str: string) {
if (!str) {
return str;
}
@ -83,6 +89,3 @@ class Skip {
return `${str.slice(0, str.length - 1)}${lastCharNewValue}`;
}
}
module.exports = Skip;

View File

@ -1,10 +1,11 @@
const { DbPrefixes } = require('../../versioning/constants').VersioningConstants;
import { VersioningConstants as VSConst } from '../../versioning/constants';
const { DbPrefixes } = VSConst
// constants for extensions
const SKIP_NONE = undefined; // to be inline with the values of NextMarker
const FILTER_ACCEPT = 1;
const FILTER_SKIP = 0;
const FILTER_END = -1;
export const SKIP_NONE = undefined; // to be inline with the values of NextMarker
export const FILTER_ACCEPT = 1;
export const FILTER_SKIP = 0;
export const FILTER_END = -1;
/**
* This function check if number is valid
@ -15,8 +16,8 @@ const FILTER_END = -1;
* @param {Number} limit - The limit to respect
* @return {Number} - The parsed number || limit
*/
function checkLimit(number, limit) {
const parsed = Number.parseInt(number, 10);
export function checkLimit(number: number, limit: number): number {
const parsed = Number.parseInt(number, 10)
const valid = !Number.isNaN(parsed) && (!limit || parsed <= limit);
return valid ? parsed : limit;
}
@ -28,7 +29,7 @@ function checkLimit(number, limit) {
* @return {string} - the incremented string
* or the input if it is not valid
*/
function inc(str) {
export function inc(str: string): string {
return str ? (str.slice(0, str.length - 1) +
String.fromCharCode(str.charCodeAt(str.length - 1) + 1)) : str;
}
@ -40,7 +41,7 @@ function inc(str) {
* @param {object} v0params - listing parameters for v0 format
* @return {object} - listing parameters for v1 format
*/
function listingParamsMasterKeysV0ToV1(v0params) {
export function listingParamsMasterKeysV0ToV1(v0params: any): any {
const v1params = Object.assign({}, v0params);
if (v0params.gt !== undefined) {
v1params.gt = `${DbPrefixes.Master}${v0params.gt}`;
@ -58,13 +59,3 @@ function listingParamsMasterKeysV0ToV1(v0params) {
}
return v1params;
}
module.exports = {
checkLimit,
inc,
listingParamsMasterKeysV0ToV1,
SKIP_NONE,
FILTER_END,
FILTER_SKIP,
FILTER_ACCEPT,
};

View File

@ -1,4 +1,4 @@
function indexOf(arr, value) {
export function indexOf<T>(arr: T[], value: T) {
if (!arr.length) {
return -1;
}
@ -22,10 +22,10 @@ function indexOf(arr, value) {
return -1;
}
function indexAtOrBelow(arr, value) {
let i;
let lo;
let hi;
export function indexAtOrBelow<T>(arr: T[], value: T) {
let i: number;
let lo: number;
let hi: number;
if (!arr.length || arr[0] > value) {
return -1;
@ -52,7 +52,7 @@ function indexAtOrBelow(arr, value) {
/*
* perform symmetric diff in O(m + n)
*/
function symDiff(k1, k2, v1, v2, cb) {
export function symDiff(k1, k2, v1, v2, cb) {
let i = 0;
let j = 0;
const n = k1.length;
@ -79,9 +79,3 @@ function symDiff(k1, k2, v1, v2, cb) {
j++;
}
}
module.exports = {
indexOf,
indexAtOrBelow,
symDiff,
};

View File

@ -1,14 +1,12 @@
const ArrayUtils = require('./ArrayUtils');
import * as ArrayUtils from './ArrayUtils';
class SortedSet {
export default class SortedSet<Key, Value> {
keys: Key[];
values: Value[];
constructor(obj) {
if (obj) {
this.keys = obj.keys;
this.values = obj.values;
} else {
this.clear();
}
constructor(obj?: { keys: Key[]; values: Value[] }) {
this.keys = obj?.keys ?? [];
this.values = obj?.values ?? [];
}
clear() {
@ -20,7 +18,7 @@ class SortedSet {
return this.keys.length;
}
set(key, value) {
set(key: Key, value: Value) {
const index = ArrayUtils.indexAtOrBelow(this.keys, key);
if (this.keys[index] === key) {
this.values[index] = value;
@ -30,17 +28,17 @@ class SortedSet {
this.values.splice(index + 1, 0, value);
}
isSet(key) {
isSet(key: Key) {
const index = ArrayUtils.indexOf(this.keys, key);
return index >= 0;
}
get(key) {
get(key: Key) {
const index = ArrayUtils.indexOf(this.keys, key);
return index >= 0 ? this.values[index] : undefined;
}
del(key) {
del(key: Key) {
const index = ArrayUtils.indexOf(this.keys, key);
if (index >= 0) {
this.keys.splice(index, 1);
@ -48,5 +46,3 @@ class SortedSet {
}
}
}
module.exports = SortedSet;

View File

@ -1,7 +1,17 @@
const stream = require('stream');
import stream from 'stream';
class MergeStream extends stream.Readable {
constructor(stream1, stream2, compare) {
export default class MergeStream extends stream.Readable {
_compare: (a: any, b: any) => number;
_streams: [stream.Readable, stream.Readable];
_peekItems: [undefined | null, undefined | null];
_streamEof: [boolean, boolean];
_streamToResume: stream.Readable | null;
constructor(
stream1: stream.Readable,
stream2: stream.Readable,
compare: (a: any, b: any) => number
) {
super({ objectMode: true });
this._compare = compare;
@ -16,13 +26,13 @@ class MergeStream extends stream.Readable {
this._streamEof = [false, false];
this._streamToResume = null;
stream1.on('data', item => this._onItem(stream1, item, 0, 1));
stream1.on('data', (item) => this._onItem(stream1, item, 0, 1));
stream1.once('end', () => this._onEnd(stream1, 0, 1));
stream1.once('error', err => this._onError(stream1, err, 0, 1));
stream1.once('error', (err) => this._onError(stream1, err, 0, 1));
stream2.on('data', item => this._onItem(stream2, item, 1, 0));
stream2.on('data', (item) => this._onItem(stream2, item, 1, 0));
stream2.once('end', () => this._onEnd(stream2, 1, 0));
stream2.once('error', err => this._onError(stream2, err, 1, 0));
stream2.once('error', (err) => this._onError(stream2, err, 1, 0));
}
_read() {
@ -41,7 +51,7 @@ class MergeStream extends stream.Readable {
callback();
}
_onItem(myStream, myItem, myIndex, otherIndex) {
_onItem(myStream: stream.Readable, myItem, myIndex, otherIndex) {
this._peekItems[myIndex] = myItem;
const otherItem = this._peekItems[otherIndex];
if (otherItem === undefined) {
@ -69,7 +79,7 @@ class MergeStream extends stream.Readable {
return undefined;
}
_onEnd(myStream, myIndex, otherIndex) {
_onEnd(myStream: stream.Readable, myIndex, otherIndex) {
this._streamEof[myIndex] = true;
if (this._peekItems[myIndex] === undefined) {
this._peekItems[myIndex] = null;
@ -94,7 +104,7 @@ class MergeStream extends stream.Readable {
return otherStream.resume();
}
_onError(myStream, err, myIndex, otherIndex) {
_onError(myStream: stream.Readable, err, myIndex, otherIndex) {
myStream.destroy();
if (this._streams[otherIndex]) {
this._streams[otherIndex].destroy();
@ -102,5 +112,3 @@ class MergeStream extends stream.Readable {
this.emit('error', err);
}
}
module.exports = MergeStream;

View File

@ -1,6 +1,4 @@
'use strict'; // eslint-disable-line strict
const constants = require('../constants');
import * as constants from '../constants';
/**
* Class containing requester's information received from Vault
@ -9,7 +7,14 @@ const constants = require('../constants');
* @return {AuthInfo} an AuthInfo instance
*/
class AuthInfo {
export default class AuthInfo {
arn
canonicalID
shortid
email
accountDisplayName
IAMdisplayName
constructor(objectFromVault) {
// amazon resource name for IAM user (if applicable)
this.arn = objectFromVault.arn;
@ -50,13 +55,12 @@ class AuthInfo {
return this.canonicalID === constants.publicId;
}
isRequesterAServiceAccount() {
return this.canonicalID.startsWith(
`${constants.zenkoServiceAccount}/`);
return this.canonicalID.startsWith(`${constants.zenkoServiceAccount}/`);
}
isRequesterThisServiceAccount(serviceName) {
return this.canonicalID ===
`${constants.zenkoServiceAccount}/${serviceName}`;
return (
this.canonicalID ===
`${constants.zenkoServiceAccount}/${serviceName}`
);
}
}
module.exports = AuthInfo;

View File

@ -1,5 +1,5 @@
const errors = require('../errors');
const AuthInfo = require('./AuthInfo');
import errors from '../errors';
import AuthInfo from './AuthInfo';
/** vaultSignatureCb parses message from Vault and instantiates
* @param {object} err - error from vault
@ -39,7 +39,10 @@ function vaultSignatureCb(err, authInfo, log, callback, streamingV4Params) {
* authentication backends.
* @class Vault
*/
class Vault {
export default class Vault {
client
implName
/**
* @constructor
* @param {object} client - authentication backend or vault client
@ -91,7 +94,7 @@ class Vault {
requestContext: serializedRCsArr,
},
(err, userInfo) => vaultSignatureCb(err, userInfo,
params.log, callback)
params.log, callback),
);
}
@ -146,7 +149,7 @@ class Vault {
requestContext: serializedRCs,
},
(err, userInfo) => vaultSignatureCb(err, userInfo,
params.log, callback, streamingV4Params)
params.log, callback, streamingV4Params),
);
}
@ -232,28 +235,28 @@ class Vault {
*/
getAccountIds(canonicalIDs, log, callback) {
log.trace('getting accountIds from Vault based on canonicalIDs',
{ canonicalIDs });
{ canonicalIDs });
this.client.getAccountIds(canonicalIDs,
{ reqUid: log.getSerializedUids() },
(err, info) => {
if (err) {
log.debug('received error message from vault',
{ errorMessage: err });
return callback(err);
}
const infoFromVault = info.message.body;
log.trace('info received from vault', { infoFromVault });
const result = {};
/* If the accountId was not found in Vault, do not
send the canonicalID back to the API */
Object.keys(infoFromVault).forEach(key => {
if (infoFromVault[key] !== 'NotFound' &&
infoFromVault[key] !== 'WrongFormat') {
result[key] = infoFromVault[key];
{ reqUid: log.getSerializedUids() },
(err, info) => {
if (err) {
log.debug('received error message from vault',
{ errorMessage: err });
return callback(err);
}
const infoFromVault = info.message.body;
log.trace('info received from vault', { infoFromVault });
const result = {};
/* If the accountId was not found in Vault, do not
send the canonicalID back to the API */
Object.keys(infoFromVault).forEach(key => {
if (infoFromVault[key] !== 'NotFound' &&
infoFromVault[key] !== 'WrongFormat') {
result[key] = infoFromVault[key];
}
});
return callback(null, result);
});
return callback(null, result);
});
}
/** checkPolicies -- call Vault to evaluate policies
@ -312,5 +315,3 @@ class Vault {
});
}
}
module.exports = Vault;

View File

@ -1,22 +1,20 @@
'use strict'; // eslint-disable-line strict
const crypto = require('crypto');
const errors = require('../errors');
const queryString = require('querystring');
const AuthInfo = require('./AuthInfo');
const v2 = require('./v2/authV2');
const v4 = require('./v4/authV4');
const constants = require('../constants');
const constructStringToSignV2 = require('./v2/constructStringToSign');
const constructStringToSignV4 = require('./v4/constructStringToSign');
const convertUTCtoISO8601 = require('./v4/timeUtils').convertUTCtoISO8601;
const vaultUtilities = require('./backends/in_memory/vaultUtilities');
const inMemoryBackend = require('./backends/in_memory/Backend');
const validateAuthConfig = require('./backends/in_memory/validateAuthConfig');
const AuthLoader = require('./backends/in_memory/AuthLoader');
const Vault = require('./Vault');
const baseBackend = require('./backends/base');
const chainBackend = require('./backends/ChainBackend');
import * as crypto from 'crypto';
import errors from '../errors';
import * as queryString from 'querystring';
import AuthInfo from './AuthInfo';
import * as v2 from './v2/authV2';
import * as v4 from './v4/authV4';
import * as constants from '../constants';
import constructStringToSignV2 from './v2/constructStringToSign';
import constructStringToSignV4 from './v4/constructStringToSign';
import { convertUTCtoISO8601 } from './v4/timeUtils';
import * as vaultUtilities from './backends/in_memory/vaultUtilities';
import * as inMemoryBackend from './backends/in_memory/Backend';
import validateAuthConfig from './backends/in_memory/validateAuthConfig';
import AuthLoader from './backends/in_memory/AuthLoader';
import Vault from './Vault';
import baseBackend from './backends/base';
import chainBackend from './backends/ChainBackend';
let vault = null;
const auth = {};
@ -73,8 +71,9 @@ function extractParams(request, log, awsService, data) {
} else if (authHeader.startsWith('AWS4')) {
version = 'v4';
} else {
log.trace('invalid authorization security header',
{ header: authHeader });
log.trace('invalid authorization security header', {
header: authHeader,
});
return { err: errors.AccessDenied };
}
} else if (data.Signature) {
@ -88,8 +87,10 @@ function extractParams(request, log, awsService, data) {
// Here, either both values are set, or none is set
if (version !== null && method !== null) {
if (!checkFunctions[version] || !checkFunctions[version][method]) {
log.trace('invalid auth version or method',
{ version, authMethod: method });
log.trace('invalid auth version or method', {
version,
authMethod: method,
});
return { err: errors.NotImplemented };
}
log.trace('identified auth method', { version, authMethod: method });
@ -121,10 +122,11 @@ function doAuth(request, log, cb, awsService, requestContexts) {
return cb(null, res.params);
}
if (requestContexts) {
requestContexts.forEach(requestContext => {
requestContexts.forEach((requestContext) => {
requestContext.setAuthType(res.params.data.authType);
requestContext.setSignatureVersion(res.params
.data.signatureVersion);
requestContext.setSignatureVersion(
res.params.data.signatureVersion
);
requestContext.setSignatureAge(res.params.data.signatureAge);
requestContext.setSecurityToken(res.params.data.securityToken);
});
@ -136,8 +138,12 @@ function doAuth(request, log, cb, awsService, requestContexts) {
return vault.authenticateV2Request(res.params, requestContexts, cb);
}
if (res.params.version === 4) {
return vault.authenticateV4Request(res.params, requestContexts, cb,
awsService);
return vault.authenticateV4Request(
res.params,
requestContexts,
cb,
awsService
);
}
log.error('authentication method not found', {
@ -160,16 +166,22 @@ function doAuth(request, log, cb, awsService, requestContexts) {
* are temporary credentials from STS
* @return {undefined}
*/
function generateV4Headers(request, data, accessKey, secretKeyValue,
awsService, proxyPath, sessionToken) {
function generateV4Headers(
request,
data,
accessKey,
secretKeyValue,
awsService,
proxyPath,
sessionToken
) {
Object.assign(request, { headers: {} });
const amzDate = convertUTCtoISO8601(Date.now());
// get date without time
const scopeDate = amzDate.slice(0, amzDate.indexOf('T'));
const region = 'us-east-1';
const service = awsService || 'iam';
const credentialScope =
`${scopeDate}/${region}/${service}/aws4_request`;
const credentialScope = `${scopeDate}/${region}/${service}/aws4_request`;
const timestamp = amzDate;
const algorithm = 'AWS4-HMAC-SHA256';
@ -179,8 +191,10 @@ function generateV4Headers(request, data, accessKey, secretKeyValue,
encodeURIComponent,
});
}
const payloadChecksum = crypto.createHash('sha256')
.update(payload, 'binary').digest('hex');
const payloadChecksum = crypto
.createHash('sha256')
.update(payload, 'binary')
.digest('hex');
request.setHeader('host', request._headers.host);
request.setHeader('x-amz-date', amzDate);
request.setHeader('x-amz-content-sha256', payloadChecksum);
@ -191,47 +205,49 @@ function generateV4Headers(request, data, accessKey, secretKeyValue,
Object.assign(request.headers, request._headers);
const signedHeaders = Object.keys(request._headers)
.filter(headerName =>
headerName.startsWith('x-amz-')
|| headerName.startsWith('x-scal-')
|| headerName === 'host'
).sort().join(';');
const params = { request, signedHeaders, payloadChecksum,
credentialScope, timestamp, query: data,
awsService: service, proxyPath };
.filter(
(headerName) =>
headerName.startsWith('x-amz-') ||
headerName.startsWith('x-scal-') ||
headerName === 'host'
)
.sort()
.join(';');
const params = {
request,
signedHeaders,
payloadChecksum,
credentialScope,
timestamp,
query: data,
awsService: service,
proxyPath,
};
const stringToSign = constructStringToSignV4(params);
const signingKey = vaultUtilities.calculateSigningKey(secretKeyValue,
region,
scopeDate,
service);
const signature = crypto.createHmac('sha256', signingKey)
.update(stringToSign, 'binary').digest('hex');
const authorizationHeader = `${algorithm} Credential=${accessKey}` +
const signingKey = vaultUtilities.calculateSigningKey(
secretKeyValue,
region,
scopeDate,
service
);
const signature = crypto
.createHmac('sha256', signingKey)
.update(stringToSign, 'binary')
.digest('hex');
const authorizationHeader =
`${algorithm} Credential=${accessKey}` +
`/${credentialScope}, SignedHeaders=${signedHeaders}, ` +
`Signature=${signature}`;
request.setHeader('authorization', authorizationHeader);
Object.assign(request, { headers: {} });
}
module.exports = {
setHandler: setAuthHandler,
server: {
extractParams,
doAuth,
},
client: {
generateV4Headers,
constructStringToSignV2,
},
inMemory: {
backend: inMemoryBackend,
validateAuthConfig,
AuthLoader,
},
backends: {
baseBackend,
chainBackend,
},
AuthInfo,
Vault,
export const server = { extractParams, doAuth };
export const client = { generateV4Headers, constructStringToSignV2 };
export const inMemory = {
backend: inMemoryBackend,
validateAuthConfig,
AuthLoader,
};
export const backends = { baseBackend, chainBackend };
export { setAuthHandler as setHandler, AuthInfo, Vault };

View File

@ -1,10 +1,8 @@
'use strict'; // eslint-disable-line strict
import assert from 'assert';
import async from 'async';
const assert = require('assert');
const async = require('async');
const errors = require('../../errors');
const BaseBackend = require('./base');
import errors from '../../errors';
import BaseBackend from './base';
/**
* Class that provides an authentication backend that will verify signatures
@ -13,13 +11,15 @@ const BaseBackend = require('./base');
*
* @class ChainBackend
*/
class ChainBackend extends BaseBackend {
export default class ChainBackend extends BaseBackend {
_clients: any[];
/**
* @constructor
* @param {string} service - service id
* @param {object[]} clients - list of authentication backends or vault clients
*/
constructor(service, clients) {
constructor(service: string, clients: any[]) {
super(service);
assert(Array.isArray(clients) && clients.length > 0, 'invalid client list');
@ -34,7 +34,6 @@ class ChainBackend extends BaseBackend {
this._clients = clients;
}
/*
* try task against each client for one to be successful
*/
@ -62,19 +61,20 @@ class ChainBackend extends BaseBackend {
verifySignatureV4(stringToSign, signatureFromRequest, accessKey, region, scopeDate, options, callback) {
this._tryEachClient((client, done) => client.verifySignatureV4(
stringToSign,
signatureFromRequest,
accessKey,
region,
scopeDate,
options,
done
signatureFromRequest,
accessKey,
region,
scopeDate,
options,
done
), callback);
}
static _mergeObjects(objectResponses) {
return objectResponses.reduce(
(retObj, resObj) => Object.assign(retObj, resObj.message.body),
{});
{}
);
}
getCanonicalIds(emailAddresses, options, callback) {
@ -90,7 +90,8 @@ class ChainBackend extends BaseBackend {
body: ChainBackend._mergeObjects(res),
},
});
});
}
);
}
getEmailAddresses(canonicalIDs, options, callback) {
@ -105,7 +106,8 @@ class ChainBackend extends BaseBackend {
body: ChainBackend._mergeObjects(res),
},
});
});
}
);
}
/*
@ -128,7 +130,7 @@ class ChainBackend extends BaseBackend {
});
});
return Object.keys(policyMap).map(key => {
return Object.keys(policyMap).map((key) => {
const policyRes = { isAllowed: policyMap[key].isAllowed };
if (policyMap[key].arn !== '') {
policyRes.arn = policyMap[key].arn;
@ -172,12 +174,12 @@ class ChainBackend extends BaseBackend {
error: !!err ? err : null,
status: res,
})
), (err, res) => {
), (err, res) => {
if (err) {
return callback(err);
}
const isError = res.some(results => !!results.error);
const isError = res.some((results) => !!results.error);
if (isError) {
return callback(errors.InternalError, res);
}
@ -185,5 +187,3 @@ class ChainBackend extends BaseBackend {
});
}
}
module.exports = ChainBackend;

View File

@ -1,13 +1,13 @@
'use strict'; // eslint-disable-line strict
const errors = require('../../errors');
import errors from '../../errors';
/**
* Base backend class
*
* @class BaseBackend
*/
class BaseBackend {
export default class BaseBackend {
service
/**
* @constructor
* @param {string} service - service identifer for construction arn
@ -82,5 +82,3 @@ class BaseBackend {
return callback(null, { code: 200, message: 'OK' });
}
}
module.exports = BaseBackend;

View File

@ -1,9 +1,9 @@
const fs = require('fs');
const glob = require('simple-glob');
const joi = require('@hapi/joi');
const werelogs = require('werelogs');
import * as fs from 'fs';
import glob from 'simple-glob';
import joi from '@hapi/joi';
import werelogs from 'werelogs';
const ARN = require('../../../models/ARN');
import ARN from '../../../models/ARN';
/**
* Load authentication information from files or pre-loaded account
@ -11,35 +11,46 @@ const ARN = require('../../../models/ARN');
*
* @class AuthLoader
*/
class AuthLoader {
export default class AuthLoader {
_log
_authData
_isValid
_joiKeysValidator
_joiValidator
constructor(logApi) {
this._log = new (logApi || werelogs).Logger('S3');
this._authData = { accounts: [] };
// null: unknown validity, true/false: valid or invalid
this._isValid = null;
this._joiKeysValidator = joi.array()
this._joiKeysValidator = joi
.array()
.items({
access: joi.string().required(),
secret: joi.string().required(),
})
.required();
const accountsJoi = joi.array()
.items({
name: joi.string().required(),
email: joi.string().email().required(),
arn: joi.string().required(),
canonicalID: joi.string().required(),
shortid: joi.string().regex(/^[0-9]{12}$/).required(),
keys: this._joiKeysValidator,
// backward-compat
users: joi.array(),
})
.required()
.unique('arn')
.unique('email')
.unique('canonicalID');
const accountsJoi = joi
.array()
.items({
name: joi.string().required(),
email: joi.string().email().required(),
arn: joi.string().required(),
canonicalID: joi.string().required(),
shortid: joi
.string()
.regex(/^[0-9]{12}$/)
.required(),
keys: this._joiKeysValidator,
// backward-compat
users: joi.array(),
})
.required()
.unique('arn')
.unique('email')
.unique('canonicalID');
this._joiValidator = joi.object({ accounts: accountsJoi });
}
@ -64,11 +75,12 @@ class AuthLoader {
* logging purpose
* @return {undefined}
*/
addAccounts(authData, filePath) {
addAccounts(authData, filePath: string) {
const isValid = this._validateData(authData, filePath);
if (isValid) {
this._authData.accounts =
this._authData.accounts.concat(authData.accounts);
this._authData.accounts = this._authData.accounts.concat(
authData.accounts
);
// defer validity checking when getting data to avoid
// logging multiple times the errors (we need to validate
// all accounts at once to detect duplicate values)
@ -87,7 +99,7 @@ class AuthLoader {
* authentication info (see {@link addAccounts()} for format)
* @return {undefined}
*/
addFile(filePath) {
addFile(filePath: string) {
const authData = JSON.parse(fs.readFileSync(filePath));
this.addAccounts(authData, filePath);
}
@ -103,9 +115,9 @@ class AuthLoader {
* {@link addAccounts()} for JSON format.
* @return {undefined}
*/
addFilesByGlob(globPattern) {
addFilesByGlob(globPattern: string | string[]) {
const files = glob(globPattern);
files.forEach(filePath => this.addFile(filePath));
files.forEach((filePath) => this.addFile(filePath));
}
/**
@ -134,9 +146,10 @@ class AuthLoader {
return this.validate() ? this._authData : null;
}
_validateData(authData, filePath) {
const res = joi.validate(authData, this._joiValidator,
{ abortEarly: false });
_validateData(authData, filePath?: string) {
const res = joi.validate(authData, this._joiValidator, {
abortEarly: false,
});
if (res.error) {
this._dumpJoiErrors(res.error.details, filePath);
return false;
@ -144,19 +157,23 @@ class AuthLoader {
let allKeys = [];
let arnError = false;
const validatedAuth = res.value;
validatedAuth.accounts.forEach(account => {
validatedAuth.accounts.forEach((account) => {
// backward-compat: ignore arn if starts with 'aws:' and log a
// warning
if (account.arn.startsWith('aws:')) {
this._log.error(
'account must have a valid AWS ARN, legacy examples ' +
'starting with \'aws:\' are not supported anymore. ' +
"starting with 'aws:' are not supported anymore. " +
'Please convert to a proper account entry (see ' +
'examples at https://github.com/scality/S3/blob/' +
'master/conf/authdata.json). Also note that support ' +
'for account users has been dropped.',
{ accountName: account.name, accountArn: account.arn,
filePath });
{
accountName: account.name,
accountArn: account.arn,
filePath,
}
);
arnError = true;
return;
}
@ -166,27 +183,33 @@ class AuthLoader {
'turning users into account entries (see examples at ' +
'https://github.com/scality/S3/blob/master/conf/' +
'authdata.json)',
{ accountName: account.name, accountArn: account.arn,
filePath });
{
accountName: account.name,
accountArn: account.arn,
filePath,
}
);
arnError = true;
return;
}
const arnObj = ARN.createFromString(account.arn);
if (arnObj.error) {
this._log.error(
'authentication config validation error',
{ reason: arnObj.error.description,
accountName: account.name, accountArn: account.arn,
filePath });
this._log.error('authentication config validation error', {
reason: arnObj.error.description,
accountName: account.name,
accountArn: account.arn,
filePath,
});
arnError = true;
return;
}
if (!arnObj.isIAMAccount()) {
this._log.error(
'authentication config validation error',
{ reason: 'not an IAM account ARN',
accountName: account.name, accountArn: account.arn,
filePath });
this._log.error('authentication config validation error', {
reason: 'not an IAM account ARN',
accountName: account.name,
accountArn: account.arn,
filePath,
});
arnError = true;
return;
}
@ -196,7 +219,9 @@ class AuthLoader {
return false;
}
const uniqueKeysRes = joi.validate(
allKeys, this._joiKeysValidator.unique('access'));
allKeys,
this._joiKeysValidator.unique('access')
);
if (uniqueKeysRes.error) {
this._dumpJoiErrors(uniqueKeysRes.error.details, filePath);
return false;
@ -205,7 +230,7 @@ class AuthLoader {
}
_dumpJoiErrors(errors, filePath) {
errors.forEach(err => {
errors.forEach((err) => {
const logInfo = { item: err.path, filePath };
if (err.type === 'array.unique') {
logInfo.reason = `duplicate value '${err.context.path}'`;
@ -214,10 +239,7 @@ class AuthLoader {
logInfo.reason = err.message;
logInfo.context = err.context;
}
this._log.error('authentication config validation error',
logInfo);
this._log.error('authentication config validation error', logInfo);
});
}
}
module.exports = AuthLoader;

View File

@ -1,12 +1,8 @@
'use strict'; // eslint-disable-line strict
const crypto = require('crypto');
const errors = require('../../../errors');
const calculateSigningKey = require('./vaultUtilities').calculateSigningKey;
const hashSignature = require('./vaultUtilities').hashSignature;
const Indexer = require('./Indexer');
const BaseBackend = require('../base');
import * as crypto from 'crypto';
import errors from '../../../errors';
import { calculateSigningKey, hashSignature } from './vaultUtilities';
import Indexer from './Indexer';
import BaseBackend from '../base';
function _formatResponse(userInfoToSend) {
return {
@ -23,6 +19,9 @@ function _formatResponse(userInfoToSend) {
* @class InMemoryBackend
*/
class InMemoryBackend extends BaseBackend {
indexer
formatResponse
/**
* @constructor
* @param {string} service - service identifer for construction arn
@ -36,15 +35,23 @@ class InMemoryBackend extends BaseBackend {
this.formatResponse = formatter;
}
verifySignatureV2(stringToSign, signatureFromRequest,
accessKey, options, callback) {
verifySignatureV2(
stringToSign,
signatureFromRequest,
accessKey,
options,
callback
) {
const entity = this.indexer.getEntityByKey(accessKey);
if (!entity) {
return callback(errors.InvalidAccessKeyId);
}
const secretKey = this.indexer.getSecretKey(entity, accessKey);
const reconstructedSig =
hashSignature(stringToSign, secretKey, options.algo);
const reconstructedSig = hashSignature(
stringToSign,
secretKey,
options.algo
);
if (signatureFromRequest !== reconstructedSig) {
return callback(errors.SignatureDoesNotMatch);
}
@ -58,16 +65,25 @@ class InMemoryBackend extends BaseBackend {
return callback(null, vaultReturnObject);
}
verifySignatureV4(stringToSign, signatureFromRequest, accessKey,
region, scopeDate, options, callback) {
verifySignatureV4(
stringToSign,
signatureFromRequest,
accessKey,
region,
scopeDate,
options,
callback
) {
const entity = this.indexer.getEntityByKey(accessKey);
if (!entity) {
return callback(errors.InvalidAccessKeyId);
}
const secretKey = this.indexer.getSecretKey(entity, accessKey);
const signingKey = calculateSigningKey(secretKey, region, scopeDate);
const reconstructedSig = crypto.createHmac('sha256', signingKey)
.update(stringToSign, 'binary').digest('hex');
const reconstructedSig = crypto
.createHmac('sha256', signingKey)
.update(stringToSign, 'binary')
.digest('hex');
if (signatureFromRequest !== reconstructedSig) {
return callback(errors.SignatureDoesNotMatch);
}
@ -83,14 +99,13 @@ class InMemoryBackend extends BaseBackend {
getCanonicalIds(emails, log, cb) {
const results = {};
emails.forEach(email => {
emails.forEach((email) => {
const lowercasedEmail = email.toLowerCase();
const entity = this.indexer.getEntityByEmail(lowercasedEmail);
if (!entity) {
results[email] = 'NotFound';
} else {
results[email] =
entity.canonicalID;
results[email] = entity.canonicalID;
}
});
const vaultReturnObject = {
@ -103,7 +118,7 @@ class InMemoryBackend extends BaseBackend {
getEmailAddresses(canonicalIDs, options, cb) {
const results = {};
canonicalIDs.forEach(canonicalId => {
canonicalIDs.forEach((canonicalId) => {
const foundEntity = this.indexer.getEntityByCanId(canonicalId);
if (!foundEntity || !foundEntity.email) {
results[canonicalId] = 'NotFound';
@ -131,7 +146,7 @@ class InMemoryBackend extends BaseBackend {
*/
getAccountIds(canonicalIDs, options, cb) {
const results = {};
canonicalIDs.forEach(canonicalID => {
canonicalIDs.forEach((canonicalID) => {
const foundEntity = this.indexer.getEntityByCanId(canonicalID);
if (!foundEntity || !foundEntity.shortid) {
results[canonicalID] = 'Not Found';
@ -148,7 +163,6 @@ class InMemoryBackend extends BaseBackend {
}
}
class S3AuthBackend extends InMemoryBackend {
/**
* @constructor

View File

@ -6,7 +6,7 @@
*
* @class Indexer
*/
class Indexer {
export default class Indexer {
/**
* @constructor
* @param {object} authdata - the authentication config file's data
@ -141,5 +141,3 @@ class Indexer {
return entity.accountDisplayName;
}
}
module.exports = Indexer;

View File

@ -1,4 +1,4 @@
const AuthLoader = require('./AuthLoader');
import AuthLoader from './AuthLoader';
/**
* @deprecated please use {@link AuthLoader} class instead
@ -9,10 +9,8 @@ const AuthLoader = require('./AuthLoader');
* @return {boolean} true on erroneous data
* false on success
*/
function validateAuthConfig(authdata, logApi) {
export default function validateAuthConfig(authdata, logApi) {
const authLoader = new AuthLoader(logApi);
authLoader.addAccounts(authdata);
return !authLoader.validate();
}
module.exports = validateAuthConfig;

View File

@ -1,6 +1,4 @@
'use strict'; // eslint-disable-line strict
const crypto = require('crypto');
import * as crypto from 'crypto';
/** hashSignature for v2 Auth
* @param {string} stringToSign - built string to sign per AWS rules
@ -8,7 +6,11 @@ const crypto = require('crypto');
* @param {string} algorithm - either SHA256 or SHA1
* @return {string} reconstructed signature
*/
function hashSignature(stringToSign, secretKey, algorithm) {
export function hashSignature(
stringToSign: string,
secretKey: string,
algorithm: 'SHA256' | 'SHA1'
): string {
const hmacObject = crypto.createHmac(algorithm, secretKey);
return hmacObject.update(stringToSign, 'binary').digest('base64');
}
@ -20,7 +22,12 @@ function hashSignature(stringToSign, secretKey, algorithm) {
* @param {string} [service] - To specify another service than s3
* @return {string} signingKey - signingKey to calculate signature
*/
function calculateSigningKey(secretKey, region, scopeDate, service) {
export function calculateSigningKey(
secretKey: string,
region: string,
scopeDate: string,
service: string
): string {
const dateKey = crypto.createHmac('sha256', `AWS4${secretKey}`)
.update(scopeDate, 'binary').digest();
const dateRegionKey = crypto.createHmac('sha256', dateKey)
@ -31,5 +38,3 @@ function calculateSigningKey(secretKey, region, scopeDate, service) {
.update('aws4_request', 'binary').digest();
return signingKey;
}
module.exports = { hashSignature, calculateSigningKey };

View File

@ -1,6 +1,4 @@
'use strict'; // eslint-disable-line strict
function algoCheck(signatureLength) {
export default function algoCheck(signatureLength) {
let algo;
// If the signature sent is 44 characters,
// this means that sha256 was used:
@ -15,5 +13,3 @@ function algoCheck(signatureLength) {
}
return algo;
}
module.exports = algoCheck;

View File

@ -1,5 +1,3 @@
'use strict'; // eslint-disable-line strict
const headerAuthCheck = require('./headerAuthCheck');
const queryAuthCheck = require('./queryAuthCheck');

View File

@ -1,9 +1,9 @@
'use strict'; // eslint-disable-line strict
const errors = require('../../errors');
import errors from '../../errors';
const epochTime = new Date('1970-01-01').getTime();
function checkRequestExpiry(timestamp, log) {
export default function checkRequestExpiry(timestamp, log) {
// If timestamp is before epochTime, the request is invalid and return
// errors.AccessDenied
if (timestamp < epochTime) {
@ -17,7 +17,7 @@ function checkRequestExpiry(timestamp, log) {
log.trace('request timestamp', { requestTimestamp: timestamp });
log.trace('current timestamp', { currentTimestamp: currentTime });
const fifteenMinutes = (15 * 60 * 1000);
const fifteenMinutes = 15 * 60 * 1000;
if (currentTime - timestamp > fifteenMinutes) {
log.trace('request timestamp is not within 15 minutes of current time');
log.debug('request time too skewed', { timestamp });
@ -32,5 +32,3 @@ function checkRequestExpiry(timestamp, log) {
return undefined;
}
module.exports = checkRequestExpiry;

View File

@ -1,11 +1,8 @@
'use strict'; // eslint-disable-line strict
import utf8 from 'utf8';
import getCanonicalizedAmzHeaders from './getCanonicalizedAmzHeaders';
import getCanonicalizedResource from './getCanonicalizedResource';
const utf8 = require('utf8');
const getCanonicalizedAmzHeaders = require('./getCanonicalizedAmzHeaders');
const getCanonicalizedResource = require('./getCanonicalizedResource');
function constructStringToSign(request, data, log, clientType) {
export default function constructStringToSign(request, data, log, clientType?: any) {
/*
Build signature per AWS requirements:
StringToSign = HTTP-Verb + '\n' +
@ -23,11 +20,11 @@ function constructStringToSign(request, data, log, clientType) {
const contentMD5 = headers['content-md5'] ?
headers['content-md5'] : query['Content-MD5'];
stringToSign += (contentMD5 ? `${contentMD5}\n` : '\n');
stringToSign += contentMD5 ? `${contentMD5}\n` : '\n';
const contentType = headers['content-type'] ?
headers['content-type'] : query['Content-Type'];
stringToSign += (contentType ? `${contentType}\n` : '\n');
stringToSign += contentType ? `${contentType}\n` : '\n';
/*
AWS docs are conflicting on whether to include x-amz-date header here
@ -42,5 +39,3 @@ function constructStringToSign(request, data, log, clientType) {
+ getCanonicalizedResource(request, clientType);
return utf8.encode(stringToSign);
}
module.exports = constructStringToSign;

View File

@ -1,6 +1,4 @@
'use strict'; // eslint-disable-line strict
function getCanonicalizedAmzHeaders(headers, clientType) {
export default function getCanonicalizedAmzHeaders(headers, clientType) {
/*
Iterate through headers and pull any headers that are x-amz headers.
Need to include 'x-amz-date' here even though AWS docs
@ -41,7 +39,5 @@ function getCanonicalizedAmzHeaders(headers, clientType) {
// Build headerString
return amzHeaders.reduce((headerStr, current) =>
`${headerStr}${current[0]}:${current[1]}\n`,
'');
'');
}
module.exports = getCanonicalizedAmzHeaders;

View File

@ -1,6 +1,4 @@
'use strict'; // eslint-disable-line strict
const url = require('url');
import * as url from 'url';
const gcpSubresources = [
'acl',
@ -41,7 +39,7 @@ const awsSubresources = [
'website',
];
function getCanonicalizedResource(request, clientType) {
export default function getCanonicalizedResource(request, clientType) {
/*
This variable is used to determine whether to insert
a '?' or '&'. Once a query parameter is added to the resourceString,
@ -117,5 +115,3 @@ function getCanonicalizedResource(request, clientType) {
}
return resourceString;
}
module.exports = getCanonicalizedResource;

View File

@ -1,12 +1,10 @@
'use strict'; // eslint-disable-line strict
import errors from '../../errors';
import * as constants from '../../constants';
import constructStringToSign from './constructStringToSign';
import checkRequestExpiry from './checkRequestExpiry';
import algoCheck from './algoCheck';
const errors = require('../../errors');
const constants = require('../../constants');
const constructStringToSign = require('./constructStringToSign');
const checkRequestExpiry = require('./checkRequestExpiry');
const algoCheck = require('./algoCheck');
function check(request, log, data) {
export function check(request, log, data) {
log.trace('running header auth check');
const headers = request.headers;
@ -17,15 +15,19 @@ function check(request, log, data) {
}
// Check to make sure timestamp is within 15 minutes of current time
let timestamp = headers['x-amz-date'] ?
headers['x-amz-date'] : headers.date;
let timestamp = headers['x-amz-date']
? headers['x-amz-date']
: headers.date;
timestamp = Date.parse(timestamp);
if (!timestamp) {
log.debug('missing or invalid date header',
{ method: 'auth/v2/headerAuthCheck.check' });
return { err: errors.AccessDenied.
customizeDescription('Authentication requires a valid Date or ' +
'x-amz-date header') };
log.debug('missing or invalid date header', {
method: 'auth/v2/headerAuthCheck.check',
});
return {
err: errors.AccessDenied.customizeDescription(
'Authentication requires a valid Date or ' + 'x-amz-date header'
),
};
}
const err = checkRequestExpiry(timestamp, log);
@ -46,8 +48,10 @@ function check(request, log, data) {
log.debug('invalid authorization header', { authInfo });
return { err: errors.InvalidArgument };
}
const accessKey = semicolonIndex > 4 ?
authInfo.substring(4, semicolonIndex).trim() : undefined;
const accessKey =
semicolonIndex > 4
? authInfo.substring(4, semicolonIndex).trim()
: undefined;
if (typeof accessKey !== 'string' || accessKey.length === 0) {
log.trace('invalid authorization header', { authInfo });
return { err: errors.MissingSecurityHeader };
@ -80,5 +84,3 @@ function check(request, log, data) {
},
};
}
module.exports = { check };

View File

@ -1,11 +1,9 @@
'use strict'; // eslint-disable-line strict
import errors from '../../errors';
import * as constants from '../../constants';
import algoCheck from './algoCheck';
import constructStringToSign from './constructStringToSign';
const errors = require('../../errors');
const constants = require('../../constants');
const algoCheck = require('./algoCheck');
const constructStringToSign = require('./constructStringToSign');
function check(request, log, data) {
export function check(request, log, data) {
log.trace('running query auth check');
if (request.method === 'POST') {
log.debug('query string auth not supported for post requests');
@ -28,26 +26,28 @@ function check(request, log, data) {
*/
const expirationTime = parseInt(data.Expires, 10) * 1000;
if (Number.isNaN(expirationTime)) {
log.debug('invalid expires parameter',
{ expires: data.Expires });
log.debug('invalid expires parameter', { expires: data.Expires });
return { err: errors.MissingSecurityHeader };
}
const currentTime = Date.now();
const preSignedURLExpiry = process.env.PRE_SIGN_URL_EXPIRY
&& !Number.isNaN(process.env.PRE_SIGN_URL_EXPIRY)
? Number.parseInt(process.env.PRE_SIGN_URL_EXPIRY, 10)
: constants.defaultPreSignedURLExpiry * 1000;
const preSignedURLExpiry =
process.env.PRE_SIGN_URL_EXPIRY &&
!Number.isNaN(process.env.PRE_SIGN_URL_EXPIRY)
? Number.parseInt(process.env.PRE_SIGN_URL_EXPIRY, 10)
: constants.defaultPreSignedURLExpiry * 1000;
if (expirationTime > currentTime + preSignedURLExpiry) {
log.debug('expires parameter too far in future',
{ expires: request.query.Expires });
log.debug('expires parameter too far in future', {
expires: request.query.Expires,
});
return { err: errors.AccessDenied };
}
if (currentTime > expirationTime) {
log.debug('current time exceeds expires time',
{ expires: request.query.Expires });
log.debug('current time exceeds expires time', {
expires: request.query.Expires,
});
return { err: errors.RequestTimeTooSkewed };
}
const accessKey = data.AWSAccessKeyId;
@ -82,5 +82,3 @@ function check(request, log, data) {
},
};
}
module.exports = { check };

View File

@ -1,5 +1,3 @@
'use strict'; // eslint-disable-line strict
/*
AWS's URI encoding rules:
URI encode every byte. Uri-Encode() must enforce the following rules:
@ -32,23 +30,27 @@ function _toHexUTF8(char) {
return res;
}
function awsURIencode(input, encodeSlash, noEncodeStar) {
export default function awsURIencode(input, encodeSlash?: any, noEncodeStar?: any) {
const encSlash = encodeSlash === undefined ? true : encodeSlash;
let encoded = '';
/**
* Duplicate query params are not suppported by AWS S3 APIs. These params
* are parsed as Arrays by Node.js HTTP parser which breaks this method
*/
*/
if (typeof input !== 'string') {
return encoded;
}
for (let i = 0; i < input.length; i++) {
let ch = input.charAt(i);
if ((ch >= 'A' && ch <= 'Z') ||
if (
(ch >= 'A' && ch <= 'Z') ||
(ch >= 'a' && ch <= 'z') ||
(ch >= '0' && ch <= '9') ||
ch === '_' || ch === '-' ||
ch === '~' || ch === '.') {
ch === '_' ||
ch === '-' ||
ch === '~' ||
ch === '.'
) {
encoded = encoded.concat(ch);
} else if (ch === ' ') {
encoded = encoded.concat('%20');
@ -76,5 +78,3 @@ function awsURIencode(input, encodeSlash, noEncodeStar) {
}
return encoded;
}
module.exports = awsURIencode;

View File

@ -1,17 +1,22 @@
'use strict'; // eslint-disable-line strict
const crypto = require('crypto');
const createCanonicalRequest = require('./createCanonicalRequest');
import * as crypto from 'crypto';
import createCanonicalRequest from './createCanonicalRequest';
/**
* constructStringToSign - creates V4 stringToSign
* @param {object} params - params object
* @returns {string} - stringToSign
*/
function constructStringToSign(params) {
const { request, signedHeaders, payloadChecksum, credentialScope, timestamp,
query, log, proxyPath } = params;
export default function constructStringToSign(params): string {
const {
request,
signedHeaders,
payloadChecksum,
credentialScope,
timestamp,
query,
log,
proxyPath,
} = params;
const path = proxyPath || request.path;
const canonicalReqResult = createCanonicalRequest({
@ -34,11 +39,11 @@ function constructStringToSign(params) {
log.debug('constructed canonicalRequest', { canonicalReqResult });
}
const sha256 = crypto.createHash('sha256');
const canonicalHex = sha256.update(canonicalReqResult, 'binary')
const canonicalHex = sha256
.update(canonicalReqResult, 'binary')
.digest('hex');
const stringToSign = `AWS4-HMAC-SHA256\n${timestamp}\n` +
`${credentialScope}\n${canonicalHex}`;
const stringToSign =
`AWS4-HMAC-SHA256\n${timestamp}\n` +
`${credentialScope}\n${canonicalHex}`;
return stringToSign;
}
module.exports = constructStringToSign;

View File

@ -1,8 +1,6 @@
'use strict'; // eslint-disable-line strict
const awsURIencode = require('./awsURIencode');
const crypto = require('crypto');
const queryString = require('querystring');
import awsURIencode from './awsURIencode';
import * as crypto from 'crypto';
import * as queryString from 'querystring';
/**
* createCanonicalRequest - creates V4 canonical request
@ -12,7 +10,7 @@ const queryString = require('querystring');
* payloadChecksum (from request)
* @returns {string} - canonicalRequest
*/
function createCanonicalRequest(params) {
export default function createCanonicalRequest(params) {
const pHttpVerb = params.pHttpVerb;
const pResource = params.pResource;
const pQuery = params.pQuery;
@ -87,5 +85,3 @@ function createCanonicalRequest(params) {
`${signedHeaders}\n${payloadChecksum}`;
return canonicalRequest;
}
module.exports = createCanonicalRequest;

View File

@ -1,16 +1,16 @@
'use strict'; // eslint-disable-line strict
const errors = require('../../../lib/errors');
const constants = require('../../constants');
const constructStringToSign = require('./constructStringToSign');
const checkTimeSkew = require('./timeUtils').checkTimeSkew;
const convertUTCtoISO8601 = require('./timeUtils').convertUTCtoISO8601;
const convertAmzTimeToMs = require('./timeUtils').convertAmzTimeToMs;
const extractAuthItems = require('./validateInputs').extractAuthItems;
const validateCredentials = require('./validateInputs').validateCredentials;
const areSignedHeadersComplete =
require('./validateInputs').areSignedHeadersComplete;
import errors from '../../../lib/errors';
import * as constants from '../../constants';
import constructStringToSign from './constructStringToSign';
import {
checkTimeSkew,
convertUTCtoISO8601,
convertAmzTimeToMs,
} from './timeUtils';
import {
extractAuthItems,
validateCredentials,
areSignedHeadersComplete,
} from './validateInputs';
/**
* V4 header auth check
@ -21,7 +21,7 @@ const areSignedHeadersComplete =
* @param {string} awsService - Aws service ('iam' or 's3')
* @return {callback} calls callback
*/
function check(request, log, data, awsService) {
export function check(request, log, data, awsService) {
log.trace('running header auth check');
const token = request.headers['x-amz-security-token'];
@ -51,8 +51,9 @@ function check(request, log, data, awsService) {
if (payloadChecksum === 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD') {
log.trace('requesting streaming v4 auth');
if (request.method !== 'PUT') {
log.debug('streaming v4 auth for put only',
{ method: 'auth/v4/headerAuthCheck.check' });
log.debug('streaming v4 auth for put only', {
method: 'auth/v4/headerAuthCheck.check',
});
return { err: errors.InvalidArgument };
}
if (!request.headers['x-amz-decoded-content-length']) {
@ -77,9 +78,12 @@ function check(request, log, data, awsService) {
if (xAmzDate) {
const xAmzDateArr = xAmzDate.split('T');
// check that x-amz- date has the correct format and after epochTime
if (xAmzDateArr.length === 2 && xAmzDateArr[0].length === 8
&& xAmzDateArr[1].length === 7
&& Number.parseInt(xAmzDateArr[0], 10) > 19700101) {
if (
xAmzDateArr.length === 2 &&
xAmzDateArr[0].length === 8 &&
xAmzDateArr[1].length === 7 &&
Number.parseInt(xAmzDateArr[0], 10) > 19700101
) {
// format of x-amz- date is ISO 8601: YYYYMMDDTHHMMSSZ
timestamp = request.headers['x-amz-date'];
}
@ -87,18 +91,27 @@ function check(request, log, data, awsService) {
timestamp = convertUTCtoISO8601(request.headers.date);
}
if (!timestamp) {
log.debug('missing or invalid date header',
{ method: 'auth/v4/headerAuthCheck.check' });
return { err: errors.AccessDenied.
customizeDescription('Authentication requires a valid Date or ' +
'x-amz-date header') };
log.debug('missing or invalid date header', {
method: 'auth/v4/headerAuthCheck.check',
});
return {
err: errors.AccessDenied.customizeDescription(
'Authentication requires a valid Date or ' + 'x-amz-date header'
),
};
}
const validationResult = validateCredentials(credentialsArr, timestamp,
log);
const validationResult = validateCredentials(
credentialsArr,
timestamp,
log
);
if (validationResult instanceof Error) {
log.debug('credentials in improper format', { credentialsArr,
timestamp, validationResult });
log.debug('credentials in improper format', {
credentialsArr,
timestamp,
validationResult,
});
return { err: validationResult };
}
// credentialsArr is [accessKey, date, region, aws-service, aws4_request]
@ -121,7 +134,7 @@ function check(request, log, data, awsService) {
// expiry is as set out in the policy.
// 15 minutes in seconds
const expiry = (15 * 60);
const expiry = 15 * 60;
const isTimeSkewed = checkTimeSkew(timestamp, expiry, log);
if (isTimeSkewed) {
return { err: errors.RequestTimeTooSkewed };
@ -133,8 +146,11 @@ function check(request, log, data, awsService) {
proxyPath = decodeURIComponent(request.headers.proxy_path);
} catch (err) {
log.debug('invalid proxy_path header', { proxyPath, err });
return { err: errors.InvalidArgument.customizeDescription(
'invalid proxy_path header') };
return {
err: errors.InvalidArgument.customizeDescription(
'invalid proxy_path header'
),
};
}
}
@ -154,7 +170,6 @@ function check(request, log, data, awsService) {
return { err: stringToSign };
}
return {
err: null,
params: {
@ -178,5 +193,3 @@ function check(request, log, data, awsService) {
},
};
}
module.exports = { check };

View File

@ -1,15 +1,10 @@
'use strict'; // eslint-disable-line strict
import * as constants from '../../constants';
import errors from '../../errors';
const constants = require('../../constants');
const errors = require('../../errors');
const constructStringToSign = require('./constructStringToSign');
const checkTimeSkew = require('./timeUtils').checkTimeSkew;
const convertAmzTimeToMs = require('./timeUtils').convertAmzTimeToMs;
const validateCredentials = require('./validateInputs').validateCredentials;
const extractQueryParams = require('./validateInputs').extractQueryParams;
const areSignedHeadersComplete =
require('./validateInputs').areSignedHeadersComplete;
import constructStringToSign from './constructStringToSign';
import { checkTimeSkew, convertAmzTimeToMs } from './timeUtils';
import { validateCredentials, extractQueryParams } from './validateInputs';
import { areSignedHeadersComplete } from './validateInputs';
/**
* V4 query auth check
@ -18,7 +13,7 @@ const areSignedHeadersComplete =
* @param {object} data - Contain authentification params (GET or POST data)
* @return {callback} calls callback
*/
function check(request, log, data) {
export function check(request, log, data) {
const authParams = extractQueryParams(data, log);
if (Object.keys(authParams).length !== 5) {
@ -44,11 +39,13 @@ function check(request, log, data) {
return { err: errors.AccessDenied };
}
const validationResult = validateCredentials(credential, timestamp,
log);
const validationResult = validateCredentials(credential, timestamp, log);
if (validationResult instanceof Error) {
log.debug('credentials in improper format', { credential,
timestamp, validationResult });
log.debug('credentials in improper format', {
credential,
timestamp,
validationResult,
});
return { err: validationResult };
}
const accessKey = credential[0];
@ -68,8 +65,11 @@ function check(request, log, data) {
proxyPath = decodeURIComponent(request.headers.proxy_path);
} catch (err) {
log.debug('invalid proxy_path header', { proxyPath });
return { err: errors.InvalidArgument.customizeDescription(
'invalid proxy_path header') };
return {
err: errors.InvalidArgument.customizeDescription(
'invalid proxy_path header'
),
};
}
}
@ -95,8 +95,7 @@ function check(request, log, data) {
signedHeaders,
payloadChecksum,
timestamp,
credentialScope:
`${scopeDate}/${region}/${service}/${requestType}`,
credentialScope: `${scopeDate}/${region}/${service}/${requestType}`,
awsService: service,
proxyPath,
});
@ -122,5 +121,3 @@ function check(request, log, data) {
},
};
}
module.exports = { check };

View File

@ -1,15 +1,31 @@
const { Transform } = require('stream');
const async = require('async');
const errors = require('../../../errors');
const constructChunkStringToSign = require('./constructChunkStringToSign');
import { Transform } from 'stream';
import async from 'async';
import errors from '../../../errors';
import constructChunkStringToSign from './constructChunkStringToSign';
/**
* This class is designed to handle the chunks sent in a streaming
* v4 Auth request
*/
class V4Transform extends Transform {
export default class V4Transform extends Transform {
log;
cb;
accessKey;
region;
scopeDate;
timestamp;
credentialScope;
lastSignature;
currentSignature;
haveMetadata;
seekingDataSize;
currentData;
dataCursor;
currentMetadata;
lastPieceDone;
lastChunk;
vault;
/**
* @constructor
* @param {object} streamingV4Params - info for chunk authentication
@ -28,8 +44,14 @@ class V4Transform extends Transform {
* @param {function} cb - callback to api
*/
constructor(streamingV4Params, vault, log, cb) {
const { accessKey, signatureFromRequest, region, scopeDate, timestamp,
credentialScope } = streamingV4Params;
const {
accessKey,
signatureFromRequest,
region,
scopeDate,
timestamp,
credentialScope,
} = streamingV4Params;
super({});
this.log = log;
this.cb = cb;
@ -79,28 +101,30 @@ class V4Transform extends Transform {
this.currentMetadata.push(remainingPlusStoredMetadata);
return { completeMetadata: false };
}
let fullMetadata = remainingPlusStoredMetadata.slice(0,
lineBreakIndex);
let fullMetadata = remainingPlusStoredMetadata.slice(0, lineBreakIndex);
// handle extra line break on end of data chunk
if (fullMetadata.length === 0) {
const chunkWithoutLeadingLineBreak = remainingPlusStoredMetadata
.slice(2);
const chunkWithoutLeadingLineBreak =
remainingPlusStoredMetadata.slice(2);
// find second line break
lineBreakIndex = chunkWithoutLeadingLineBreak.indexOf('\r\n');
if (lineBreakIndex < 0) {
this.currentMetadata.push(chunkWithoutLeadingLineBreak);
return { completeMetadata: false };
}
fullMetadata = chunkWithoutLeadingLineBreak.slice(0,
lineBreakIndex);
fullMetadata = chunkWithoutLeadingLineBreak.slice(
0,
lineBreakIndex
);
}
const splitMeta = fullMetadata.toString().split(';');
this.log.trace('parsed full metadata for chunk', { splitMeta });
if (splitMeta.length !== 2) {
this.log.trace('chunk body did not contain correct ' +
'metadata format');
this.log.trace(
'chunk body did not contain correct ' + 'metadata format'
);
return { err: errors.InvalidArgument };
}
let dataSize = splitMeta[0];
@ -132,8 +156,9 @@ class V4Transform extends Transform {
completeMetadata: true,
// start slice at lineBreak plus 2 to remove line break at end of
// metadata piece since length of '\r\n' is 2
unparsedChunk: remainingPlusStoredMetadata
.slice(lineBreakIndex + 2),
unparsedChunk: remainingPlusStoredMetadata.slice(
lineBreakIndex + 2
),
};
}
@ -146,10 +171,13 @@ class V4Transform extends Transform {
*/
_authenticate(dataToSend, done) {
// use prior sig to construct new string to sign
const stringToSign = constructChunkStringToSign(this.timestamp,
this.credentialScope, this.lastSignature, dataToSend);
this.log.trace('constructed chunk string to sign',
{ stringToSign });
const stringToSign = constructChunkStringToSign(
this.timestamp,
this.credentialScope,
this.lastSignature,
dataToSend
);
this.log.trace('constructed chunk string to sign', { stringToSign });
// once used prior sig to construct string to sign, reassign
// lastSignature to current signature
this.lastSignature = this.currentSignature;
@ -165,17 +193,18 @@ class V4Transform extends Transform {
credentialScope: this.credentialScope,
},
};
return this.vault.authenticateV4Request(vaultParams, null, err => {
return this.vault.authenticateV4Request(vaultParams, null, (err) => {
if (err) {
this.log.trace('err from vault on streaming v4 auth',
{ error: err, paramsSentToVault: vaultParams.data });
this.log.trace('err from vault on streaming v4 auth', {
error: err,
paramsSentToVault: vaultParams.data,
});
return done(err);
}
return done();
});
}
/**
* This function will parse the chunk into metadata and data,
* use the metadata to authenticate with vault and send the
@ -195,9 +224,10 @@ class V4Transform extends Transform {
if (this.lastPieceDone) {
const slice = chunk.slice(0, 10);
this.log.trace('received chunk after end.' +
'See first 10 bytes of chunk',
{ chunk: slice.toString() });
this.log.trace(
'received chunk after end.' + 'See first 10 bytes of chunk',
{ chunk: slice.toString() }
);
return callback();
}
let unparsedChunk = chunk;
@ -206,10 +236,11 @@ class V4Transform extends Transform {
// test function
() => chunkLeftToEvaluate,
// async function
done => {
(done) => {
if (!this.haveMetadata) {
this.log.trace('do not have metadata so calling ' +
'_parseMetadata');
this.log.trace(
'do not have metadata so calling ' + '_parseMetadata'
);
// need to parse our metadata
const parsedMetadataResults =
this._parseMetadata(unparsedChunk);
@ -227,7 +258,7 @@ class V4Transform extends Transform {
}
if (this.lastChunk) {
this.log.trace('authenticating final chunk with no data');
return this._authenticate(null, err => {
return this._authenticate(null, (err) => {
if (err) {
return done(err);
}
@ -246,17 +277,18 @@ class V4Transform extends Transform {
}
// parse just the next data piece without \r\n at the end
// (therefore, minus 2)
const nextDataPiece =
unparsedChunk.slice(0, this.seekingDataSize - 2);
const nextDataPiece = unparsedChunk.slice(
0,
this.seekingDataSize - 2
);
// add parsed data piece to other currentData pieces
// so that this.currentData is the full data piece
nextDataPiece.copy(this.currentData, this.dataCursor);
return this._authenticate(this.currentData, err => {
return this._authenticate(this.currentData, (err) => {
if (err) {
return done(err);
}
unparsedChunk =
unparsedChunk.slice(this.seekingDataSize);
unparsedChunk = unparsedChunk.slice(this.seekingDataSize);
this.push(this.currentData);
this.haveMetadata = false;
this.seekingDataSize = -1;
@ -267,7 +299,7 @@ class V4Transform extends Transform {
});
},
// final callback
err => {
(err) => {
if (err) {
return this.cb(err);
}
@ -277,5 +309,3 @@ class V4Transform extends Transform {
);
}
}
module.exports = V4Transform;

View File

@ -1,6 +1,5 @@
const crypto = require('crypto');
const constants = require('../../../constants');
import * as crypto from 'crypto';
import * as constants from '../../../constants';
/**
* Constructs stringToSign for chunk
@ -13,8 +12,12 @@ const constants = require('../../../constants');
* @param {string} justDataChunk - data portion of chunk
* @returns {string} stringToSign
*/
function constructChunkStringToSign(timestamp,
credentialScope, lastSignature, justDataChunk) {
export default function constructChunkStringToSign(
timestamp: string,
credentialScope: string,
lastSignature: string,
justDataChunk: string
): string {
let currentChunkHash;
// for last chunk, there will be no data, so use emptyStringHash
if (!justDataChunk) {
@ -22,11 +25,12 @@ function constructChunkStringToSign(timestamp,
} else {
currentChunkHash = crypto.createHash('sha256');
currentChunkHash = currentChunkHash
.update(justDataChunk, 'binary').digest('hex');
.update(justDataChunk, 'binary')
.digest('hex');
}
return `AWS4-HMAC-SHA256-PAYLOAD\n${timestamp}\n` +
return (
`AWS4-HMAC-SHA256-PAYLOAD\n${timestamp}\n` +
`${credentialScope}\n${lastSignature}\n` +
`${constants.emptyStringHash}\n${currentChunkHash}`;
`${constants.emptyStringHash}\n${currentChunkHash}`
);
}
module.exports = constructChunkStringToSign;

View File

@ -1,12 +1,10 @@
'use strict'; // eslint-disable-line strict
/**
* Convert timestamp to milliseconds since Unix Epoch
* @param {string} timestamp of ISO8601Timestamp format without
* dashes or colons, e.g. 20160202T220410Z
* @return {number} number of milliseconds since Unix Epoch
*/
function convertAmzTimeToMs(timestamp) {
export function convertAmzTimeToMs(timestamp) {
const arr = timestamp.split('');
// Convert to YYYY-MM-DDTHH:mm:ss.sssZ
const ISO8601time = `${arr.slice(0, 4).join('')}-${arr[4]}${arr[5]}` +
@ -21,7 +19,7 @@ function convertAmzTimeToMs(timestamp) {
* @param {string} timestamp of UTC form: Fri, 10 Feb 2012 21:34:55 GMT
* @return {string} ISO8601 timestamp of form: YYYYMMDDTHHMMSSZ
*/
function convertUTCtoISO8601(timestamp) {
export function convertUTCtoISO8601(timestamp) {
// convert to ISO string: YYYY-MM-DDTHH:mm:ss.sssZ.
const converted = new Date(timestamp).toISOString();
// Remove "-"s and "."s and milliseconds
@ -36,7 +34,7 @@ function convertUTCtoISO8601(timestamp) {
* @param {object} log - log for request
* @return {boolean} true if there is a time problem
*/
function checkTimeSkew(timestamp, expiry, log) {
export function checkTimeSkew(timestamp, expiry, log) {
const currentTime = Date.now();
const fifteenMinutes = (15 * 60 * 1000);
const parsedTimestamp = convertAmzTimeToMs(timestamp);
@ -56,5 +54,3 @@ function checkTimeSkew(timestamp, expiry, log) {
}
return false;
}
module.exports = { convertAmzTimeToMs, convertUTCtoISO8601, checkTimeSkew };

View File

@ -1,6 +1,4 @@
'use strict'; // eslint-disable-line strict
const errors = require('../../../lib/errors');
import errors from '../../../lib/errors';
/**
* Validate Credentials
@ -11,7 +9,7 @@ const errors = require('../../../lib/errors');
* @param {object} log - logging object
* @return {boolean} true if credentials are correct format, false if not
*/
function validateCredentials(credentials, timestamp, log) {
export function validateCredentials(credentials, timestamp, log) {
if (!Array.isArray(credentials) || credentials.length !== 5) {
log.warn('credentials in improper format', { credentials });
return errors.InvalidArgument;
@ -25,32 +23,39 @@ function validateCredentials(credentials, timestamp, log) {
log.warn('accessKey provided is wrong format', { accessKey });
return errors.InvalidArgument;
}
// The scope date (format YYYYMMDD) must be same date as the timestamp
// on the request from the x-amz-date param (if queryAuthCheck)
// or from the x-amz-date header or date header (if headerAuthCheck)
// Format of timestamp is ISO 8601: YYYYMMDDTHHMMSSZ.
// http://docs.aws.amazon.com/AmazonS3/latest/API/
// sigv4-query-string-auth.html
// http://docs.aws.amazon.com/general/latest/gr/
// sigv4-date-handling.html
// The scope date (format YYYYMMDD) must be same date as the timestamp
// on the request from the x-amz-date param (if queryAuthCheck)
// or from the x-amz-date header or date header (if headerAuthCheck)
// Format of timestamp is ISO 8601: YYYYMMDDTHHMMSSZ.
// http://docs.aws.amazon.com/AmazonS3/latest/API/
// sigv4-query-string-auth.html
// http://docs.aws.amazon.com/general/latest/gr/
// sigv4-date-handling.html
// convert timestamp to format of scopeDate YYYYMMDD
// convert timestamp to format of scopeDate YYYYMMDD
const timestampDate = timestamp.split('T')[0];
if (scopeDate.length !== 8 || scopeDate !== timestampDate) {
log.warn('scope date must be the same date as the timestamp date',
{ scopeDate, timestampDate });
log.warn('scope date must be the same date as the timestamp date', {
scopeDate,
timestampDate,
});
return errors.RequestTimeTooSkewed;
}
if (service !== 's3' && service !== 'iam' && service !== 'ring' &&
service !== 'sts') {
if (
service !== 's3' &&
service !== 'iam' &&
service !== 'ring' &&
service !== 'sts'
) {
log.warn('service in credentials is not one of s3/iam/ring/sts', {
service,
});
return errors.InvalidArgument;
}
if (requestType !== 'aws4_request') {
log.warn('requestType contained in params is not aws4_request',
{ requestType });
log.warn('requestType contained in params is not aws4_request', {
requestType,
});
return errors.InvalidArgument;
}
return {};
@ -62,13 +67,14 @@ function validateCredentials(credentials, timestamp, log) {
* @param {object} log - logging object
* @return {object} object containing extracted query params for authV4
*/
function extractQueryParams(queryObj, log) {
export function extractQueryParams(queryObj, log) {
const authParams = {};
// Do not need the algorithm sent back
if (queryObj['X-Amz-Algorithm'] !== 'AWS4-HMAC-SHA256') {
log.warn('algorithm param incorrect',
{ algo: queryObj['X-Amz-Algorithm'] });
log.warn('algorithm param incorrect', {
algo: queryObj['X-Amz-Algorithm'],
});
return authParams;
}
@ -81,7 +87,6 @@ function extractQueryParams(queryObj, log) {
return authParams;
}
const signature = queryObj['X-Amz-Signature'];
if (signature && signature.length === 64) {
authParams.signatureFromRequest = signature;
@ -94,14 +99,15 @@ function extractQueryParams(queryObj, log) {
if (timestamp && timestamp.length === 16) {
authParams.timestamp = timestamp;
} else {
log.warn('missing or invalid timestamp',
{ timestamp: queryObj['X-Amz-Date'] });
log.warn('missing or invalid timestamp', {
timestamp: queryObj['X-Amz-Date'],
});
return authParams;
}
const expiry = Number.parseInt(queryObj['X-Amz-Expires'], 10);
const sevenDays = 604800;
if (expiry && (expiry > 0 && expiry <= sevenDays)) {
if (expiry && expiry > 0 && expiry <= sevenDays) {
authParams.expiry = expiry;
} else {
log.warn('invalid expiry', { expiry });
@ -118,17 +124,15 @@ function extractQueryParams(queryObj, log) {
return authParams;
}
/**
* Extract and validate components from auth header
* @param {string} authHeader - authorization header from request
* @param {object} log - logging object
* @return {object} object containing extracted auth header items for authV4
*/
function extractAuthItems(authHeader, log) {
export function extractAuthItems(authHeader, log) {
const authItems = {};
const authArray = authHeader
.replace('AWS4-HMAC-SHA256 ', '').split(',');
const authArray = authHeader.replace('AWS4-HMAC-SHA256 ', '').split(',');
if (authArray.length < 3) {
return authItems;
@ -138,25 +142,34 @@ function extractAuthItems(authHeader, log) {
const signedHeadersStr = authArray[1];
const signatureStr = authArray[2];
log.trace('credentials from request', { credentialStr });
if (credentialStr && credentialStr.trim().startsWith('Credential=')
&& credentialStr.indexOf('/') > -1) {
if (
credentialStr &&
credentialStr.trim().startsWith('Credential=') &&
credentialStr.indexOf('/') > -1
) {
authItems.credentialsArr = credentialStr
.trim().replace('Credential=', '').split('/');
.trim()
.replace('Credential=', '')
.split('/');
} else {
log.warn('missing credentials');
}
log.trace('signed headers from request', { signedHeadersStr });
if (signedHeadersStr && signedHeadersStr.trim()
.startsWith('SignedHeaders=')) {
if (
signedHeadersStr &&
signedHeadersStr.trim().startsWith('SignedHeaders=')
) {
authItems.signedHeaders = signedHeadersStr
.trim().replace('SignedHeaders=', '');
.trim()
.replace('SignedHeaders=', '');
} else {
log.warn('missing signed headers');
}
log.trace('signature from request', { signatureStr });
if (signatureStr && signatureStr.trim().startsWith('Signature=')) {
authItems.signatureFromRequest = signatureStr
.trim().replace('Signature=', '');
.trim()
.replace('Signature=', '');
} else {
log.warn('missing signature');
}
@ -170,21 +183,20 @@ function extractAuthItems(authHeader, log) {
* @param {object} allHeaders - request.headers
* @return {boolean} true if all x-amz-headers included and false if not
*/
function areSignedHeadersComplete(signedHeaders, allHeaders) {
export function areSignedHeadersComplete(signedHeaders, allHeaders) {
const signedHeadersList = signedHeaders.split(';');
if (signedHeadersList.indexOf('host') === -1) {
return false;
}
const headers = Object.keys(allHeaders);
for (let i = 0; i < headers.length; i++) {
if ((headers[i].startsWith('x-amz-')
|| headers[i].startsWith('x-scal-'))
&& signedHeadersList.indexOf(headers[i]) === -1) {
if (
(headers[i].startsWith('x-amz-') ||
headers[i].startsWith('x-scal-')) &&
signedHeadersList.indexOf(headers[i]) === -1
) {
return false;
}
}
return true;
}
module.exports = { validateCredentials, extractQueryParams,
areSignedHeadersComplete, extractAuthItems };

View File

@ -103,11 +103,11 @@ module.exports = {
gcpTaggingPrefix: 'aws-tag-',
productName: 'APN/1.0 Scality/1.0 Scality CloudServer for Zenko',
legacyLocations: ['sproxyd', 'legacy'],
// healthcheck default call from nginx is every 2 seconds
// healthcheck default call from nginx is every 2 seconds
// for external backends, don't call unless at least 1 minute
// (60,000 milliseconds) since last call
externalBackendHealthCheckInterval: 60000,
// some of the available data backends (if called directly rather
// some of the available data backends (if called directly rather
// than through the multiple backend gateway) need a key provided
// as a string as first parameter of the get/delete methods.
clientsRequireStringKey: { sproxyd: true, cdmi: true },

View File

@ -1,4 +1,4 @@
'use strict'; // eslint-disable-line strict
import { LevelDB } from 'level';
const writeOptions = { sync: true };
@ -18,7 +18,7 @@ const writeOptions = { sync: true };
* @param {String} message - the Error message.
* @returns {Error} the Error object.
*/
function propError(propName, message) {
function propError(propName: string, message: string): Error {
const err = new Error(message);
err[propName] = true;
return err;
@ -27,7 +27,7 @@ function propError(propName, message) {
/**
* Running transaction with multiple updates to be committed atomically
*/
class IndexTransaction {
export class IndexTransaction {
/**
* Builds a new transaction
*
@ -36,7 +36,7 @@ class IndexTransaction {
*
* @returns {IndexTransaction} a new empty transaction
*/
constructor(db) {
constructor(db: LevelDB) {
this.operations = [];
this.db = db;
this.closed = false;
@ -63,13 +63,17 @@ class IndexTransaction {
*/
push(op) {
if (this.closed) {
throw propError('pushOnCommittedTransaction',
'can not add ops to already committed transaction');
throw propError(
'pushOnCommittedTransaction',
'can not add ops to already committed transaction'
);
}
if (op.type !== 'put' && op.type !== 'del') {
throw propError('invalidTransactionVerb',
`unknown action type: ${op.type}`);
throw propError(
'invalidTransactionVerb',
`unknown action type: ${op.type}`
);
}
if (op.key === undefined) {
@ -136,14 +140,22 @@ class IndexTransaction {
*/
addCondition(condition) {
if (this.closed) {
throw propError('pushOnCommittedTransaction',
'can not add conditions to already committed transaction');
throw propError(
'pushOnCommittedTransaction',
'can not add conditions to already committed transaction'
);
}
if (condition === undefined || Object.keys(condition).length === 0) {
throw propError('missingCondition', 'missing condition for conditional put');
throw propError(
'missingCondition',
'missing condition for conditional put'
);
}
if (typeof (condition.notExists) !== 'string') {
throw propError('unsupportedConditionalOperation', 'missing key or supported condition');
if (typeof condition.notExists !== 'string') {
throw propError(
'unsupportedConditionalOperation',
'missing key or supported condition'
);
}
this.conditions.push(condition);
}
@ -158,13 +170,21 @@ class IndexTransaction {
*/
commit(cb) {
if (this.closed) {
return cb(propError('alreadyCommitted',
'transaction was already committed'));
return cb(
propError(
'alreadyCommitted',
'transaction was already committed'
)
);
}
if (this.operations.length === 0) {
return cb(propError('emptyTransaction',
'tried to commit an empty transaction'));
return cb(
propError(
'emptyTransaction',
'tried to commit an empty transaction'
)
);
}
this.closed = true;
@ -176,7 +196,3 @@ class IndexTransaction {
return this.db.batch(this.operations, writeOptions, cb);
}
}
module.exports = {
IndexTransaction,
};

View File

@ -1,4 +1,4 @@
function reshapeExceptionError(error) {
export function reshapeExceptionError(error) {
const { message, code, stack, name } = error;
return {
message,
@ -7,7 +7,3 @@ function reshapeExceptionError(error) {
name,
};
}
module.exports = {
reshapeExceptionError,
};

View File

@ -1,11 +1,14 @@
'use strict'; // eslint-disable-line strict
import errorsObj from '../errors/arsenalErrors.json';
/**
* ArsenalError
*
* @extends {Error}
*/
class ArsenalError extends Error {
export class ArsenalError extends Error {
code: number
description: string
/**
* constructor.
*
@ -13,7 +16,7 @@ class ArsenalError extends Error {
* @param {number} code - HTTP status code
* @param {string} desc - Verbose description of error
*/
constructor(type, code, desc) {
constructor(type: string, code: number, desc: string) {
super(type);
/**
@ -65,23 +68,12 @@ class ArsenalError extends Error {
}
}
/**
* Generate an Errors instances object.
*
* @returns {Object.<string, ArsenalError>} - object field by arsenalError
* instances
*/
function errorsGen() {
const errors = {};
const errorsObj = require('../errors/arsenalErrors.json');
const errors: { [key: string]: ArsenalError } = {};
Object.keys(errorsObj)
.filter((index) => index !== '_comment')
.forEach((index) => {
const { code, description } = errorsObj[index];
errors[index] = new ArsenalError(index, code, description);
});
Object.keys(errorsObj)
.filter(index => index !== '_comment')
.forEach(index => {
errors[index] = new ArsenalError(index, errorsObj[index].code,
errorsObj[index].description);
});
return errors;
}
module.exports = errorsGen();
export default errors;

View File

@ -17,9 +17,9 @@ describe('decyrptSecret', () => {
describe('parseServiceCredentials', () => {
const conf = {
users: [{ accessKey,
accountType: 'service-clueso',
secretKey,
userName: 'Search Service Account' }],
accountType: 'service-clueso',
secretKey,
userName: 'Search Service Account' }],
};
const auth = JSON.stringify({ privateKey });

View File

@ -1,6 +1,4 @@
'use strict'; // eslint-disable-line strict
const ciphers = [
export const ciphers = [
'DHE-RSA-AES128-GCM-SHA256',
'ECDHE-ECDSA-AES128-GCM-SHA256',
'ECDHE-RSA-AES256-GCM-SHA384',
@ -28,7 +26,3 @@ const ciphers = [
'!EDH-RSA-DES-CBC3-SHA',
'!KRB5-DES-CBC3-SHA',
].join(':');
module.exports = {
ciphers,
};

View File

@ -29,16 +29,11 @@ c2CNfUEqyRbJF4pE9ZcdQReT5p/llmyhQdvq6cHH+cKJk63C6DHRVoStsnsUcvKe
bLxKsygK77ttjr61cxLoDJeGd5L5h1CPmwIBAg==
-----END DH PARAMETERS-----
*/
'use strict'; // eslint-disable-line strict
const dhparam =
export const dhparam =
'MIIBCAKCAQEAh99T77KGNuiY9N6xrCJ3QNv4SFADTa3CD+1VMTAdRJLHUNpglB+i' +
'AoTYiLDFZgtTCpx0ZZUD+JM3qiCZy0OK5/ZGlVD7sZmxjRtdpVK4qIPtwav8t0J7' +
'c2CNfUEqyRbJF4pE9ZcdQReT5p/llmyhQdvq6cHH+cKJk63C6DHRVoStsnsUcvKe' +
'23PLGZulKg8H3eRBxHamHkmyuEVDtoNhMIoJONsdXSpo5GgcD4EQMM8xb/qsnCxn' +
'6QIGTBvcHskxtlTZOfUPk4XQ6Yb3tQi2TurzkQHLln4U7p/GZs+D+6D3SgSPqr6P' +
'bLxKsygK77ttjr61cxLoDJeGd5L5h1CPmwIBAg==';
module.exports = {
dhparam,
};

2
lib/https/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * as ciphers from './ciphers'
export * as dhparam from './dh2048'

View File

@ -1,6 +1,4 @@
'use strict'; // eslint-disable-line strict
const ipaddr = require('ipaddr.js');
import ipaddr from 'ipaddr.js';
/**
* checkIPinRangeOrMatch checks whether a given ip address is in an ip address
@ -9,7 +7,7 @@ const ipaddr = require('ipaddr.js');
* @param {object} ip - parsed ip address
* @return {boolean} true if in range, false if not
*/
function checkIPinRangeOrMatch(cidr, ip) {
export function checkIPinRangeOrMatch(cidr, ip) {
// If there is an exact match of the ip address, no need to check ranges
if (ip.toString() === cidr) {
return true;
@ -39,7 +37,7 @@ function checkIPinRangeOrMatch(cidr, ip) {
* @param {string} ip - IPV4/IPV6/IPV4-mapped IPV6 address
* @return {object} parsedIp - Object representation of parsed IP
*/
function parseIp(ip) {
export function parseIp(ip) {
if (ipaddr.IPv4.isValid(ip)) {
return ipaddr.parse(ip);
}
@ -60,7 +58,7 @@ function parseIp(ip) {
* @param {string} ip - IP address
* @return {boolean} - true if there is match or false for no match
*/
function ipMatchCidrList(cidrList, ip) {
export function ipMatchCidrList(cidrList, ip) {
const parsedIp = parseIp(ip);
return cidrList.some(item => {
let cidr;
@ -75,9 +73,3 @@ function ipMatchCidrList(cidrList, ip) {
return checkIPinRangeOrMatch(cidr || item, parsedIp);
});
}
module.exports = {
checkIPinRangeOrMatch,
ipMatchCidrList,
parseIp,
};

View File

@ -1,6 +1,5 @@
'use strict'; // eslint-disable-line
const debug = require('util').debuglog('jsutil');
import { debuglog } from 'util';
const debug = debuglog('jsutil');
// JavaScript utility functions
@ -17,7 +16,7 @@ const debug = require('util').debuglog('jsutil');
* @return {function} a callable wrapper mirroring <tt>func</tt> but
* only calls <tt>func</tt> at first invocation.
*/
module.exports.once = function once(func) {
export function once(func) {
const state = { called: false, res: undefined };
return function wrapper(...args) {
if (!state.called) {
@ -25,7 +24,7 @@ module.exports.once = function once(func) {
state.res = func.apply(func, args);
} else {
debug('function already called:', func,
'returning cached result:', state.res);
'returning cached result:', state.res);
}
return state.res;
};

View File

@ -1,17 +1,19 @@
const Redis = require('ioredis');
import Redis from 'ioredis';
export default class RedisClient {
_client: Redis
class RedisClient {
/**
* @constructor
* @param {Object} config - config
* @param {string} config.host - Redis host
* @param {number} config.port - Redis port
* @param {string} config.password - Redis password
* @param {werelogs.Logger} logger - logger instance
*/
* @constructor
* @param {Object} config - config
* @param {string} config.host - Redis host
* @param {number} config.port - Redis port
* @param {string} config.password - Redis password
* @param {werelogs.Logger} logger - logger instance
*/
constructor(config, logger) {
this._client = new Redis(config);
this._client.on('error', err =>
this._client.on('error', (err) =>
logger.trace('error from redis', {
error: err,
method: 'RedisClient.constructor',
@ -23,18 +25,18 @@ class RedisClient {
}
/**
* scan a pattern and return matching keys
* @param {string} pattern - string pattern to match with all existing keys
* @param {number} [count=10] - scan count
* @param {callback} cb - callback (error, result)
* @return {undefined}
*/
scan(pattern, count = 10, cb) {
* scan a pattern and return matching keys
* @param {string} pattern - string pattern to match with all existing keys
* @param {number} [count=10] - scan count
* @param {callback} cb - callback (error, result)
* @return {undefined}
*/
scan(pattern: string, count = 10, cb) {
const params = { match: pattern, count };
const keys = [];
const stream = this._client.scanStream(params);
stream.on('data', resultKeys => {
stream.on('data', (resultKeys) => {
for (let i = 0; i < resultKeys.length; i++) {
keys.push(resultKeys[i]);
}
@ -45,15 +47,18 @@ class RedisClient {
}
/**
* increment value of a key by 1 and set a ttl
* @param {string} key - key holding the value
* @param {number} expiry - expiry in seconds
* @param {callback} cb - callback
* @return {undefined}
*/
incrEx(key, expiry, cb) {
* increment value of a key by 1 and set a ttl
* @param {string} key - key holding the value
* @param {number} expiry - expiry in seconds
* @param {callback} cb - callback
* @return {undefined}
*/
incrEx(key: string, expiry: number, cb) {
return this._client
.multi([['incr', key], ['expire', key, expiry]])
.multi([
['incr', key],
['expire', key, expiry],
])
.exec(cb);
}
@ -64,7 +69,7 @@ class RedisClient {
* @param {callback} cb - callback
* @return {undefined}
*/
incrby(key, amount, cb) {
incrby(key: string, amount: number, cb) {
return this._client.incrby(key, amount, cb);
}
@ -76,9 +81,12 @@ class RedisClient {
* @param {callback} cb - callback
* @return {undefined}
*/
incrbyEx(key, amount, expiry, cb) {
incrbyEx(key: string, amount: number, expiry: number, cb) {
return this._client
.multi([['incrby', key, amount], ['expire', key, expiry]])
.multi([
['incrby', key, amount],
['expire', key, expiry],
])
.exec(cb);
}
@ -89,7 +97,7 @@ class RedisClient {
* @param {callback} cb - callback
* @return {undefined}
*/
decrby(key, amount, cb) {
decrby(key: string, amount: number, cb) {
return this._client.decrby(key, amount, cb);
}
@ -99,7 +107,7 @@ class RedisClient {
* @param {callback} cb - callback
* @return {undefined}
*/
get(key, cb) {
get(key: string, cb) {
return this._client.get(key, cb);
}
@ -111,17 +119,17 @@ class RedisClient {
* If cb response returns 1, key exists.
* @return {undefined}
*/
exists(key, cb) {
exists(key: string, cb) {
return this._client.exists(key, cb);
}
/**
* execute a batch of commands
* @param {string[]} cmds - list of commands
* @param {callback} cb - callback
* @return {undefined}
*/
batch(cmds, cb) {
* execute a batch of commands
* @param {string[]} cmds - list of commands
* @param {callback} cb - callback
* @return {undefined}
*/
batch(cmds: string[], cb) {
return this._client.pipeline(cmds).exec(cb);
}
@ -134,7 +142,7 @@ class RedisClient {
* @param {callback} cb - callback
* @return {undefined}
*/
zadd(key, score, value, cb) {
zadd(key: string, score: number, value: string, cb) {
return this._client.zadd(key, score, value, cb);
}
@ -147,7 +155,7 @@ class RedisClient {
* @param {function} cb - callback
* @return {undefined}
*/
zcard(key, cb) {
zcard(key: string, cb) {
return this._client.zcard(key, cb);
}
@ -161,7 +169,7 @@ class RedisClient {
* @param {function} cb - callback
* @return {undefined}
*/
zscore(key, value, cb) {
zscore(key: string, value: string, cb) {
return this._client.zscore(key, value, cb);
}
@ -174,7 +182,7 @@ class RedisClient {
* The cb response returns number of values removed
* @return {undefined}
*/
zrem(key, value, cb) {
zrem(key: string, value: string | any[], cb) {
return this._client.zrem(key, value, cb);
}
@ -186,7 +194,7 @@ class RedisClient {
* @param {function} cb - callback
* @return {undefined}
*/
zrange(key, start, end, cb) {
zrange(key: string, start: number, end: number, cb) {
return this._client.zrange(key, start, end, cb);
}
@ -200,7 +208,7 @@ class RedisClient {
* @param {function} cb - callback
* @return {undefined}
*/
zrangebyscore(key, min, max, cb) {
zrangebyscore(key: string, min: number | string, max: number | string, cb) {
return this._client.zrangebyscore(key, min, max, cb);
}
@ -210,7 +218,7 @@ class RedisClient {
* @param {function} cb - callback
* @return {undefined}
*/
ttl(key, cb) {
ttl(key: string, cb) {
return this._client.ttl(key, cb);
}
@ -226,5 +234,3 @@ class RedisClient {
return this._client.client('list', cb);
}
}
module.exports = RedisClient;

View File

@ -1,13 +1,18 @@
const async = require('async');
import async from 'async';
import RedisClient from './RedisClient';
export default class StatsClient {
_redis?: RedisClient;
_interval: number;
_expiry: number;
class StatsClient {
/**
* @constructor
* @param {object} redisClient - RedisClient instance
* @param {number} interval - sampling interval by seconds
* @param {number} expiry - sampling duration by seconds
*/
constructor(redisClient, interval, expiry) {
constructor(redisClient: RedisClient, interval: number, expiry: number) {
this._redis = redisClient;
this._interval = interval;
this._expiry = expiry;
@ -24,9 +29,9 @@ class StatsClient {
* @param {object} d - Date instance
* @return {number} timestamp - normalized to the nearest interval
*/
_normalizeTimestamp(d) {
_normalizeTimestamp(d: Date): number {
const s = d.getSeconds();
return d.setSeconds(s - s % this._interval, 0);
return d.setSeconds(s - (s % this._interval), 0);
}
/**
@ -34,7 +39,7 @@ class StatsClient {
* @param {object} d - Date instance
* @return {number} timestamp - set to the previous interval
*/
_setPrevInterval(d) {
_setPrevInterval(d: Date): number {
return d.setSeconds(d.getSeconds() - this._interval);
}
@ -44,7 +49,7 @@ class StatsClient {
* @param {Date} date - Date instance
* @return {string} key - key for redis
*/
buildKey(name, date) {
buildKey(name: string, date: Date): string {
return `${name}:${this._normalizeTimestamp(date)}`;
}
@ -54,7 +59,7 @@ class StatsClient {
* @param {array} arr - Date instance
* @return {string} key - key for redis
*/
_getCount(arr) {
_getCount(arr: any[]): string {
return arr.reduce((prev, a) => {
let num = parseInt(a[1], 10);
num = Number.isNaN(num) ? 0 : num;
@ -69,7 +74,7 @@ class StatsClient {
* @param {function} cb - callback
* @return {undefined}
*/
reportNewRequest(id, incr, cb) {
reportNewRequest(id: string, incr: number, cb) {
if (!this._redis) {
return undefined;
}
@ -81,8 +86,8 @@ class StatsClient {
callback = incr;
amount = 1;
} else {
callback = (cb && typeof cb === 'function') ? cb : this._noop;
amount = (typeof incr === 'number') ? incr : 1;
callback = cb && typeof cb === 'function' ? cb : this._noop;
amount = typeof incr === 'number' ? incr : 1;
}
const key = this.buildKey(`${id}:requests`, new Date());
@ -97,7 +102,7 @@ class StatsClient {
* @param {function} [cb] - callback
* @return {undefined}
*/
incrementKey(key, incr, cb) {
incrementKey(key: string, incr: number, cb) {
const callback = cb || this._noop;
return this._redis.incrby(key, incr, callback);
}
@ -109,18 +114,18 @@ class StatsClient {
* @param {function} [cb] - callback
* @return {undefined}
*/
decrementKey(key, decr, cb) {
decrementKey(key: string, decr: number, cb) {
const callback = cb || this._noop;
return this._redis.decrby(key, decr, callback);
}
/**
* report/record a request that ended up being a 500 on the server
* @param {string} id - service identifier
* @param {callback} cb - callback
* @return {undefined}
*/
report500(id, cb) {
* report/record a request that ended up being a 500 on the server
* @param {string} id - service identifier
* @param {callback} cb - callback
* @return {undefined}
*/
report500(id: string, cb) {
if (!this._redis) {
return undefined;
}
@ -136,41 +141,46 @@ class StatsClient {
* @param {callback} cb - callback to call with the err/result
* @return {undefined}
*/
getAllStats(log, ids, cb) {
getAllStats(log, ids: any[], cb) {
if (!this._redis) {
return cb(null, {});
}
const statsRes = {
'requests': 0,
requests: 0,
'500s': 0,
'sampleDuration': this._expiry,
sampleDuration: this._expiry,
};
let requests = 0;
let errors = 0;
// for now set concurrency to default of 10
return async.eachLimit(ids, 10, (id, done) => {
this.getStats(log, id, (err, res) => {
if (err) {
return done(err);
}
requests += res.requests;
errors += res['500s'];
return done();
});
}, error => {
if (error) {
log.error('error getting stats', {
error,
method: 'StatsClient.getAllStats',
return async.eachLimit(
ids,
10,
(id, done) => {
this.getStats(log, id, (err, res) => {
if (err) {
return done(err);
}
requests += res.requests;
errors += res['500s'];
return done();
});
},
(error) => {
if (error) {
log.error('error getting stats', {
error,
method: 'StatsClient.getAllStats',
});
return cb(null, statsRes);
}
statsRes.requests = requests;
statsRes['500s'] = errors;
return cb(null, statsRes);
}
statsRes.requests = requests;
statsRes['500s'] = errors;
return cb(null, statsRes);
});
);
}
/**
@ -180,7 +190,7 @@ class StatsClient {
* @param {callback} cb - callback to call with the err/result
* @return {undefined}
*/
getStats(log, id, cb) {
getStats(log, id: string, cb) {
if (!this._redis) {
return cb(null, {});
}
@ -205,9 +215,9 @@ class StatsClient {
* index 1 contains the result
*/
const statsRes = {
'requests': 0,
requests: 0,
'500s': 0,
'sampleDuration': this._expiry,
sampleDuration: this._expiry,
};
if (err) {
log.error('error getting stats', {
@ -215,10 +225,10 @@ class StatsClient {
method: 'StatsClient.getStats',
});
/**
* Redis for stats is not a critial component, ignoring
* any error here as returning an InternalError
* would be confused with the health of the service
*/
* Redis for stats is not a critial component, ignoring
* any error here as returning an InternalError
* would be confused with the health of the service
*/
return cb(null, statsRes);
}
statsRes.requests = this._getCount(results[0]);
@ -227,5 +237,3 @@ class StatsClient {
});
}
}
module.exports = StatsClient;

View File

@ -1,6 +1,5 @@
const async = require('async');
const StatsClient = require('./StatsClient');
import async from 'async';
import StatsClient from './StatsClient';
/**
* @class StatsModel
@ -8,40 +7,39 @@ const StatsClient = require('./StatsClient');
* @classdesc Extend and overwrite how timestamps are normalized by minutes
* rather than by seconds
*/
class StatsModel extends StatsClient {
export default class StatsModel extends StatsClient {
/**
* Utility method to convert 2d array rows to columns, and vice versa
* See also: https://docs.ruby-lang.org/en/2.0.0/Array.html#method-i-zip
* @param {array} arrays - 2d array of integers
* @return {array} converted array
*/
_zip(arrays) {
if (arrays.length > 0 && arrays.every(a => Array.isArray(a))) {
return arrays[0].map((_, i) => arrays.map(a => a[i]));
* Utility method to convert 2d array rows to columns, and vice versa
* See also: https://docs.ruby-lang.org/en/2.0.0/Array.html#method-i-zip
* @param {array} arrays - 2d array of integers
* @return {array} converted array
*/
_zip(arrays: number[][]) {
if (arrays.length > 0 && arrays.every((a) => Array.isArray(a))) {
return arrays[0].map((_, i) => arrays.map((a) => a[i]));
}
return [];
}
/**
* normalize to the nearest interval
* @param {object} d - Date instance
* @return {number} timestamp - normalized to the nearest interval
*/
_normalizeTimestamp(d) {
* normalize to the nearest interval
* @param {object} d - Date instance
* @return {number} timestamp - normalized to the nearest interval
*/
_normalizeTimestamp(d: Date) {
const m = d.getMinutes();
return d.setMinutes(m - m % (Math.floor(this._interval / 60)), 0, 0);
return d.setMinutes(m - (m % Math.floor(this._interval / 60)), 0, 0);
}
/**
* override the method to get the count as an array of integers separated
* by each interval
* typical input looks like [[null, '1'], [null, '2'], [null, null]...]
* @param {array} arr - each index contains the result of each batch command
* where index 0 signifies the error and index 1 contains the result
* @return {array} array of integers, ordered from most recent interval to
* oldest interval with length of (expiry / interval)
*/
* override the method to get the count as an array of integers separated
* by each interval
* typical input looks like [[null, '1'], [null, '2'], [null, null]...]
* @param {array} arr - each index contains the result of each batch command
* where index 0 signifies the error and index 1 contains the result
* @return {array} array of integers, ordered from most recent interval to
* oldest interval with length of (expiry / interval)
*/
_getCount(arr) {
const size = Math.floor(this._expiry / this._interval);
const array = arr.reduce((store, i) => {
@ -58,23 +56,23 @@ class StatsModel extends StatsClient {
}
/**
* wrapper on `getStats` that handles a list of keys
* override the method to reduce the returned 2d array from `_getCount`
* @param {object} log - Werelogs request logger
* @param {array} ids - service identifiers
* @param {callback} cb - callback to call with the err/result
* @return {undefined}
*/
getAllStats(log, ids, cb) {
* wrapper on `getStats` that handles a list of keys
* override the method to reduce the returned 2d array from `_getCount`
* @param {object} log - Werelogs request logger
* @param {array} ids - service identifiers
* @param {callback} cb - callback to call with the err/result
* @return {undefined}
*/
getAllStats(log, ids: string[], cb) {
if (!this._redis) {
return cb(null, {});
}
const size = Math.floor(this._expiry / this._interval);
const statsRes = {
'requests': Array(size).fill(0),
requests: Array(size).fill(0),
'500s': Array(size).fill(0),
'sampleDuration': this._expiry,
sampleDuration: this._expiry,
};
const requests = [];
const errors = [];
@ -118,9 +116,9 @@ class StatsModel extends StatsClient {
* @param {function} cb - Callback
* @return {undefined}
*/
getAllGlobalStats(ids, log, cb) {
const reqsKeys = ids.map(key => (['get', key]));
return this._redis.batch(reqsKeys, (err, res) => {
getAllGlobalStats(ids: string[], log, cb) {
const reqsKeys = ids.map((key) => ['get', key]);
return this._redis!.batch(reqsKeys, (err, res) => {
const statsRes = { requests: 0 };
if (err) {
log.error('error getting metrics', {
@ -149,7 +147,7 @@ class StatsModel extends StatsClient {
* @param {Date} d - Date instance
* @return {number} timestamp - normalized to the nearest hour
*/
normalizeTimestampByHour(d) {
normalizeTimestampByHour(d: Date) {
return d.setMinutes(0, 0, 0);
}
@ -158,7 +156,7 @@ class StatsModel extends StatsClient {
* @param {Date} d - Date instance
* @return {number} timestamp - one hour prior to date passed
*/
_getDatePreviousHour(d) {
_getDatePreviousHour(d: Date) {
return d.setHours(d.getHours() - 1);
}
@ -167,8 +165,8 @@ class StatsModel extends StatsClient {
* @param {number} epoch - epoch time
* @return {array} array of sorted set key timestamps
*/
getSortedSetHours(epoch) {
const timestamps = [];
getSortedSetHours(epoch: number) {
const timestamps: number[] = [];
let date = this.normalizeTimestampByHour(new Date(epoch));
while (timestamps.length < 24) {
timestamps.push(date);
@ -182,7 +180,7 @@ class StatsModel extends StatsClient {
* @param {number} epoch - epoch time
* @return {string} normalized hour timestamp for given time
*/
getSortedSetCurrentHour(epoch) {
getSortedSetCurrentHour(epoch: number) {
return this.normalizeTimestampByHour(new Date(epoch));
}
@ -194,8 +192,8 @@ class StatsModel extends StatsClient {
* @param {callback} cb - callback
* @return {undefined}
*/
addToSortedSet(key, score, value, cb) {
this._redis.exists(key, (err, resCode) => {
addToSortedSet(key: string, score: number, value: string, cb) {
this._redis!.exists(key, (err, resCode) => {
if (err) {
return cb(err);
}
@ -204,8 +202,7 @@ class StatsModel extends StatsClient {
const msInADay = 24 * 60 * 60 * 1000;
const nearestHour = this.normalizeTimestampByHour(new Date());
// in seconds
const ttl = Math.ceil(
(msInADay - (Date.now() - nearestHour)) / 1000);
const ttl = Math.ceil((msInADay - (Date.now() - nearestHour)) / 1000);
const cmds = [
['zadd', key, score, value],
['expire', key, ttl],
@ -214,7 +211,7 @@ class StatsModel extends StatsClient {
if (err) {
return cb(err);
}
const cmdErr = res.find(r => r[0] !== null);
const cmdErr = res.find((r) => r[0] !== null);
if (cmdErr) {
return cb(cmdErr);
}
@ -222,9 +219,7 @@ class StatsModel extends StatsClient {
return cb(null, successResponse);
});
}
return this._redis.zadd(key, score, value, cb);
return this._redis!.zadd(key, score, value, cb);
});
}
}
module.exports = StatsModel;

View File

@ -1,13 +1,13 @@
const promClient = require('prom-client');
import promClient from 'prom-client';
const collectDefaultMetricsIntervalMs =
process.env.COLLECT_DEFAULT_METRICS_INTERVAL_MS !== undefined ?
Number.parseInt(process.env.COLLECT_DEFAULT_METRICS_INTERVAL_MS, 10) :
10000;
process.env.COLLECT_DEFAULT_METRICS_INTERVAL_MS !== undefined
? Number.parseInt(process.env.COLLECT_DEFAULT_METRICS_INTERVAL_MS, 10)
: 10000;
promClient.collectDefaultMetrics({ timeout: collectDefaultMetricsIntervalMs });
class ZenkoMetrics {
export default class ZenkoMetrics {
static createCounter(params) {
return new promClient.Counter(params);
}
@ -36,5 +36,3 @@ class ZenkoMetrics {
return promClient.register.contentType;
}
}
module.exports = ZenkoMetrics;

View File

@ -1,11 +1,17 @@
const errors = require('../errors');
import errors from '../errors';
const validServices = {
aws: ['s3', 'iam', 'sts', 'ring'],
scality: ['utapi', 'sso'],
};
class ARN {
export default class ARN {
_partition: string;
_service: string;
_region: string | null;
_accountId: string | null;
_resource: string;
/**
*
* Create an ARN object from its individual components
@ -17,7 +23,7 @@ class ARN {
* @param {string} [accountId] - AWS 12-digit account ID
* @param {string} resource - AWS resource path (e.g. 'foo/bar')
*/
constructor(partition, service, region, accountId, resource) {
constructor(partition: string, service: string, region: string, accountId: string, resource: string) {
this._partition = partition;
this._service = service;
this._region = region || null;
@ -25,9 +31,9 @@ class ARN {
this._resource = resource;
}
static createFromString(arnStr) {
static createFromString(arnStr: string) {
const [arn, partition, service, region, accountId,
resourceType, resource] = arnStr.split(':');
resourceType, resource] = arnStr.split(':');
if (arn !== 'arn') {
return { error: errors.InvalidArgument.customizeDescription(
@ -57,8 +63,8 @@ class ARN {
`bad ARN: bad account ID "${accountId}": ` +
'must be a 12-digit number or "*"') };
}
const fullResource = (resource !== undefined ?
`${resourceType}:${resource}` : resourceType);
const fullResource = resource !== undefined ?
`${resourceType}:${resource}` : resourceType;
return new ARN(partition, service, region, accountId, fullResource);
}
@ -79,28 +85,26 @@ class ARN {
}
isIAMAccount() {
return this.getService() === 'iam'
&& this.getAccountId() !== null
&& this.getAccountId() !== '*'
&& this.getResource() === 'root';
return this.getService() === 'iam' &&
this.getAccountId() !== null &&
this.getAccountId() !== '*' &&
this.getResource() === 'root';
}
isIAMUser() {
return this.getService() === 'iam'
&& this.getAccountId() !== null
&& this.getAccountId() !== '*'
&& this.getResource().startsWith('user/');
return this.getService() === 'iam' &&
this.getAccountId() !== null &&
this.getAccountId() !== '*' &&
this.getResource().startsWith('user/');
}
isIAMRole() {
return this.getService() === 'iam'
&& this.getAccountId() !== null
&& this.getResource().startsWith('role');
return this.getService() === 'iam' &&
this.getAccountId() !== null &&
this.getResource().startsWith('role');
}
toString() {
return ['arn', this.getPartition(), this.getService(),
this.getRegion(), this.getAccountId(), this.getResource()]
this.getRegion(), this.getAccountId(), this.getResource()]
.join(':');
}
}
module.exports = ARN;

View File

@ -1,7 +1,13 @@
const { legacyLocations } = require('../constants');
const escapeForXml = require('../s3middleware/escapeForXml');
import { legacyLocations } from '../constants';
import escapeForXml from '../s3middleware/escapeForXml';
export default class BackendInfo {
_config;
_objectLocationConstraint;
_bucketLocationConstraint;
_requestEndpoint;
_legacyLocationConstraint;
class BackendInfo {
/**
* Represents the info necessary to evaluate which data backend to use
* on a data put call.
@ -52,9 +58,9 @@ class BackendInfo {
*/
static isRequestEndpointPresent(config, requestEndpoint, log) {
if (Object.keys(config.restEndpoints).
indexOf(requestEndpoint) < 0) {
indexOf(requestEndpoint) < 0) {
log.trace('requestEndpoint does not match config restEndpoints',
{ requestEndpoint });
{ requestEndpoint });
return false;
}
return true;
@ -70,10 +76,10 @@ class BackendInfo {
*/
static isRequestEndpointValueValid(config, requestEndpoint, log) {
if (Object.keys(config.locationConstraints).
indexOf(config.restEndpoints[requestEndpoint]) < 0) {
indexOf(config.restEndpoints[requestEndpoint]) < 0) {
log.trace('the default locationConstraint for request' +
'Endpoint does not match any config locationConstraint',
{ requestEndpoint });
{ requestEndpoint });
return false;
}
return true;
@ -110,7 +116,7 @@ class BackendInfo {
*/
static isValidRequestEndpointOrBackend(config, requestEndpoint, log) {
if (!BackendInfo.isRequestEndpointPresent(config, requestEndpoint,
log)) {
log)) {
return BackendInfo.isMemOrFileBackend(config, log);
}
return BackendInfo.isRequestEndpointValueValid(config, requestEndpoint,
@ -132,7 +138,7 @@ class BackendInfo {
bucketLocationConstraint, requestEndpoint, log) {
if (objectLocationConstraint) {
if (BackendInfo.isValidLocationConstraint(config,
objectLocationConstraint, log)) {
objectLocationConstraint, log)) {
log.trace('objectLocationConstraint is valid');
return { isValid: true };
}
@ -143,7 +149,7 @@ class BackendInfo {
}
if (bucketLocationConstraint) {
if (BackendInfo.isValidLocationConstraint(config,
bucketLocationConstraint, log)) {
bucketLocationConstraint, log)) {
log.trace('bucketLocationConstraint is valid');
return { isValid: true };
}
@ -159,7 +165,7 @@ class BackendInfo {
return { isValid: true, legacyLocationConstraint };
}
if (!BackendInfo.isValidRequestEndpointOrBackend(config,
requestEndpoint, log)) {
requestEndpoint, log)) {
return { isValid: false, description: 'Endpoint Location Error - ' +
`Your endpoint "${requestEndpoint}" is not in restEndpoints ` +
'in your config OR the default location constraint for request ' +
@ -167,7 +173,7 @@ class BackendInfo {
'match any config locationConstraint - Please update.' };
}
if (BackendInfo.isRequestEndpointPresent(config, requestEndpoint,
log)) {
log)) {
return { isValid: true };
}
return { isValid: true, defaultedToDataBackend: true };
@ -236,5 +242,3 @@ class BackendInfo {
return this._config.backends.data;
}
}
module.exports = BackendInfo;

View File

@ -2,7 +2,9 @@
* Helper class to ease access to the Azure specific information for
* storage accounts mapped to buckets.
*/
class BucketAzureInfo {
export default class BucketAzureInfo {
_data
/**
* @constructor
* @param {object} obj - Raw structure for the Azure info on storage account
@ -233,5 +235,3 @@ class BucketAzureInfo {
return this._data;
}
}
module.exports = BucketAzureInfo;

View File

@ -1,19 +1,44 @@
const assert = require('assert');
const uuid = require('uuid/v4');
import assert from 'assert';
import uuid from 'uuid/v4';
const { WebsiteConfiguration } = require('./WebsiteConfiguration');
const ReplicationConfiguration = require('./ReplicationConfiguration');
const LifecycleConfiguration = require('./LifecycleConfiguration');
const ObjectLockConfiguration = require('./ObjectLockConfiguration');
const BucketPolicy = require('./BucketPolicy');
const NotificationConfiguration = require('./NotificationConfiguration');
import { WebsiteConfiguration } from './WebsiteConfiguration';
import ReplicationConfiguration from './ReplicationConfiguration';
import LifecycleConfiguration from './LifecycleConfiguration';
import ObjectLockConfiguration from './ObjectLockConfiguration';
import BucketPolicy from './BucketPolicy';
import NotificationConfiguration from './NotificationConfiguration';
// WHEN UPDATING THIS NUMBER, UPDATE BucketInfoModelVersion.md CHANGELOG
// BucketInfoModelVersion.md can be found in documentation/ at the root
// of this repository
const modelVersion = 14;
class BucketInfo {
export default class BucketInfo {
_acl;
_name;
_owner;
_ownerDisplayName;
_creationDate;
_mdBucketModelVersion;
_transient;
_deleted;
_serverSideEncryption;
_versioningConfiguration;
_locationConstraint;
_readLocationConstraint;
_websiteConfiguration;
_replicationConfiguration;
_cors;
_lifecycleConfiguration;
_bucketPolicy;
_uid;
_isNFS;
_ingestion;
_azureInfo;
_objectLockEnabled;
_objectLockConfiguration;
_notificationConfiguration;
/**
* Represents all bucket information.
* @constructor
@ -69,13 +94,13 @@ class BucketInfo {
* @param {object} [notificationConfiguration] - bucket notification configuration
*/
constructor(name, owner, ownerDisplayName, creationDate,
mdBucketModelVersion, acl, transient, deleted,
serverSideEncryption, versioningConfiguration,
locationConstraint, websiteConfiguration, cors,
replicationConfiguration, lifecycleConfiguration,
bucketPolicy, uid, readLocationConstraint, isNFS,
ingestionConfig, azureInfo, objectLockEnabled,
objectLockConfiguration, notificationConfiguration) {
mdBucketModelVersion, acl, transient, deleted,
serverSideEncryption, versioningConfiguration,
locationConstraint, websiteConfiguration, cors,
replicationConfiguration, lifecycleConfiguration,
bucketPolicy, uid, readLocationConstraint, isNFS,
ingestionConfig, azureInfo, objectLockEnabled,
objectLockConfiguration, notificationConfiguration) {
assert.strictEqual(typeof name, 'string');
assert.strictEqual(typeof owner, 'string');
assert.strictEqual(typeof ownerDisplayName, 'string');
@ -94,7 +119,7 @@ class BucketInfo {
if (serverSideEncryption) {
assert.strictEqual(typeof serverSideEncryption, 'object');
const { cryptoScheme, algorithm, masterKeyId,
configuredMasterKeyId, mandatory } = serverSideEncryption;
configuredMasterKeyId, mandatory } = serverSideEncryption;
assert.strictEqual(typeof cryptoScheme, 'number');
assert.strictEqual(typeof algorithm, 'string');
assert.strictEqual(typeof masterKeyId, 'string');
@ -768,5 +793,3 @@ class BucketInfo {
return this;
}
}
module.exports = BucketInfo;

View File

@ -1,7 +1,7 @@
const assert = require('assert');
import assert from 'assert';
const errors = require('../errors');
const { validateResourcePolicy } = require('../policy/policyValidator');
import errors from '../errors';
import { validateResourcePolicy } from '../policy/policyValidator';
/**
* Format of json policy:
@ -49,7 +49,10 @@ const objectActions = [
's3:PutObjectTagging',
];
class BucketPolicy {
export default class BucketPolicy {
_json
_policy
/**
* Create a Bucket Policy instance
* @param {string} json - the json policy
@ -75,8 +78,11 @@ class BucketPolicy {
*/
_getPolicy() {
if (!this._json || this._json === '') {
return { error: errors.MalformedPolicy.customizeDescription(
'request json is empty or undefined') };
return {
error: errors.MalformedPolicy.customizeDescription(
'request json is empty or undefined'
),
};
}
const validSchema = validateResourcePolicy(this._json);
if (validSchema.error) {
@ -104,25 +110,32 @@ class BucketPolicy {
* @return {error} - contains error or empty obj
*/
_validateActionResource() {
const invalid = this._policy.Statement.every(s => {
const actions = typeof s.Action === 'string' ?
[s.Action] : s.Action;
const resources = typeof s.Resource === 'string' ?
[s.Resource] : s.Resource;
const objectAction = actions.some(a =>
a.includes('Object') || objectActions.includes(a));
const invalid = this._policy.Statement.every((s) => {
const actions =
typeof s.Action === 'string' ? [s.Action] : s.Action;
const resources =
typeof s.Resource === 'string' ? [s.Resource] : s.Resource;
const objectAction = actions.some(
(a) => a.includes('Object') || objectActions.includes(a)
);
// wildcardObjectAction checks for actions such as 's3:*' or
// 's3:Put*' but will return false for actions such as
// 's3:PutBucket*'
const wildcardObjectAction = actions.some(
a => a.includes('*') && !a.includes('Bucket'));
const objectResource = resources.some(r => r.includes('/'));
return ((objectAction && !objectResource) ||
(objectResource && !objectAction && !wildcardObjectAction));
(a) => a.includes('*') && !a.includes('Bucket')
);
const objectResource = resources.some((r) => r.includes('/'));
return (
(objectAction && !objectResource) ||
(objectResource && !objectAction && !wildcardObjectAction)
);
});
if (invalid) {
return { error: errors.MalformedPolicy.customizeDescription(
'Action does not apply to any resource(s) in statement') };
return {
error: errors.MalformedPolicy.customizeDescription(
'Action does not apply to any resource(s) in statement'
),
};
}
return {};
}
@ -139,5 +152,3 @@ class BucketPolicy {
assert.deepStrictEqual(validated, { error: null, valid: true });
}
}
module.exports = BucketPolicy;

View File

@ -1,9 +1,9 @@
const assert = require('assert');
const UUID = require('uuid');
import assert from 'assert';
import UUID from 'uuid';
const errors = require('../errors');
const LifecycleRule = require('./LifecycleRule');
const escapeForXml = require('../s3middleware/escapeForXml');
import errors from '../errors';
import LifecycleRule from './LifecycleRule';
import escapeForXml from '../s3middleware/escapeForXml';
const MAX_DAYS = 2147483647; // Max 32-bit signed binary integer.
@ -83,7 +83,7 @@ const MAX_DAYS = 2147483647; // Max 32-bit signed binary integer.
};
*/
class LifecycleConfiguration {
export default class LifecycleConfiguration {
/**
* Create a Lifecycle Configuration instance
* @param {string} xml - the parsed xml
@ -381,7 +381,7 @@ class LifecycleConfiguration {
if (!tags[i].Key || !tags[i].Value) {
tagObj.error =
errors.MissingRequiredParameter.customizeDescription(
'Tag XML does not contain both Key and Value');
'Tag XML does not contain both Key and Value');
break;
}
@ -929,7 +929,7 @@ class LifecycleConfiguration {
const daysInt = parseInt(subExp.Days[0], 10);
if (daysInt < 1) {
expObj.error = errors.InvalidArgument.customizeDescription(
'Expiration days is not a positive integer');
'Expiration days is not a positive integer');
} else {
expObj.days = daysInt;
}
@ -1125,10 +1125,10 @@ class LifecycleConfiguration {
const { noncurrentDays, storageClass } = transition;
xml.push(
`<${actionName}>`,
`<NoncurrentDays>${noncurrentDays}` +
`<NoncurrentDays>${noncurrentDays}` +
'</NoncurrentDays>',
`<StorageClass>${storageClass}</StorageClass>`,
`</${actionName}>`
`<StorageClass>${storageClass}</StorageClass>`,
`</${actionName}>`,
);
});
Action = xml.join('');
@ -1146,9 +1146,9 @@ class LifecycleConfiguration {
}
xml.push(
`<${actionName}>`,
element,
`<StorageClass>${storageClass}</StorageClass>`,
`</${actionName}>`
element,
`<StorageClass>${storageClass}</StorageClass>`,
`</${actionName}>`,
);
});
Action = xml.join('');
@ -1220,5 +1220,3 @@ class LifecycleConfiguration {
return { Rules: rulesJSON };
}
}
module.exports = LifecycleConfiguration;

View File

@ -1,11 +1,11 @@
const uuid = require('uuid/v4');
import uuid from 'uuid/v4';
/**
* @class LifecycleRule
*
* @classdesc Simple get/set class to build a single Rule
*/
class LifecycleRule {
export default class LifecycleRule {
constructor(id, status) {
// defaults
this.id = id || uuid();
@ -134,5 +134,3 @@ class LifecycleRule {
return this;
}
}
module.exports = LifecycleRule;

View File

@ -1,11 +1,11 @@
const assert = require('assert');
const UUID = require('uuid');
import assert from 'assert';
import UUID from 'uuid';
const {
import {
supportedNotificationEvents,
notificationArnPrefix,
} = require('../constants');
const errors = require('../errors');
} from '../constants';
import errors from '../errors';
/**
* Format of xml request:
@ -27,7 +27,7 @@ const errors = require('../errors');
* </NotificationConfiguration>
*/
/**
/**
* Format of config:
*
* config = {
@ -51,7 +51,7 @@ const errors = require('../errors');
* }
*/
class NotificationConfiguration {
export default class NotificationConfiguration {
/**
* Create a Notification Configuration instance
* @param {string} xml - parsed configuration xml
@ -307,5 +307,3 @@ class NotificationConfiguration {
return;
}
}
module.exports = NotificationConfiguration;

View File

@ -1,6 +1,5 @@
const assert = require('assert');
const errors = require('../errors');
import assert from 'assert';
import errors from '../errors';
/**
* Format of xml request:
@ -17,7 +16,7 @@ const errors = require('../errors');
* </ObjectLockConfiguration>
*/
/**
/**
* Format of config:
*
* config = {
@ -27,7 +26,7 @@ const errors = require('../errors');
* }
* }
*/
class ObjectLockConfiguration {
export default class ObjectLockConfiguration {
/**
* Create an Object Lock Configuration instance
* @param {string} xml - the parsed configuration xml
@ -234,5 +233,3 @@ class ObjectLockConfiguration {
'</ObjectLockConfiguration>';
}
}
module.exports = ObjectLockConfiguration;

View File

@ -1,16 +1,15 @@
const crypto = require('crypto');
import * as crypto from 'crypto';
const constants = require('../constants');
const VersionIDUtils = require('../versioning/VersionID');
import * as constants from '../constants';
import * as VersionIDUtils from '../versioning/VersionID';
const ObjectMDLocation = require('./ObjectMDLocation');
import ObjectMDLocation from './ObjectMDLocation';
/**
* Class to manage metadata object for regular s3 objects (instead of
* mpuPart metadata for example)
*/
class ObjectMD {
export default class ObjectMD {
/**
* Create a new instance of ObjectMD. Parameter <tt>objMd</tt> is
* reserved for internal use, users should call
@ -149,7 +148,7 @@ class ObjectMD {
Object.assign(this._data, objMd._data);
Object.assign(this._data.replicationInfo,
objMd._data.replicationInfo);
objMd._data.replicationInfo);
}
_updateFromParsedJSON(objMd) {
@ -1190,5 +1189,3 @@ class ObjectMD {
return this._data;
}
}
module.exports = ObjectMD;

View File

@ -2,7 +2,7 @@
* Helper class to ease access to the Azure specific information for
* Blob and Container objects.
*/
class ObjectMDAzureInfo {
export default class ObjectMDAzureInfo {
/**
* @constructor
* @param {object} obj - Raw structure for the Azure info on Blob/Container
@ -158,5 +158,3 @@ class ObjectMDAzureInfo {
return this._data;
}
}
module.exports = ObjectMDAzureInfo;

View File

@ -2,8 +2,7 @@
* Helper class to ease access to a single data location in metadata
* 'location' array
*/
class ObjectMDLocation {
export default class ObjectMDLocation {
/**
* @constructor
* @param {object} locationObj - single data location info
@ -127,5 +126,3 @@ class ObjectMDLocation {
return this._data;
}
}
module.exports = ObjectMDLocation;

View File

@ -1,9 +1,9 @@
const assert = require('assert');
const UUID = require('uuid');
import assert from 'assert';
import UUID from 'uuid';
const escapeForXml = require('../s3middleware/escapeForXml');
const errors = require('../errors');
const { isValidBucketName } = require('../s3routes/routesUtils');
import escapeForXml from '../s3middleware/escapeForXml';
import errors from '../errors';
import { isValidBucketName } from '../s3routes/routesUtils';
const MAX_RULES = 1000;
const RULE_ID_LIMIT = 255;
@ -37,7 +37,19 @@ const validStorageClasses = [
</ReplicationConfiguration>
*/
class ReplicationConfiguration {
export default class ReplicationConfiguration {
_parsedXML
_log
_config
_configPrefixes
_configIDs
_role
_destination
_rules
_prevStorageClass
_hasScalityDestination
_preferredReadLocation
/**
* Create a ReplicationConfiguration instance
* @param {string} xml - The parsed XML
@ -469,5 +481,3 @@ class ReplicationConfiguration {
});
}
}
module.exports = ReplicationConfiguration;

View File

@ -1,4 +1,7 @@
class RoutingRule {
export class RoutingRule {
_redirect;
_condition;
/**
* Represents a routing rule in a website configuration.
* @constructor
@ -52,7 +55,12 @@ class RoutingRule {
}
}
class WebsiteConfiguration {
export class WebsiteConfiguration {
_indexDocument;
_errorDocument;
_redirectAllRequestsTo;
_routingRules;
/**
* Object that represents website configuration
* @constructor
@ -188,8 +196,3 @@ class WebsiteConfiguration {
return this._routingRules;
}
}
module.exports = {
RoutingRule,
WebsiteConfiguration,
};

View File

@ -17,7 +17,7 @@ function shuffle(array) {
}
}
class RoundRobin {
export default class RoundRobin {
/**
* @constructor
* @param {object[]|string[]} hostsList - list of hosts to query
@ -111,7 +111,7 @@ class RoundRobin {
pickHost() {
if (this.logger) {
this.logger.debug('pick host',
{ host: this.getCurrentHost() });
{ host: this.getCurrentHost() });
}
const curHost = this.getCurrentHost();
++this.pickCount;
@ -163,9 +163,7 @@ class RoundRobin {
}
if (this.logger) {
this.logger.debug('round robin host',
{ newHost: this.getCurrentHost() });
{ newHost: this.getCurrentHost() });
}
}
}
module.exports = RoundRobin;

View File

@ -1,16 +1,13 @@
'use strict'; // eslint-disable-line
const http = require('http');
const https = require('https');
const assert = require('assert');
const dhparam = require('../../https/dh2048').dhparam;
const ciphers = require('../../https/ciphers').ciphers;
const errors = require('../../errors');
const { checkSupportIPv6 } = require('./utils');
import * as http from 'http';
import * as https from 'https';
import assert from 'assert';
import { dhparam } from '../../https/dh2048';
import { ciphers } from '../../https/ciphers';
import errors from '../../errors';
import { checkSupportIPv6 } from './utils';
class Server {
export default class Server {
/**
* @constructor
*
@ -431,16 +428,16 @@ class Server {
// Setting no delay of the socket to the value configured
sock.setNoDelay(this.isNoDelay());
sock.on('error', err => this._logger.info(
'socket error - request rejected', { error: err }));
'socket error - request rejected', { error: err }));
});
this._server.on('tlsClientError', (err, sock) =>
this._onClientError(err, sock));
this._onClientError(err, sock));
this._server.on('clientError', (err, sock) =>
this._onClientError(err, sock));
this._onClientError(err, sock));
this._server.on('checkContinue', (req, res) =>
this._onCheckContinue(req, res));
this._onCheckContinue(req, res));
this._server.on('checkExpectation', (req, res) =>
this._onCheckExpectation(req, res));
this._onCheckExpectation(req, res));
this._server.on('listening', () => this._onListening());
}
this._server.listen(this._port, this._address);
@ -459,5 +456,3 @@ class Server {
return this;
}
}
module.exports = Server;

View File

@ -1,7 +1,5 @@
'use strict'; // eslint-disable-line
const os = require('os');
const errors = require('../../errors');
import * as os from 'os';
import errors from '../../errors';
/**
* Parse the Range header into an object
@ -16,7 +14,7 @@ const errors = require('../../errors');
* - an error attribute of type errors.InvalidArgument if the range
* syntax is invalid
*/
function parseRangeSpec(rangeHeader) {
export function parseRangeSpec(rangeHeader) {
const rangeMatch = /^bytes=([0-9]+)?-([0-9]+)?$/.exec(rangeHeader);
if (rangeMatch) {
const rangeValues = rangeMatch.slice(1, 3);
@ -55,7 +53,7 @@ function parseRangeSpec(rangeHeader) {
* - or an 'error' attribute of type errors.InvalidRange if the
* requested range is out of object's boundaries.
*/
function getByteRangeFromSpec(rangeSpec, objectSize) {
export function getByteRangeFromSpec(rangeSpec, objectSize) {
if (rangeSpec.suffix !== undefined) {
if (rangeSpec.suffix === 0) {
// 0-byte suffix is always invalid (even on empty objects)
@ -72,8 +70,8 @@ function getByteRangeFromSpec(rangeSpec, objectSize) {
if (rangeSpec.start < objectSize) {
// test is false if end is undefined
return { range: [rangeSpec.start,
(rangeSpec.end < objectSize ?
rangeSpec.end : objectSize - 1)] };
(rangeSpec.end < objectSize ?
rangeSpec.end : objectSize - 1)] };
}
return { error: errors.InvalidRange };
}
@ -95,7 +93,7 @@ function getByteRangeFromSpec(rangeSpec, objectSize) {
* - or an 'error' attribute instead of type errors.InvalidRange if
* the requested range is out of object's boundaries.
*/
function parseRange(rangeHeader, objectSize) {
export function parseRange(rangeHeader, objectSize) {
const rangeSpec = parseRangeSpec(rangeHeader);
if (rangeSpec.error) {
// invalid range syntax is silently ignored in HTTP spec,
@ -105,15 +103,8 @@ function parseRange(rangeHeader, objectSize) {
return getByteRangeFromSpec(rangeSpec, objectSize);
}
function checkSupportIPv6() {
export function checkSupportIPv6() {
const niList = os.networkInterfaces();
return Object.keys(niList).some(network =>
niList[network].some(intfc => intfc.family === 'IPv6'));
}
module.exports = {
parseRangeSpec,
getByteRangeFromSpec,
parseRange,
checkSupportIPv6,
};

View File

@ -1,12 +1,8 @@
'use strict'; // eslint-disable-line
/* eslint new-cap: "off" */
const async = require('async');
const errors = require('../../errors');
const TTLVCodec = require('./codec/ttlv.js');
const TlsTransport = require('./transport/tls.js');
const KMIP = require('.');
import async from 'async';
import errors from '../../errors';
import TTLVCodec from './codec/ttlv';
import TlsTransport from './transport/tls';
import KMIP from '.';
const CRYPTOGRAPHIC_OBJECT_TYPE = 'Symmetric Key';
const CRYPTOGRAPHIC_ALGORITHM = 'AES';
@ -55,7 +51,12 @@ function _arsenalError(err) {
if (typeof err === 'string') {
return errors.InternalError
.customizeDescription(`${messagePrefix} ${err}`);
} else if (err instanceof Error) {
} else if (
err instanceof Error ||
// INFO: The second part is here only for Jest, to remove when we'll be
// fully migrated to TS
(err && typeof err.message === 'string')
) {
return errors.InternalError
.customizeDescription(`${messagePrefix} ${err.message}`);
}
@ -90,8 +91,8 @@ function _negotiateProtocolVersion(client, logger, cb) {
if (err) {
const error = _arsenalError(err);
logger.error('KMIP::negotiateProtocolVersion',
{ error,
vendorIdentification: client.vendorIdentification });
{ error,
vendorIdentification: client.vendorIdentification });
return cb(error);
}
const majorVersions =
@ -102,8 +103,8 @@ function _negotiateProtocolVersion(client, logger, cb) {
majorVersions.length !== minorVersions.length) {
const error = _arsenalError('No suitable protocol version');
logger.error('KMIP::negotiateProtocolVersion',
{ error,
vendorIdentification: client.vendorIdentification });
{ error,
vendorIdentification: client.vendorIdentification });
return cb(error);
}
client.kmip.changeProtocolVersion(majorVersions[0], minorVersions[0]);
@ -126,8 +127,8 @@ function _mapExtensions(client, logger, cb) {
if (err) {
const error = _arsenalError(err);
logger.error('KMIP::mapExtensions',
{ error,
vendorIdentification: client.vendorIdentification });
{ error,
vendorIdentification: client.vendorIdentification });
return cb(error);
}
const extensionNames = response.lookup(searchFilter.extensionName);
@ -135,8 +136,8 @@ function _mapExtensions(client, logger, cb) {
if (extensionNames.length !== extensionTags.length) {
const error = _arsenalError('Inconsistent extension list');
logger.error('KMIP::mapExtensions',
{ error,
vendorIdentification: client.vendorIdentification });
{ error,
vendorIdentification: client.vendorIdentification });
return cb(error);
}
extensionNames.forEach((extensionName, idx) => {
@ -160,7 +161,7 @@ function _queryServerInformation(client, logger, cb) {
if (err) {
const error = _arsenalError(err);
logger.warn('KMIP::queryServerInformation',
{ error });
{ error });
/* no error returned, caller can keep going */
return cb();
}
@ -170,9 +171,9 @@ function _queryServerInformation(client, logger, cb) {
JSON.stringify(response.lookup(searchFilter.serverInformation)[0]));
logger.info('KMIP Server identified',
{ vendorIdentification: client.vendorIdentification,
serverInformation: client.serverInformation,
negotiatedProtocolVersion: client.kmip.protocolVersion });
{ vendorIdentification: client.vendorIdentification,
serverInformation: client.serverInformation,
negotiatedProtocolVersion: client.kmip.protocolVersion });
return cb();
});
}
@ -196,8 +197,8 @@ function _queryOperationsAndObjects(client, logger, cb) {
if (err) {
const error = _arsenalError(err);
logger.error('KMIP::queryOperationsAndObjects',
{ error,
vendorIdentification: client.vendorIdentification });
{ error,
vendorIdentification: client.vendorIdentification });
return cb(error);
}
const supportedOperations = response.lookup(searchFilter.operation);
@ -222,22 +223,21 @@ function _queryOperationsAndObjects(client, logger, cb) {
logger.warn('KMIP::queryOperationsAndObjects: ' +
'The KMIP Server announces that it ' +
'does not support all of the required features',
{ vendorIdentification: client.vendorIdentification,
serverInformation: client.serverInformation,
supportsEncrypt, supportsDecrypt,
supportsActivate, supportsRevoke,
supportsCreate, supportsDestroy,
supportsQuery, supportsSymmetricKeys });
{ vendorIdentification: client.vendorIdentification,
serverInformation: client.serverInformation,
supportsEncrypt, supportsDecrypt,
supportsActivate, supportsRevoke,
supportsCreate, supportsDestroy,
supportsQuery, supportsSymmetricKeys });
} else {
logger.info('KMIP Server provides the necessary feature set',
{ vendorIdentification: client.vendorIdentification });
{ vendorIdentification: client.vendorIdentification });
}
return cb();
});
}
class Client {
export default class Client {
/**
* Construct a high level KMIP driver suitable for cloudserver
* @param {Object} options - Instance options
@ -264,8 +264,8 @@ class Client {
this.vendorIdentification = '';
this.serverInformation = [];
this.kmip = new KMIP(CodecClass || TTLVCodec,
TransportClass || TlsTransport,
options);
TransportClass || TlsTransport,
options);
this.kmip.registerHandshakeFunction((logger, cb) => {
this._kmipHandshake(logger, cb);
});
@ -322,8 +322,8 @@ class Client {
if (err) {
const error = _arsenalError(err);
logger.error('KMIP::_activateBucketKey',
{ error,
serverInformation: this.serverInformation });
{ error,
serverInformation: this.serverInformation });
return cb(error);
}
const uniqueIdentifier =
@ -332,7 +332,7 @@ class Client {
const error = _arsenalError(
'Server did not return the expected identifier');
logger.error('KMIP::cipherDataKey',
{ error, uniqueIdentifier });
{ error, uniqueIdentifier });
return cb(error);
}
return cb(null, keyIdentifier);
@ -351,20 +351,20 @@ class Client {
const attributes = [];
if (!!this.options.bucketNameAttributeName) {
attributes.push(KMIP.Attribute('TextString',
this.options.bucketNameAttributeName,
bucketName));
this.options.bucketNameAttributeName,
bucketName));
}
attributes.push(...[
KMIP.Attribute('Enumeration', 'Cryptographic Algorithm',
CRYPTOGRAPHIC_ALGORITHM),
CRYPTOGRAPHIC_ALGORITHM),
KMIP.Attribute('Integer', 'Cryptographic Length',
CRYPTOGRAPHIC_LENGTH),
CRYPTOGRAPHIC_LENGTH),
KMIP.Attribute('Integer', 'Cryptographic Usage Mask',
this.kmip.encodeMask('Cryptographic Usage Mask',
CRYPTOGRAPHIC_USAGE_MASK))]);
this.kmip.encodeMask('Cryptographic Usage Mask',
CRYPTOGRAPHIC_USAGE_MASK))]);
if (this.options.compoundCreateActivate) {
attributes.push(KMIP.Attribute('Date-Time', 'Activation Date',
new Date(Date.UTC())));
new Date(Date.UTC())));
}
return this.kmip.request(logger, 'Create', [
@ -374,8 +374,8 @@ class Client {
if (err) {
const error = _arsenalError(err);
logger.error('KMIP::createBucketKey',
{ error,
serverInformation: this.serverInformation });
{ error,
serverInformation: this.serverInformation });
return cb(error);
}
const createdObjectType =
@ -386,7 +386,7 @@ class Client {
const error = _arsenalError(
'Server created an object of wrong type');
logger.error('KMIP::createBucketKey',
{ error, createdObjectType });
{ error, createdObjectType });
return cb(error);
}
if (!this.options.compoundCreateActivate) {
@ -411,16 +411,16 @@ class Client {
KMIP.TextString('Unique Identifier', bucketKeyId),
KMIP.Structure('Revocation Reason', [
KMIP.Enumeration('Revocation Reason Code',
'Cessation of Operation'),
'Cessation of Operation'),
KMIP.TextString('Revocation Message',
'About to be deleted'),
'About to be deleted'),
]),
], (err, response) => {
if (err) {
const error = _arsenalError(err);
logger.error('KMIP::_revokeBucketKey',
{ error,
serverInformation: this.serverInformation });
{ error,
serverInformation: this.serverInformation });
return cb(error);
}
const uniqueIdentifier =
@ -429,7 +429,7 @@ class Client {
const error = _arsenalError(
'Server did not return the expected identifier');
logger.error('KMIP::_revokeBucketKey',
{ error, uniqueIdentifier });
{ error, uniqueIdentifier });
return cb(error);
}
return cb();
@ -448,8 +448,8 @@ class Client {
if (err) {
const error = _arsenalError(err);
logger.error('KMIP::destroyBucketKey: revocation failed',
{ error,
serverInformation: this.serverInformation });
{ error,
serverInformation: this.serverInformation });
return cb(error);
}
return this.kmip.request(logger, 'Destroy', [
@ -458,8 +458,8 @@ class Client {
if (err) {
const error = _arsenalError(err);
logger.error('KMIP::destroyBucketKey',
{ error,
serverInformation: this.serverInformation });
{ error,
serverInformation: this.serverInformation });
return cb(error);
}
const uniqueIdentifier =
@ -468,7 +468,7 @@ class Client {
const error = _arsenalError(
'Server did not return the expected identifier');
logger.error('KMIP::destroyBucketKey',
{ error, uniqueIdentifier });
{ error, uniqueIdentifier });
return cb(error);
}
return cb();
@ -487,19 +487,19 @@ class Client {
* @callback called with (err, cipheredDataKey: Buffer)
*/
cipherDataKey(cryptoScheme,
masterKeyId,
plainTextDataKey,
logger,
cb) {
masterKeyId,
plainTextDataKey,
logger,
cb) {
return this.kmip.request(logger, 'Encrypt', [
KMIP.TextString('Unique Identifier', masterKeyId),
KMIP.Structure('Cryptographic Parameters', [
KMIP.Enumeration('Block Cipher Mode',
CRYPTOGRAPHIC_CIPHER_MODE),
CRYPTOGRAPHIC_CIPHER_MODE),
KMIP.Enumeration('Padding Method',
CRYPTOGRAPHIC_PADDING_METHOD),
CRYPTOGRAPHIC_PADDING_METHOD),
KMIP.Enumeration('Cryptographic Algorithm',
CRYPTOGRAPHIC_ALGORITHM),
CRYPTOGRAPHIC_ALGORITHM),
]),
KMIP.ByteString('Data', plainTextDataKey),
KMIP.ByteString('IV/Counter/Nonce', CRYPTOGRAPHIC_DEFAULT_IV),
@ -507,8 +507,8 @@ class Client {
if (err) {
const error = _arsenalError(err);
logger.error('KMIP::cipherDataKey',
{ error,
serverInformation: this.serverInformation });
{ error,
serverInformation: this.serverInformation });
return cb(error);
}
const uniqueIdentifier =
@ -518,7 +518,7 @@ class Client {
const error = _arsenalError(
'Server did not return the expected identifier');
logger.error('KMIP::cipherDataKey',
{ error, uniqueIdentifier });
{ error, uniqueIdentifier });
return cb(error);
}
return cb(null, data);
@ -536,19 +536,19 @@ class Client {
* @callback called with (err, plainTextDataKey: Buffer)
*/
decipherDataKey(cryptoScheme,
masterKeyId,
cipheredDataKey,
logger,
cb) {
masterKeyId,
cipheredDataKey,
logger,
cb) {
return this.kmip.request(logger, 'Decrypt', [
KMIP.TextString('Unique Identifier', masterKeyId),
KMIP.Structure('Cryptographic Parameters', [
KMIP.Enumeration('Block Cipher Mode',
CRYPTOGRAPHIC_CIPHER_MODE),
CRYPTOGRAPHIC_CIPHER_MODE),
KMIP.Enumeration('Padding Method',
CRYPTOGRAPHIC_PADDING_METHOD),
CRYPTOGRAPHIC_PADDING_METHOD),
KMIP.Enumeration('Cryptographic Algorithm',
CRYPTOGRAPHIC_ALGORITHM),
CRYPTOGRAPHIC_ALGORITHM),
]),
KMIP.ByteString('Data', cipheredDataKey),
KMIP.ByteString('IV/Counter/Nonce', CRYPTOGRAPHIC_DEFAULT_IV),
@ -556,8 +556,8 @@ class Client {
if (err) {
const error = _arsenalError(err);
logger.error('KMIP::decipherDataKey',
{ error,
serverInformation: this.serverInformation });
{ error,
serverInformation: this.serverInformation });
return cb(error);
}
const uniqueIdentifier =
@ -567,7 +567,7 @@ class Client {
const error = _arsenalError(
'Server did not return the right identifier');
logger.error('KMIP::decipherDataKey',
{ error, uniqueIdentifier });
{ error, uniqueIdentifier });
return cb(error);
}
return cb(null, data);
@ -599,5 +599,3 @@ class Client {
});
}
}
module.exports = Client;

View File

@ -1,6 +1,4 @@
'use strict'; // eslint-disable-line
const assert = require('assert');
import assert from 'assert';
function _lookup(decodedTTLV, path) {
@ -31,7 +29,7 @@ function _lookup(decodedTTLV, path) {
return res;
}
class Message {
export default class Message {
/**
* Construct a new abstract Message
* @param {Object} content - the content of the message
@ -50,5 +48,3 @@ class Message {
return _lookup(this.content, path);
}
}
module.exports = Message;

View File

@ -1,8 +1,5 @@
'use strict'; // eslint-disable-line
/* eslint dot-notation: "off" */
const KMIPTags = require('../tags.json');
const KMIPMessage = require('../Message.js');
import KMIPTags from '../tags.json';
import KMIPMessage from '../Message';
const UINT32_MAX = Math.pow(2, 32);
@ -26,7 +23,7 @@ function _throwError(logger, msg, data) {
throw Error(msg);
}
function TTLVCodec() {
export default function TTLVCodec() {
if (!new.target) {
return new TTLVCodec();
}
@ -55,15 +52,15 @@ function TTLVCodec() {
const property = {};
if (!TypeDecoder[elementType]) {
_throwError(logger,
'Unknown element type',
{ funcName, elementTag, elementType });
'Unknown element type',
{ funcName, elementTag, elementType });
}
const elementValue = value.slice(i + 8,
i + 8 + elementLength);
i + 8 + elementLength);
if (elementValue.length !== elementLength) {
_throwError(logger, 'BUG: Wrong buffer size',
{ funcName, elementLength,
bufferLength: elementValue.length });
{ funcName, elementLength,
bufferLength: elementValue.length });
}
property.type = TypeDecoder[elementType].name;
property.value = TypeDecoder[elementType]
@ -75,7 +72,7 @@ function TTLVCodec() {
const tagInfo = TagDecoder[elementTag];
if (!tagInfo) {
logger.debug('Unknown element tag',
{ funcName, elementTag });
{ funcName, elementTag });
property.tag = elementTag;
element['Unknown Tag'] = property;
} else {
@ -83,8 +80,8 @@ function TTLVCodec() {
if (tagInfo.name === 'Attribute Name') {
if (property.type !== 'TextString') {
_throwError(logger,
'Invalide type',
{ funcName, type: property.type });
'Invalide type',
{ funcName, type: property.type });
}
diversion = property.value;
}
@ -114,8 +111,8 @@ function TTLVCodec() {
}
const itemResult =
TypeEncoder[itemType].encode(itemTagName,
itemValue,
itemDiversion);
itemValue,
itemDiversion);
encodedValue = encodedValue
.concat(_ttlvPadVector(itemResult));
});
@ -133,9 +130,9 @@ function TTLVCodec() {
const fixedLength = 4;
if (fixedLength !== value.length) {
_throwError(logger,
'Length mismatch',
{ funcName, fixedLength,
bufferLength: value.length });
'Length mismatch',
{ funcName, fixedLength,
bufferLength: value.length });
}
return value.readUInt32BE(0);
},
@ -156,16 +153,16 @@ function TTLVCodec() {
const fixedLength = 8;
if (fixedLength !== value.length) {
_throwError(logger,
'Length mismatch',
{ funcName, fixedLength,
bufferLength: value.length });
'Length mismatch',
{ funcName, fixedLength,
bufferLength: value.length });
}
const longUInt = UINT32_MAX * value.readUInt32BE(0) +
value.readUInt32BE(4);
if (longUInt > Number.MAX_SAFE_INTEGER) {
_throwError(logger,
'53-bit overflow',
{ funcName, longUInt });
'53-bit overflow',
{ funcName, longUInt });
}
return longUInt;
},
@ -200,9 +197,9 @@ function TTLVCodec() {
const fixedLength = 4;
if (fixedLength !== value.length) {
_throwError(logger,
'Length mismatch',
{ funcName, fixedLength,
bufferLength: value.length });
'Length mismatch',
{ funcName, fixedLength,
bufferLength: value.length });
}
const enumValue = value.toString('hex');
const actualTag = diversion ? TagEncoder[diversion].value : tag;
@ -211,10 +208,10 @@ function TTLVCodec() {
!enumInfo.enumeration ||
!enumInfo.enumeration[enumValue]) {
return { tag,
value: enumValue,
message: 'Unknown enumeration value',
diversion,
};
value: enumValue,
message: 'Unknown enumeration value',
diversion,
};
}
return enumInfo.enumeration[enumValue];
},
@ -227,7 +224,7 @@ function TTLVCodec() {
const actualTag = diversion || tagName;
const encodedValue =
Buffer.from(TagEncoder[actualTag].enumeration[value],
'hex');
'hex');
return _ttlvPadVector([tag, type, length, encodedValue]);
},
},
@ -238,9 +235,9 @@ function TTLVCodec() {
const fixedLength = 8;
if (fixedLength !== value.length) {
_throwError(logger,
'Length mismatch',
{ funcName, fixedLength,
bufferLength: value.length });
'Length mismatch',
{ funcName, fixedLength,
bufferLength: value.length });
}
const msUInt = value.readUInt32BE(0);
const lsUInt = value.readUInt32BE(4);
@ -267,7 +264,7 @@ function TTLVCodec() {
const length = Buffer.alloc(4);
length.writeUInt32BE(value.length);
return _ttlvPadVector([tag, type, length,
Buffer.from(value, 'utf8')]);
Buffer.from(value, 'utf8')]);
},
},
'08': {
@ -289,17 +286,17 @@ function TTLVCodec() {
const fixedLength = 8;
if (fixedLength !== value.length) {
_throwError(logger,
'Length mismatch',
{ funcName, fixedLength,
bufferLength: value.length });
'Length mismatch',
{ funcName, fixedLength,
bufferLength: value.length });
}
const d = new Date(0);
const utcSeconds = UINT32_MAX * value.readUInt32BE(0) +
value.readUInt32BE(4);
if (utcSeconds > Number.MAX_SAFE_INTEGER) {
_throwError(logger,
'53-bit overflow',
{ funcName, utcSeconds });
'53-bit overflow',
{ funcName, utcSeconds });
}
d.setUTCSeconds(utcSeconds);
return d;
@ -323,9 +320,9 @@ function TTLVCodec() {
const fixedLength = 4;
if (fixedLength !== value.length) {
_throwError(logger,
'Length mismatch',
{ funcName, fixedLength,
bufferLength: value.length });
'Length mismatch',
{ funcName, fixedLength,
bufferLength: value.length });
}
return value.readInt32BE(0);
},
@ -415,8 +412,8 @@ function TTLVCodec() {
throw Error(`Unknown Type '${type}'`);
}
const itemValue = TypeEncoder[type].encode(key,
item[key].value,
item[key].diversion);
item[key].value,
item[key].diversion);
result = result.concat(_ttlvPadVector(itemValue));
});
});
@ -430,5 +427,3 @@ function TTLVCodec() {
};
return this;
}
module.exports = TTLVCodec;

View File

@ -1,9 +1,6 @@
'use strict'; // eslint-disable-line
/* eslint new-cap: "off" */
import uuidv4 from 'uuid/v4';
const uuidv4 = require('uuid/v4');
const Message = require('./Message.js');
import Message from './Message';
/* This client requires at least a KMIP 1.2 compatible server */
const DEFAULT_PROTOCOL_VERSION_MAJOR = 1;
@ -22,7 +19,7 @@ function _PrimitiveType(tagName, type, value) {
return { [tagName]: { type, value } };
}
class KMIP {
export default class KMIP {
/**
* Construct a new KMIP Object
* @param {Class} Codec -
@ -275,11 +272,11 @@ class KMIP {
KMIP.Structure('Request Header', [
KMIP.Structure('Protocol Version', [
KMIP.Integer('Protocol Version Major',
this.protocolVersion.major),
this.protocolVersion.major),
KMIP.Integer('Protocol Version Minor',
this.protocolVersion.minor)]),
this.protocolVersion.minor)]),
KMIP.Integer('Maximum Response Size',
this.maximumResponseSize),
this.maximumResponseSize),
KMIP.Integer('Batch Count', 1)]),
KMIP.Structure('Batch Item', [
KMIP.Enumeration('Operation', operation),
@ -292,7 +289,7 @@ class KMIP {
(err, conversation, rawResponse) => {
if (err) {
logger.error('KMIP::request: Failed to send message',
{ error: err });
{ error: err });
return cb(err);
}
const response = this._decodeMessage(logger, rawResponse);
@ -311,16 +308,16 @@ class KMIP {
this.transport.abortPipeline(conversation);
const error = Error('Invalid batch item ID returned');
logger.error('KMIP::request: failed',
{ resultUniqueBatchItemID, uuid, error });
{ resultUniqueBatchItemID, uuid, error });
return cb(error);
}
if (performedOperation !== operation) {
this.transport.abortPipeline(conversation);
const error = Error('Operation mismatch',
{ got: performedOperation,
expected: operation });
{ got: performedOperation,
expected: operation });
logger.error('KMIP::request: Operation mismatch',
{ error });
{ error });
return cb(error);
}
if (resultStatus !== 'Success') {
@ -331,20 +328,15 @@ class KMIP {
response.lookup(
'Response Message/Batch Item/Result Message')[0];
const error = Error('KMIP request failure',
{ resultStatus,
resultReason,
resultMessage });
{ resultStatus,
resultReason,
resultMessage });
logger.error('KMIP::request: request failed',
{ error, resultStatus,
resultReason, resultMessage });
{ error, resultStatus,
resultReason, resultMessage });
return cb(error);
}
return cb(null, response);
});
}
}
module.exports = KMIP;

View File

@ -1,11 +1,9 @@
'use strict'; // eslint-disable-line
const assert = require('assert');
import assert from 'assert';
const DEFAULT_PIPELINE_DEPTH = 8;
const DEFAULT_KMIP_PORT = 5696;
class TransportTemplate {
export default class TransportTemplate {
/**
* Construct a new object of the TransportTemplate class
* @param {Object} channel - Typically the tls object
@ -86,8 +84,8 @@ class TransportTemplate {
const deferedRequest = this.deferedRequests.shift();
process.nextTick(() => {
this.send(logger,
deferedRequest.encodedMessage,
deferedRequest.cb);
deferedRequest.encodedMessage,
deferedRequest.cb);
});
} else if (this.callbackPipeline.length === 0 &&
this.deferedRequests.length === 0 &&
@ -170,5 +168,3 @@ class TransportTemplate {
conversation.end();
}
}
module.exports = TransportTemplate;

View File

@ -1,5 +1,3 @@
'use strict'; // eslint-disable-line
const tls = require('tls');
const TransportTemplate = require('./TransportTemplate.js');
@ -9,4 +7,4 @@ class TlsTransport extends TransportTemplate {
}
}
module.exports = TlsTransport;
module.exports = TlsTra

View File

@ -1,14 +1,14 @@
const httpServer = require('../http/server');
const werelogs = require('werelogs');
const errors = require('../../errors');
const ZenkoMetrics = require('../../metrics/ZenkoMetrics');
const { sendSuccess, sendError } = require('./Utils');
import httpServer from '../http/server';
import werelogs from 'werelogs';
import errors from '../../errors';
import ZenkoMetrics from '../../metrics/ZenkoMetrics';
import { sendSuccess, sendError } from './Utils';
function checkStub(log) { // eslint-disable-line
return true;
}
class HealthProbeServer extends httpServer {
export default class HealthProbeServer extends httpServer {
constructor(params) {
const logging = new werelogs.Logger('HealthProbeServer');
super(params.port, logging);
@ -72,5 +72,3 @@ class HealthProbeServer extends httpServer {
res.end(ZenkoMetrics.asPrometheus());
}
}
module.exports = HealthProbeServer;

View File

@ -1,10 +1,10 @@
const httpServer = require('../http/server');
const werelogs = require('werelogs');
const errors = require('../../errors');
import httpServer from '../http/server';
import werelogs from 'werelogs';
import errors from '../../errors';
const DEFAULT_LIVE_ROUTE = '/_/live';
const DEFAULT_READY_ROUTE = '/_/ready';
const DEFAULT_METRICS_ROUTE = '/metrics';
export const DEFAULT_LIVE_ROUTE = '/_/live';
export const DEFAULT_READY_ROUTE = '/_/ready';
export const DEFAULT_METRICS_ROUTE = '/metrics';
/**
* ProbeDelegate is used to handle probe checks.
@ -28,7 +28,7 @@ const DEFAULT_METRICS_ROUTE = '/metrics';
*
* @extends {httpServer}
*/
class ProbeServer extends httpServer {
export class ProbeServer extends httpServer {
/**
* Create a new ProbeServer with parameters
*
@ -92,10 +92,3 @@ class ProbeServer extends httpServer {
this._handlers.get(req.url)(res, log);
}
}
module.exports = {
ProbeServer,
DEFAULT_LIVE_ROUTE,
DEFAULT_READY_ROUTE,
DEFAULT_METRICS_ROUTE,
};

View File

@ -5,7 +5,7 @@
* @param {string} [message] - Message to send as response, defaults to OK
* @returns {undefined}
*/
function sendSuccess(res, log, message = 'OK') {
export function sendSuccess(res, log, message = 'OK') {
log.debug('replying with success');
res.writeHead(200);
res.end(message);
@ -19,14 +19,14 @@ function sendSuccess(res, log, message = 'OK') {
* @param {string} [optMessage] - Message to use instead of the errors message
* @returns {undefined}
*/
function sendError(res, log, error, optMessage) {
export function sendError(res, log, error, optMessage) {
const message = optMessage || error.description || '';
log.debug('sending back error response',
{
httpCode: error.code,
errorType: error.message,
error: message,
}
},
);
res.writeHead(error.code);
res.end(JSON.stringify({
@ -34,8 +34,3 @@ function sendError(res, log, error, optMessage) {
errorMessage: message,
}));
}
module.exports = {
sendSuccess,
sendError,
};

View File

@ -1,12 +1,10 @@
'use strict'; // eslint-disable-line
import assert from 'assert';
import http from 'http';
import werelogs from 'werelogs';
const assert = require('assert');
const http = require('http');
const werelogs = require('werelogs');
const constants = require('../../constants');
const utils = require('./utils');
const errors = require('../../errors');
import * as constants from '../../constants';
import * as utils from './utils';
import errors from '../../errors';
const HttpAgent = require('agentkeepalive');
@ -64,7 +62,7 @@ function makeErrorFromHTTPResponse(response) {
*
* The API is usable when the object is constructed.
*/
class RESTClient {
export default class RESTClient {
/**
* Interface to the data file server
* @constructor
@ -311,5 +309,3 @@ class RESTClient {
* @callback RESTClient~deleteCallback
* @param {Error} - The encountered error
*/
module.exports = RESTClient;

View File

@ -1,15 +1,13 @@
'use strict'; // eslint-disable-line
import assert from 'assert';
import url from 'url';
const assert = require('assert');
const url = require('url');
import werelogs from 'werelogs';
const werelogs = require('werelogs');
const httpServer = require('../http/server');
const constants = require('../../constants');
const { parseURL } = require('./utils');
const httpUtils = require('../http/utils');
const errors = require('../../errors');
import httpServer from '../http/server';
import * as constants from '../../constants';
import { parseURL } from './utils';
import * as httpUtils from '../http/utils';
import errors from '../../errors';
function setContentLength(response, contentLength) {
response.setHeader('Content-Length', contentLength.toString());
@ -19,7 +17,7 @@ function setContentRange(response, byteRange, objectSize) {
const [start, end] = byteRange;
assert(start !== undefined && end !== undefined);
response.setHeader('Content-Range',
`bytes ${start}-${end}/${objectSize}`);
`bytes ${start}-${end}/${objectSize}`);
}
function sendError(res, log, error, optMessage) {
@ -44,8 +42,7 @@ function sendError(res, log, error, optMessage) {
* You have to call setup() to initialize the storage backend, then
* start() to start listening to the configured port.
*/
class RESTServer extends httpServer {
export default class RESTServer extends httpServer {
/**
* @constructor
* @param {Object} params - constructor params
@ -227,7 +224,7 @@ class RESTServer extends httpServer {
return sendError(res, log, err);
}
log.debug('sending back 200/206 response with contents',
{ key: pathInfo.key });
{ key: pathInfo.key });
setContentLength(res, contentLength);
res.setHeader('Accept-Ranges', 'bytes');
if (byteRange) {
@ -265,7 +262,7 @@ class RESTServer extends httpServer {
return sendError(res, log, err);
}
log.debug('sending back 204 response to DELETE',
{ key: pathInfo.key });
{ key: pathInfo.key });
res.writeHead(204);
return res.end(() => {
log.debug('DELETE response sent', { key: pathInfo.key });
@ -274,5 +271,3 @@ class RESTServer extends httpServer {
return undefined;
}
}
module.exports = RESTServer;

View File

@ -1,12 +1,10 @@
'use strict'; // eslint-disable-line
const errors = require('../../errors');
const constants = require('../../constants');
const url = require('url');
import errors from '../../errors';
import * as constants from '../../constants';
import * as url from 'url';
const passthroughPrefixLength = constants.passthroughFileURL.length;
function explodePath(path) {
export function explodePath(path) {
if (path.startsWith(constants.passthroughFileURL)) {
const key = path.slice(passthroughPrefixLength + 1);
return {
@ -19,7 +17,7 @@ function explodePath(path) {
return {
service: pathMatch[1],
key: (pathMatch[3] !== undefined && pathMatch[3].length > 0 ?
pathMatch[3] : undefined),
pathMatch[3] : undefined),
};
}
throw errors.InvalidURI.customizeDescription('malformed URI');
@ -37,7 +35,7 @@ function explodePath(path) {
* - pathInfo.service {String} - The name of REST service ("DataFile")
* - pathInfo.key {String} - The requested key
*/
function parseURL(urlStr, expectKey) {
export function parseURL(urlStr, expectKey) {
const urlObj = url.parse(urlStr);
const pathInfo = explodePath(decodeURI(urlObj.path));
if ((pathInfo.service !== constants.dataFileURL)
@ -61,8 +59,3 @@ function parseURL(urlStr, expectKey) {
}
return pathInfo;
}
module.exports = {
explodePath,
parseURL,
};

View File

@ -1,8 +1,6 @@
'use strict'; // eslint-disable-line
import assert from 'assert';
const assert = require('assert');
const rpc = require('./rpc.js');
import * as rpc from './rpc';
/**
* @class
@ -17,7 +15,6 @@ const rpc = require('./rpc.js');
* RPC client object accessing the sub-level transparently.
*/
class LevelDbClient extends rpc.BaseClient {
/**
* @constructor
*
@ -78,7 +75,6 @@ class LevelDbClient extends rpc.BaseClient {
* env.subDb (env is passed as first parameter of received RPC calls).
*/
class LevelDbService extends rpc.BaseService {
/**
* @constructor
*

View File

@ -1,17 +1,14 @@
'use strict'; // eslint-disable-line
import http from 'http';
import io from 'socket.io';
import ioClient from 'socket.io-client';
import * as sioStream from './sio-stream';
import async from 'async';
import assert from 'assert';
import { EventEmitter } from 'events';
const http = require('http');
const io = require('socket.io');
const ioClient = require('socket.io-client');
const sioStream = require('./sio-stream');
const async = require('async');
const assert = require('assert');
const EventEmitter = require('events').EventEmitter;
const flattenError = require('./utils').flattenError;
const reconstructError = require('./utils').reconstructError;
const errors = require('../../errors');
const jsutil = require('../../jsutil');
import { flattenError, reconstructError } from './utils';
import errors from '../../errors';
import * as jsutil from '../../jsutil';
const DEFAULT_CALL_TIMEOUT_MS = 30000;
@ -36,8 +33,7 @@ let streamRPCJSONObj;
* - the return value is passed as callback's second argument (unless
* an error occurred).
*/
class BaseClient extends EventEmitter {
export class BaseClient extends EventEmitter {
/**
* @constructor
*
@ -54,7 +50,7 @@ class BaseClient extends EventEmitter {
*/
constructor(params) {
const { url, logger, callTimeoutMs,
streamMaxPendingAck, streamAckTimeoutMs } = params;
streamMaxPendingAck, streamAckTimeoutMs } = params;
assert(url);
assert(logger);
@ -82,11 +78,11 @@ class BaseClient extends EventEmitter {
_call(remoteCall, args, cb) {
const wrapCb = (err, data) => {
cb(reconstructError(err),
this.socketStreams.decodeStreams(data));
this.socketStreams.decodeStreams(data));
};
this.logger.debug('remote call', { remoteCall, args });
this.socket.emit('call', remoteCall,
this.socketStreams.encodeStreams(args), wrapCb);
this.socketStreams.encodeStreams(args), wrapCb);
return undefined;
}
@ -113,8 +109,8 @@ class BaseClient extends EventEmitter {
throw new Error(`argument cb=${cb} is not a callback`);
}
async.timeout(this._call.bind(this), timeoutMs,
`operation ${remoteCall} timed out`)(remoteCall,
args, cb);
`operation ${remoteCall} timed out`)(remoteCall,
args, cb);
return undefined;
}
@ -142,7 +138,7 @@ class BaseClient extends EventEmitter {
const url = this.url;
this.socket.on('error', err => {
this.logger.warn('connectivity error to the RPC service',
{ url, error: err });
{ url, error: err });
});
this.socket.on('connect', () => {
this.emit('connect');
@ -156,7 +152,7 @@ class BaseClient extends EventEmitter {
this.getManifest((err, manifest) => {
if (err) {
this.logger.error('Error fetching manifest from RPC server',
{ error: err });
{ error: err });
} else {
manifest.api.forEach(apiItem => {
this.createCall(apiItem.name);
@ -250,8 +246,7 @@ class BaseClient extends EventEmitter {
* method.
*
*/
class BaseService {
export class BaseService {
/**
* @constructor
*
@ -469,7 +464,8 @@ class BaseService {
* @return {Object} a server object, not yet listening on a TCP port
* (you must call listen(port) on the returned object)
*/
function RPCServer(params) {
export function RPCServer(params) {
assert(params.logger);
const httpServer = http.createServer();
@ -497,7 +493,7 @@ function RPCServer(params) {
conn.on('error', err => {
log.error('error on socket.io connection',
{ namespace: service.namespace, error: err });
{ namespace: service.namespace, error: err });
});
conn.on('call', (remoteCall, args, cb) => {
const decodedArgs = streamsSocket.decodeStreams(args);
@ -647,8 +643,8 @@ streamRPCJSONObj = function _streamRPCJSONObj(obj, wstream, cb) {
// primitive types
if (obj === undefined) {
wstream.write('null'); // if undefined elements are present in
// arrays, convert them to JSON null
// objects
// arrays, convert them to JSON null
// objects
} else {
wstream.write(JSON.stringify(obj));
}
@ -668,7 +664,7 @@ streamRPCJSONObj = function _streamRPCJSONObj(obj, wstream, cb) {
* @return {Object} a HTTP server object, not yet listening on a TCP
* port (you must call listen(port) on the returned object)
*/
function RESTServer(params) {
export function RESTServer(params) {
assert(params);
assert(params.logger);
const httpServer = http.createServer((req, res) => {
@ -739,11 +735,3 @@ function RESTServer(params) {
return httpServer;
}
module.exports = {
BaseClient,
BaseService,
RPCServer,
RESTServer,
};

View File

@ -1,13 +1,11 @@
'use strict'; // eslint-disable-line
import uuid from 'uuid';
import stream from 'stream';
import debug_ from 'debug';
import assert from 'assert';
import async from 'async';
import { flattenError, reconstructError } from './utils';
const debug = debug_('sio-stream');
const uuid = require('uuid');
const stream = require('stream');
const debug = require('debug')('sio-stream');
const assert = require('assert');
const async = require('async');
const flattenError = require('./utils').flattenError;
const reconstructError = require('./utils').reconstructError;
const DEFAULT_MAX_PENDING_ACK = 4;
const DEFAULT_ACK_TIMEOUT_MS = 5000;
@ -16,7 +14,7 @@ class SIOOutputStream extends stream.Writable {
constructor(socket, streamId, maxPendingAck, ackTimeoutMs) {
super({ objectMode: true });
this._initOutputStream(socket, streamId, maxPendingAck,
ackTimeoutMs);
ackTimeoutMs);
}
_initOutputStream(socket, streamId, maxPendingAck, ackTimeoutMs) {
@ -194,7 +192,7 @@ class SIOStreamSocket {
this.socket.on('stream-data', (payload, cb) => {
const { streamId, data } = payload;
log.debug('received \'stream-data\' event',
{ streamId, size: data.length });
{ streamId, size: data.length });
const stream = this.remoteStreams[streamId];
if (!stream) {
log.debug('no such remote stream registered', { streamId });
@ -280,15 +278,15 @@ class SIOStreamSocket {
let transportStream;
if (isReadStream) {
transportStream = new SIOOutputStream(this, streamId,
this.maxPendingAck,
this.ackTimeoutMs);
this.maxPendingAck,
this.ackTimeoutMs);
} else {
transportStream = new SIOInputStream(this, streamId);
}
this.localStreams[streamId] = arg;
arg.once('close', () => {
log.debug('stream closed, removing from local streams',
{ streamId });
{ streamId });
delete this.localStreams[streamId];
});
arg.on('error', error => {
@ -350,8 +348,8 @@ class SIOStreamSocket {
stream = new SIOInputStream(this, streamId);
} else if (arg.writable) {
stream = new SIOOutputStream(this, streamId,
this.maxPendingAck,
this.ackTimeoutMs);
this.maxPendingAck,
this.ackTimeoutMs);
} else {
throw new Error('can\'t decode stream neither readable ' +
'nor writable');
@ -360,14 +358,14 @@ class SIOStreamSocket {
if (arg.readable) {
stream.once('close', () => {
log.debug('stream closed, removing from remote streams',
{ streamId });
{ streamId });
delete this.remoteStreams[streamId];
});
}
if (arg.writable) {
stream.once('finish', () => {
log.debug('stream finished, removing from remote streams',
{ streamId });
{ streamId });
delete this.remoteStreams[streamId];
});
}
@ -399,7 +397,7 @@ class SIOStreamSocket {
_write(streamId, data, cb) {
this.logger.debug('emit \'stream-data\' event',
{ streamId, size: data.length });
{ streamId, size: data.length });
this.socket.emit('stream-data', { streamId, data }, cb);
}
@ -433,7 +431,7 @@ class SIOStreamSocket {
}
}
module.exports.createSocket = function createSocket(
export function createSocket(
socket,
logger,
maxPendingAck = DEFAULT_MAX_PENDING_ACK,

View File

@ -1,5 +1,3 @@
'use strict'; // eslint-disable-line
/**
* @brief turn all <tt>err</tt> own and prototype attributes into own attributes
*
@ -9,7 +7,7 @@
* @param {Error} err error object
* @return {Object} flattened object containing <tt>err</tt> attributes
*/
module.exports.flattenError = function flattenError(err) {
export function flattenError(err) {
if (!err) {
return err;
}
@ -35,7 +33,7 @@ module.exports.flattenError = function flattenError(err) {
* @return {Error} a reconstructed Error object inheriting <tt>err</tt>
* attributes
*/
module.exports.reconstructError = function reconstructError(err) {
export function reconstructError(err) {
if (!err) {
return err;
}

View File

@ -1,9 +1,7 @@
'use strict'; // eslint-disable-line strict
import { URL } from 'url';
import { decryptSecret } from '../executables/pensieveCreds/utils';
const { URL } = require('url');
const { decryptSecret } = require('../executables/pensieveCreds/utils');
function patchLocations(overlayLocations, creds, log) {
export function patchLocations(overlayLocations, creds, log) {
if (!overlayLocations) {
return {};
}
@ -153,7 +151,3 @@ function patchLocations(overlayLocations, creds, log) {
});
return locations;
}
module.exports = {
patchLocations,
};

View File

@ -1,9 +1,7 @@
'use strict'; // eslint-disable-line strict
const Ajv = require('ajv');
const userPolicySchema = require('./userPolicySchema');
const resourcePolicySchema = require('./resourcePolicySchema');
const errors = require('../errors');
import Ajv from 'ajv';
import userPolicySchema from './userPolicySchema.json';
import resourcePolicySchema from './resourcePolicySchema.json';
import errors from '../errors';
const ajValidate = new Ajv({ allErrors: true });
ajValidate.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'));
@ -117,7 +115,7 @@ function _validatePolicy(type, policy) {
* @returns {Object} - returns object with properties error and value
* @returns {ValidationResult} - result of the validation
*/
function validateUserPolicy(policy) {
export function validateUserPolicy(policy) {
return _validatePolicy('user', policy);
}
@ -127,11 +125,6 @@ function validateUserPolicy(policy) {
* @returns {Object} - returns object with properties error and value
* @returns {ValidationResult} - result of the validation
*/
function validateResourcePolicy(policy) {
export function validateResourcePolicy(policy) {
return _validatePolicy('resource', policy);
}
module.exports = {
validateUserPolicy,
validateResourcePolicy,
};

View File

@ -1,19 +1,17 @@
'use strict'; // eslint-disable-line strict
const parseIp = require('../ipCheck').parseIp;
import { parseIp } from '../ipCheck';
// http://docs.aws.amazon.com/IAM/latest/UserGuide/list_s3.html
// For MPU actions:
// http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html
// For bucket head and object head:
// http://docs.aws.amazon.com/AmazonS3/latest/dev/
// using-with-s3-actions.html
const {
import {
actionMapRQ,
actionMapIAM,
actionMapSSO,
actionMapSTS,
actionMapMetadata,
} = require('./utils/actionMaps');
} from './utils/actionMaps';
const _actionNeedQuotaCheck = {
objectPut: true,
@ -129,7 +127,7 @@ function _buildArn(service, generalResource, specificResource, requesterInfo) {
* @return {RequestContext} a RequestContext instance
*/
class RequestContext {
export default class RequestContext {
constructor(headers, query, generalResource, specificResource,
requesterIp, sslEnabled, apiMethod,
awsService, locationConstraint, requesterInfo,
@ -648,5 +646,3 @@ class RequestContext {
return this._needTagEval;
}
}
module.exports = RequestContext;

View File

@ -1,14 +1,8 @@
'use strict'; // eslint-disable-line strict
const substituteVariables = require('./utils/variables.js');
const handleWildcards = require('./utils/wildcards.js').handleWildcards;
const conditions = require('./utils/conditions.js');
const findConditionKey = conditions.findConditionKey;
const convertConditionOperator = conditions.convertConditionOperator;
const checkArnMatch = require('./utils/checkArnMatch.js');
const { transformTagKeyValue } = require('./utils/objectTags');
const evaluators = {};
import { substituteVariables } from './utils/variables';
import { handleWildcards } from './utils/wildcards';
import { findConditionKey, convertConditionOperator } from './utils/conditions.js';
import checkArnMatch from './utils/checkArnMatch.js';
import { transformTagKeyValue } from './utils/objectTags';
const operatorsWithVariables = ['StringEquals', 'StringNotEquals',
'StringEqualsIgnoreCase', 'StringNotEqualsIgnoreCase',
@ -28,7 +22,7 @@ const tagConditions = new Set(['s3:ExistingObjectTag', 's3:RequestObjectTagKey',
* @param {object} log - logger
* @return {boolean} true if applicable, false if not
*/
evaluators.isResourceApplicable = (requestContext, statementResource, log) => {
export function isResourceApplicable(requestContext, statementResource, log) {
const resource = requestContext.getResource();
if (!Array.isArray(statementResource)) {
// eslint-disable-next-line no-param-reassign
@ -50,7 +44,7 @@ evaluators.isResourceApplicable = (requestContext, statementResource, log) => {
requestResourceArr, true);
if (arnSegmentsMatch) {
log.trace('policy resource is applicable to request',
{ requestResource: resource, policyResource });
{ requestResource: resource, policyResource });
return true;
}
continue;
@ -69,7 +63,7 @@ evaluators.isResourceApplicable = (requestContext, statementResource, log) => {
* @param {Object} log - logger
* @return {boolean} true if applicable, false if not
*/
evaluators.isActionApplicable = (requestAction, statementAction, log) => {
export function isActionApplicable(requestAction, statementAction, log) {
if (!Array.isArray(statementAction)) {
// eslint-disable-next-line no-param-reassign
statementAction = [statementAction];
@ -101,7 +95,7 @@ evaluators.isActionApplicable = (requestAction, statementAction, log) => {
* @return {Object} contains whether conditions are allowed and whether they
* contain any tag condition keys
*/
evaluators.meetConditions = (requestContext, statementCondition, log) => {
export function meetConditions(requestContext, statementCondition, log) {
// The Condition portion of a policy is an object with different
// operators as keys
const conditionEval = {};
@ -204,7 +198,7 @@ evaluators.meetConditions = (requestContext, statementCondition, log) => {
* @return {string} Allow if permitted, Deny if not permitted or Neutral
* if not applicable
*/
evaluators.evaluatePolicy = (requestContext, policy, log) => {
export function evaluatePolicy(requestContext, policy, log) {
// TODO: For bucket policies need to add Principal evaluation
let verdict = 'Neutral';
@ -216,33 +210,33 @@ evaluators.evaluatePolicy = (requestContext, policy, log) => {
const currentStatement = policy.Statement[i];
// If affirmative resource is in policy and request resource is
// not applicable, move on to next statement
if (currentStatement.Resource && !evaluators.isResourceApplicable(requestContext,
if (currentStatement.Resource && !isResourceApplicable(requestContext,
currentStatement.Resource, log)) {
continue;
}
// If NotResource is in policy and resource matches NotResource
// in policy, move on to next statement
if (currentStatement.NotResource &&
evaluators.isResourceApplicable(requestContext,
currentStatement.NotResource, log)) {
isResourceApplicable(requestContext,
currentStatement.NotResource, log)) {
continue;
}
// If affirmative action is in policy and request action is not
// applicable, move on to next statement
if (currentStatement.Action &&
!evaluators.isActionApplicable(requestContext.getAction(),
currentStatement.Action, log)) {
!isActionApplicable(requestContext.getAction(),
currentStatement.Action, log)) {
continue;
}
// If NotAction is in policy and action matches NotAction in policy,
// move on to next statement
if (currentStatement.NotAction &&
evaluators.isActionApplicable(requestContext.getAction(),
currentStatement.NotAction, log)) {
isActionApplicable(requestContext.getAction(),
currentStatement.NotAction, log)) {
continue;
}
const conditionEval = currentStatement.Condition ?
evaluators.meetConditions(requestContext, currentStatement.Condition, log) :
meetConditions(requestContext, currentStatement.Condition, log) :
null;
// If do not meet conditions move on to next statement
if (conditionEval && !conditionEval.allow) {
@ -276,12 +270,12 @@ evaluators.evaluatePolicy = (requestContext, policy, log) => {
* @return {string} Allow if permitted, Deny if not permitted.
* Default is to Deny. Deny overrides an Allow
*/
evaluators.evaluateAllPolicies = (requestContext, allPolicies, log) => {
export function evaluateAllPolicies(requestContext, allPolicies, log) {
log.trace('evaluating all policies');
let verdict = 'Deny';
for (let i = 0; i < allPolicies.length; i++) {
const singlePolicyVerdict =
evaluators.evaluatePolicy(requestContext, allPolicies[i], log);
evaluatePolicy(requestContext, allPolicies[i], log);
// If there is any Deny, just return Deny
if (singlePolicyVerdict === 'Deny') {
return 'Deny';
@ -293,5 +287,3 @@ evaluators.evaluateAllPolicies = (requestContext, allPolicies, log) => {
log.trace('result of evaluating all pollicies', { verdict });
return verdict;
};
module.exports = evaluators;

View File

@ -1,9 +1,9 @@
const { meetConditions } = require('./evaluator');
import { meetConditions } from './evaluator';
/**
* Class with methods to manage the policy 'principal' validation
*/
class Principal {
export default class Principal {
/**
* Function to evaluate conditions if needed
*
@ -176,5 +176,3 @@ class Principal {
};
}
}
module.exports = Principal;

View File

@ -1,4 +1,4 @@
const ipCheck = require('../ipCheck');
import * as ipCheck from '../ipCheck';
/**
* getClientIp - Gets the client IP from the request
@ -6,7 +6,7 @@ const ipCheck = require('../ipCheck');
* @param {object} s3config - s3 config
* @return {string} - returns client IP from the request
*/
function getClientIp(request, s3config) {
export function getClientIp(request, s3config) {
const requestConfig = s3config ? s3config.requests : {};
const remoteAddress = request.socket.remoteAddress;
const clientIp = requestConfig ? remoteAddress : request.headers['x-forwarded-for'] || remoteAddress;
@ -26,7 +26,3 @@ function getClientIp(request, s3config) {
}
return clientIp;
}
module.exports = {
getClientIp,
};

View File

@ -1,4 +1,4 @@
const sharedActionMap = {
export const sharedActionMap = {
bucketDelete: 's3:DeleteBucket',
// the "s3:PutEncryptionConfiguration" action also governs DELETE
bucketDeleteEncryption: 's3:PutEncryptionConfiguration',
@ -47,7 +47,7 @@ const sharedActionMap = {
};
// action map used for request context
const actionMapRQ = Object.assign({
export const actionMapRQ = Object.assign({
bucketPut: 's3:CreateBucket',
// for bucketDeleteCors need s3:PutBucketCORS permission
// see http://docs.aws.amazon.com/AmazonS3/latest/API/
@ -73,15 +73,15 @@ const actionMapRQ = Object.assign({
}, sharedActionMap);
// action map used for bucket policies
const actionMapBP = Object.assign({}, sharedActionMap);
export const actionMapBP = Object.assign({}, sharedActionMap);
// action map for all relevant s3 actions
const actionMapS3 = Object.assign({
export const actionMapS3 = Object.assign({
bucketGetNotification: 's3:GetBucketNotification',
bucketPutNotification: 's3:PutBucketNotification',
}, sharedActionMap, actionMapRQ, actionMapBP);
const actionMonitoringMapS3 = {
export const actionMonitoringMapS3 = {
bucketDelete: 'DeleteBucket',
bucketDeleteCors: 'DeleteBucketCors',
bucketDeleteEncryption: 'DeleteBucketEncryption',
@ -139,7 +139,7 @@ const actionMonitoringMapS3 = {
serviceGet: 'ListBuckets',
};
const actionMapIAM = {
export const actionMapIAM = {
attachGroupPolicy: 'iam:AttachGroupPolicy',
attachUserPolicy: 'iam:AttachUserPolicy',
createAccessKey: 'iam:CreateAccessKey',
@ -178,26 +178,15 @@ const actionMapIAM = {
getCredentialReport: 'iam:GetCredentialReport',
};
const actionMapSSO = {
export const actionMapSSO = {
SsoAuthorize: 'sso:Authorize',
};
const actionMapSTS = {
export const actionMapSTS = {
assumeRole: 'sts:AssumeRole',
};
const actionMapMetadata = {
export const actionMapMetadata = {
admin: 'metadata:admin',
default: 'metadata:bucketd',
};
module.exports = {
actionMapRQ,
actionMapBP,
actionMapS3,
actionMonitoringMapS3,
actionMapIAM,
actionMapSSO,
actionMapSTS,
actionMapMetadata,
};

Some files were not shown because too many files have changed in this diff Show More