2017-08-15 23:16:38 -07:00
|
|
|
import Ember from 'ember';
|
2017-06-01 13:11:26 -07:00
|
|
|
import { datasetClassifiers } from 'wherehows-web/constants/dataset-classification';
|
2017-10-07 17:49:57 -07:00
|
|
|
import { lastSeenSuggestionInterval } from 'wherehows-web/constants/metadata-acquisition';
|
2017-06-01 13:11:26 -07:00
|
|
|
|
2017-08-30 10:26:44 -07:00
|
|
|
const { assert, Logger: { warn } } = Ember;
|
2017-08-15 23:16:38 -07:00
|
|
|
|
2017-03-24 20:50:42 -07:00
|
|
|
/**
|
2017-05-19 03:05:25 -07:00
|
|
|
* Builds a default shape for securitySpecification & privacyCompliancePolicy with default / unset values
|
2017-04-28 22:02:46 -07:00
|
|
|
* for non null properties as per Avro schema
|
2017-05-19 09:52:19 -07:00
|
|
|
* @param {Number} datasetId id for the dataset that this privacy object applies to
|
2017-04-01 14:13:28 -07:00
|
|
|
*/
|
2017-05-19 10:52:58 -07:00
|
|
|
const createInitialComplianceInfo = datasetId => ({
|
2017-05-19 03:05:25 -07:00
|
|
|
datasetId,
|
2017-10-20 21:57:40 -07:00
|
|
|
complianceType: '',
|
2017-10-19 18:17:16 -07:00
|
|
|
compliancePurgeNote: '',
|
2017-05-19 08:45:05 -07:00
|
|
|
complianceEntities: [],
|
2017-10-19 18:17:16 -07:00
|
|
|
datasetClassification: {}
|
2017-05-19 03:05:25 -07:00
|
|
|
});
|
2017-04-28 22:02:46 -07:00
|
|
|
|
2017-06-01 13:11:26 -07:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @type {{complianceEntities: {type: string, of: {type: string, keys: [*]}}, datasetClassification: {type: string, keys: (*)}, fieldClassification: {type: string}}}
|
|
|
|
*/
|
|
|
|
const policyShape = {
|
|
|
|
complianceEntities: {
|
|
|
|
type: 'array',
|
|
|
|
of: {
|
|
|
|
type: 'object',
|
|
|
|
keys: [
|
|
|
|
'identifierField:string',
|
|
|
|
'identifierType:string',
|
2017-08-30 10:26:44 -07:00
|
|
|
'securityClassification:string|object',
|
2017-06-01 13:11:26 -07:00
|
|
|
'logicalType:string|object|undefined'
|
|
|
|
]
|
|
|
|
}
|
|
|
|
},
|
2017-08-15 23:16:38 -07:00
|
|
|
datasetClassification: { type: 'object', keys: Object.keys(datasetClassifiers).map(key => `${key}:boolean`) }
|
2017-06-01 13:11:26 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks that a policy is valid
|
|
|
|
* @param candidatePolicy
|
2017-08-30 10:26:44 -07:00
|
|
|
* @return {boolean}
|
2017-06-01 13:11:26 -07:00
|
|
|
*/
|
|
|
|
const isPolicyExpectedShape = (candidatePolicy = {}) => {
|
|
|
|
const candidateMatchesShape = policyKey => {
|
2017-08-15 23:16:38 -07:00
|
|
|
assert(
|
|
|
|
`Expected each compliance policy attribute to be one of ${Object.keys(policyShape)}, but got ${policyKey}`,
|
|
|
|
policyShape.hasOwnProperty(policyKey)
|
|
|
|
);
|
|
|
|
|
2017-06-01 13:11:26 -07:00
|
|
|
const policyProps = policyShape[policyKey];
|
|
|
|
const expectedType = policyProps.type;
|
|
|
|
const policyKeyValue = candidatePolicy[policyKey];
|
2017-08-15 23:16:38 -07:00
|
|
|
const isValueExpectedType =
|
|
|
|
expectedType === 'array' ? Array.isArray(policyKeyValue) : typeof policyKeyValue === expectedType;
|
|
|
|
const typeDeclarations =
|
|
|
|
{
|
|
|
|
get array() {
|
|
|
|
return policyProps.of.keys;
|
|
|
|
},
|
|
|
|
get object() {
|
|
|
|
return policyProps.keys;
|
|
|
|
}
|
|
|
|
}[expectedType] || [];
|
2017-06-01 13:11:26 -07:00
|
|
|
|
|
|
|
if (!policyKeyValue || !isValueExpectedType) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (expectedType === 'array') {
|
|
|
|
return policyKeyValue.every(value => {
|
|
|
|
if (!value && typeof value !== policyProps.of.type) {
|
2017-08-30 10:26:44 -07:00
|
|
|
warn(`Typedefs for ${policyKey} with value ${policyKeyValue} does not equal ${policyProps.of.type}`);
|
2017-06-01 13:11:26 -07:00
|
|
|
return false;
|
|
|
|
}
|
2017-08-30 10:26:44 -07:00
|
|
|
|
2017-06-01 13:11:26 -07:00
|
|
|
return typeDeclarations.every(typeString => {
|
|
|
|
const [key, type] = typeString.split(':');
|
2017-08-30 10:26:44 -07:00
|
|
|
const result = type.includes(typeof value[key]);
|
|
|
|
if (!result) {
|
|
|
|
warn(`Typedefs for ${policyKey} don't include '${typeof value[key]}' for ${key}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
2017-06-01 13:11:26 -07:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (expectedType === typeof {}) {
|
|
|
|
return typeDeclarations.every(typeString => {
|
|
|
|
const [key, type] = typeString.split(':');
|
2017-08-30 10:26:44 -07:00
|
|
|
const result = type.includes(typeof policyKeyValue[key]);
|
|
|
|
if (!result) {
|
|
|
|
warn(`Typedefs for ${policyKey} don't include ${typeof policyKeyValue[key]} for ${key}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
2017-06-01 13:11:26 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if (typeof candidatePolicy === 'object' && candidatePolicy) {
|
|
|
|
return Object.keys(policyShape).every(candidateMatchesShape);
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
2017-10-07 17:49:57 -07:00
|
|
|
/**
|
|
|
|
* Checks if the compliance suggestion has a date that is equal or exceeds the policy mod time by at least the
|
|
|
|
* ms time in lastSeenSuggestionInterval
|
|
|
|
* @param {number} [policyModificationTime = 0] timestamp for the policy modification date
|
|
|
|
* @param {number} suggestionModificationTime timestamp for the suggestion modification date
|
|
|
|
* @return {boolean}
|
|
|
|
*/
|
|
|
|
const isRecentSuggestion = (policyModificationTime = 0, suggestionModificationTime) =>
|
|
|
|
!!suggestionModificationTime && suggestionModificationTime - policyModificationTime >= lastSeenSuggestionInterval;
|
|
|
|
|
2017-09-26 23:07:29 -07:00
|
|
|
/**
|
|
|
|
* Checks if a compliance policy changeSet field requires user attention: if a suggestion
|
|
|
|
* is available but the user has not indicated intent or a policy for the field does not currently exist remotely
|
|
|
|
* and the related field changeSet has not been modified on the client
|
|
|
|
* @param {boolean} isDirty flag indicating the field changeSet has been modified on the client
|
|
|
|
* @param {object|void} suggestion the field suggestion properties
|
|
|
|
* @param {boolean} privacyPolicyExists flag indicating that the field has a current policy upstream
|
|
|
|
* @param {string} suggestionAuthority possibly empty string indicating the user intent for the suggestion
|
|
|
|
* @return {boolean}
|
|
|
|
*/
|
2017-10-02 18:49:35 -07:00
|
|
|
const fieldChangeSetRequiresReview = ({ isDirty, suggestion, privacyPolicyExists, suggestionAuthority } = {}) => {
|
2017-09-26 23:07:29 -07:00
|
|
|
if (suggestion) {
|
|
|
|
return !suggestionAuthority;
|
|
|
|
}
|
|
|
|
|
2017-10-02 18:49:35 -07:00
|
|
|
// If either the privacy policy exists, or user has made changes, then no review is required
|
|
|
|
return !(privacyPolicyExists || isDirty);
|
2017-09-26 23:07:29 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Merges the column fields with the suggestion for the field if available
|
|
|
|
* @param {object} mappedColumnFields a map of column fields to compliance entity properties
|
|
|
|
* @param {object} fieldSuggestionMap a map of field suggestion properties keyed by field name
|
|
|
|
* @return {Array<object>} mapped column field augmented with suggestion if available
|
|
|
|
*/
|
|
|
|
const mergeMappedColumnFieldsWithSuggestions = (mappedColumnFields = {}, fieldSuggestionMap = {}) =>
|
|
|
|
Object.keys(mappedColumnFields).map(fieldName => {
|
|
|
|
const {
|
|
|
|
identifierField,
|
|
|
|
dataType,
|
|
|
|
identifierType,
|
|
|
|
logicalType,
|
|
|
|
securityClassification,
|
2017-10-07 17:49:57 -07:00
|
|
|
policyModificationTime,
|
2017-09-26 23:07:29 -07:00
|
|
|
privacyPolicyExists,
|
|
|
|
isDirty
|
|
|
|
} = mappedColumnFields[fieldName];
|
|
|
|
const suggestion = fieldSuggestionMap[identifierField];
|
|
|
|
|
|
|
|
const field = {
|
|
|
|
identifierField,
|
|
|
|
dataType,
|
|
|
|
identifierType,
|
|
|
|
logicalType,
|
|
|
|
privacyPolicyExists,
|
|
|
|
isDirty,
|
|
|
|
classification: securityClassification
|
|
|
|
};
|
|
|
|
|
2017-10-07 17:49:57 -07:00
|
|
|
// If a suggestion exists for this field add the suggestion attribute to the field properties / changeSet
|
|
|
|
// Check if suggestion isRecent before augmenting, otherwise, suggestion will not be considered on changeSet
|
|
|
|
if (suggestion && isRecentSuggestion(policyModificationTime, suggestion.suggestionsModificationTime)) {
|
2017-09-26 23:07:29 -07:00
|
|
|
return { ...field, suggestion };
|
|
|
|
}
|
|
|
|
|
|
|
|
return field;
|
|
|
|
});
|
|
|
|
|
|
|
|
export {
|
|
|
|
createInitialComplianceInfo,
|
|
|
|
isPolicyExpectedShape,
|
|
|
|
fieldChangeSetRequiresReview,
|
2017-10-07 17:49:57 -07:00
|
|
|
mergeMappedColumnFieldsWithSuggestions,
|
|
|
|
isRecentSuggestion
|
2017-09-26 23:07:29 -07:00
|
|
|
};
|