Merge pull request #935 from theseyi/default-sec-class

adds types for dataset security class. defaults dataset security class to internal with user confirmation
This commit is contained in:
Seyi Adebajo 2018-01-17 11:14:03 -08:00 committed by GitHub
commit f48ed9b0cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 331 additions and 210 deletions

View File

@ -5,6 +5,10 @@ import { run, schedule } from '@ember/runloop';
import { inject } from '@ember/service'; import { inject } from '@ember/service';
import { classify } from '@ember/string'; import { classify } from '@ember/string';
import { IFieldIdentifierOption } from 'wherehows-web/constants/dataset-compliance'; import { IFieldIdentifierOption } from 'wherehows-web/constants/dataset-compliance';
import { Classification } from 'wherehows-web/constants/datasets/compliance';
import { IDatasetView } from 'wherehows-web/typings/api/datasets/dataset';
import { IDataPlatform } from 'wherehows-web/typings/api/list/platforms';
import { readPlatforms } from 'wherehows-web/utils/api/list/platforms';
import isTrackingHeaderField from 'wherehows-web/utils/validators/tracking-headers'; import isTrackingHeaderField from 'wherehows-web/utils/validators/tracking-headers';
import { import {
@ -22,7 +26,8 @@ import {
IComplianceField, IComplianceField,
DatasetClassification, DatasetClassification,
SuggestionIntent, SuggestionIntent,
PurgePolicy PurgePolicy,
getSupportedPurgePolicies
} from 'wherehows-web/constants'; } from 'wherehows-web/constants';
import { import {
isPolicyExpectedShape, isPolicyExpectedShape,
@ -43,7 +48,7 @@ import {
IComplianceSuggestion IComplianceSuggestion
} from 'wherehows-web/typings/api/datasets/compliance'; } from 'wherehows-web/typings/api/datasets/compliance';
import { ApiStatus } from 'wherehows-web/utils/api'; import { ApiStatus } from 'wherehows-web/utils/api';
import { task } from 'ember-concurrency'; import { task, TaskInstance } from 'ember-concurrency';
/** /**
* Describes the DatasetCompliance actions index signature to allow * Describes the DatasetCompliance actions index signature to allow
@ -52,6 +57,7 @@ import { task } from 'ember-concurrency';
interface IDatasetComplianceActions { interface IDatasetComplianceActions {
didEditCompliancePolicy: () => Promise<boolean>; didEditCompliancePolicy: () => Promise<boolean>;
didEditPurgePolicy: () => Promise<{} | void>; didEditPurgePolicy: () => Promise<{} | void>;
didEditDatasetLevelCompliancePolicy: () => Promise<void>;
[K: string]: (...args: Array<any>) => any; [K: string]: (...args: Array<any>) => any;
} }
@ -96,7 +102,8 @@ const {
helpText, helpText,
successUploading, successUploading,
invalidPolicyData, invalidPolicyData,
missingPurgePolicy missingPurgePolicy,
defaultDatasetClassificationMsg
} = compliancePolicyStrings; } = compliancePolicyStrings;
/** /**
@ -137,11 +144,24 @@ const changeSetFieldsRequiringReview = arrayFilter<IComplianceChangeSet>(fieldCh
const initialStepIndex = -1; const initialStepIndex = -1;
/** /**
* Applies the observer to the editStepIndex to trigger the update edit step task * Defines observers for the DatasetCompliance Component
* @type {Component}
*/ */
const ObservableDecorator = Component.extend({ const ObservableDecorator = Component.extend({
/**
* Observes changes editStepIndex to trigger the update edit step task
* @type {() => void}
*/
editStepIndexChanged: observer('editStepIndex', function(this: DatasetCompliance) { editStepIndexChanged: observer('editStepIndex', function(this: DatasetCompliance) {
get(this, 'updateEditStepTask').perform(); get(this, 'updateEditStepTask').perform();
}),
/**
* Observes changes to the platform property and invokes the task to update the supportedPurgePolicies prop
* @type {() => void}
*/
platformChanged: observer('platform', function(this: DatasetCompliance) {
get(this, 'complianceAvailabilityTask').perform();
}) })
}); });
@ -158,6 +178,8 @@ export default class DatasetCompliance extends ObservableDecorator {
_hasBadData: boolean; _hasBadData: boolean;
_message: string; _message: string;
_alertType: string; _alertType: string;
platform: IDatasetView['platform'];
isCompliancePolicyAvailable: boolean = false;
showAllDatasetMemberData: boolean; showAllDatasetMemberData: boolean;
complianceInfo: void | IComplianceInfo; complianceInfo: void | IComplianceInfo;
complianceSuggestion: IComplianceSuggestion; complianceSuggestion: IComplianceSuggestion;
@ -231,6 +253,13 @@ export default class DatasetCompliance extends ObservableDecorator {
*/ */
isSaving = false; isSaving = false;
/**
* The list of supported purge policies for the related platform
* @type {Array<PurgePolicy>}
* @memberof DatasetCompliance
*/
supportedPurgePolicies: Array<PurgePolicy> = [];
constructor() { constructor() {
super(...arguments); super(...arguments);
@ -350,6 +379,10 @@ export default class DatasetCompliance extends ObservableDecorator {
} }
} }
didInsertElement() {
get(this, 'complianceAvailabilityTask').perform();
}
/** /**
* @override * @override
*/ */
@ -360,6 +393,33 @@ export default class DatasetCompliance extends ObservableDecorator {
this.enableDomCloaking(); this.enableDomCloaking();
} }
/**
* Parent task to determine if a compliance policy can be created or updated for the dataset
* @type {Task<TaskInstance<Promise<Array<IDataPlatform>>>, () => TaskInstance<TaskInstance<Promise<Array<IDataPlatform>>>>>}
* @memberof DatasetCompliance
*/
complianceAvailabilityTask = task(function*(
this: DatasetCompliance
): IterableIterator<TaskInstance<Promise<Array<IDataPlatform>>>> {
yield get(this, 'getPlatformPoliciesTask').perform();
const supportedPurgePolicies = get(this, 'supportedPurgePolicies');
set(this, 'isCompliancePolicyAvailable', !!supportedPurgePolicies.length);
}).restartable();
/**
* Task to retrieve platform policies and set supported policies for the current platform
* @type {Task<Promise<Array<IDataPlatform>>, () => TaskInstance<Promise<Array<IDataPlatform>>>>}
* @memberof DatasetCompliance
*/
getPlatformPoliciesTask = task(function*(this: DatasetCompliance): IterableIterator<Promise<Array<IDataPlatform>>> {
const platform = get(this, 'platform');
if (platform) {
set(this, 'supportedPurgePolicies', getSupportedPurgePolicies(platform, yield readPlatforms()));
}
}).restartable();
/** /**
* A `lite` / intermediary step to occlusion culling, this helps to improve the rendering of * A `lite` / intermediary step to occlusion culling, this helps to improve the rendering of
* elements that are currently rendered in the viewport by hiding that aren't. * elements that are currently rendered in the viewport by hiding that aren't.
@ -369,13 +429,14 @@ export default class DatasetCompliance extends ObservableDecorator {
*/ */
enableDomCloaking() { enableDomCloaking() {
const dom = this.element.querySelector('.dataset-compliance-fields'); const dom = this.element.querySelector('.dataset-compliance-fields');
const triggerCount = 100; const triggerThreshold = 100;
if (dom) { if (dom) {
const rows = dom.querySelectorAll('tbody tr'); const rows = dom.querySelectorAll('tbody tr');
// if we already have watchers for elements, or if the elements previously cached are no longer valid, // if we already have watchers for elements, or if the elements previously cached are no longer valid,
// e.g. those elements were destroyed when new data was received, pagination etc // e.g. those elements were destroyed when new data was received, pagination etc
if (rows.length > triggerCount && (!this.complianceWatchers || !this.complianceWatchers.has(rows[0]))) { if (rows.length > triggerThreshold && (!this.complianceWatchers || !this.complianceWatchers.has(rows[0]))) {
/** /**
* If an item is not in the viewport add a class to occlude it * If an item is not in the viewport add a class to occlude it
*/ */
@ -1047,6 +1108,39 @@ export default class DatasetCompliance extends ObservableDecorator {
return isConfirmed; return isConfirmed;
}, },
/**
* Handles tasks to be processed after the wizard step to edit a datasets pii and security classification is
* completed
* @returns {Promise<void>}
*/
async didEditDatasetLevelCompliancePolicy(this: DatasetCompliance): Promise<void> {
const complianceInfo = get(this, 'complianceInfo');
if (complianceInfo) {
const { confidentiality, containingPersonalData } = complianceInfo;
const dialogActions: { [prop: string]: () => void } = {};
const confirmConfidentialityPromise = new Promise((resolve, reject) => {
dialogActions['didConfirm'] = () => resolve();
dialogActions['didDismiss'] = () => reject();
});
// defaults the containing personal data flag to false if undefined
if (typeof containingPersonalData === 'undefined') {
set(complianceInfo, 'containingPersonalData', false);
}
if (!confidentiality) {
get(this, 'notifications').notify(NotificationEvent.confirm, {
dialogActions,
header: 'Confirm dataset classification',
content: defaultDatasetClassificationMsg
});
await confirmConfidentialityPromise;
set(complianceInfo, 'confidentiality', Classification.Internal);
}
}
},
/** /**
* Handles post processing tasks after the purge policy step has been completed * Handles post processing tasks after the purge policy step has been completed
* @returns {(Promise<void | {}>)} * @returns {(Promise<void | {}>)}

View File

@ -1,20 +1,17 @@
import Component from '@ember/component'; import Component from '@ember/component';
import { get, set, observer } from '@ember/object'; import { get, set } from '@ember/object';
import { run, next } from '@ember/runloop'; import { run, next } from '@ember/runloop';
import { task } from 'ember-concurrency'; import DatasetCompliance from 'wherehows-web/components/dataset-compliance';
import { import {
baseCommentEditorOptions, baseCommentEditorOptions,
DatasetPlatform, DatasetPlatform,
exemptPolicy, exemptPolicy,
getSupportedPurgePolicies,
isExempt, isExempt,
missingPolicyText, missingPolicyText,
PurgePolicy, PurgePolicy,
purgePolicyProps purgePolicyProps
} from 'wherehows-web/constants'; } from 'wherehows-web/constants';
import { IComplianceInfo } from 'wherehows-web/typings/api/datasets/compliance'; import { IComplianceInfo } from 'wherehows-web/typings/api/datasets/compliance';
import { IDataPlatform } from 'wherehows-web/typings/api/list/platforms';
import { readPlatforms } from 'wherehows-web/utils/api/list/platforms';
export default class PurgePolicyComponent extends Component { export default class PurgePolicyComponent extends Component {
/** /**
@ -40,7 +37,7 @@ export default class PurgePolicyComponent extends Component {
* @type {Array<PurgePolicy>} * @type {Array<PurgePolicy>}
* @memberof PurgePolicyComponent * @memberof PurgePolicyComponent
*/ */
supportedPurgePolicies: Array<PurgePolicy> = []; supportedPurgePolicies: DatasetCompliance['supportedPurgePolicies'];
/** /**
* The dataset's platform * The dataset's platform
@ -87,31 +84,6 @@ export default class PurgePolicyComponent extends Component {
this.checkExemption(get(this, 'purgePolicy')); this.checkExemption(get(this, 'purgePolicy'));
} }
didInsertElement() {
get(this, 'getPlatformPolicies').perform();
}
/**
* Observes changes to the platform property and invokes the task to update the supportedPurgePolicies prop
* @type {void}
* @memberof PurgePolicyComponent
*/
platformChanged = observer('platform', function(this: PurgePolicyComponent) {
get(this, 'getPlatformPolicies').perform();
});
/**
* Task to retrieve platform policies for and set supported policies for the current platform
* @memberof PurgePolicyComponent
*/
getPlatformPolicies = task(function*(this: PurgePolicyComponent): IterableIterator<Promise<Array<IDataPlatform>>> {
const platform = get(this, 'platform');
if (platform) {
set(this, 'supportedPurgePolicies', getSupportedPurgePolicies(platform, yield readPlatforms()));
}
}).restartable();
/** /**
* Checks that the selected purge policy is exempt, if so, set the * Checks that the selected purge policy is exempt, if so, set the
* flag to request the exemption to true * flag to request the exemption to true

View File

@ -55,7 +55,9 @@ const compliancePolicyStrings = {
'This security classification is from go/dht and should be good enough in most cases. ' + 'This security classification is from go/dht and should be good enough in most cases. ' +
'You can optionally override it if required by house security.' 'You can optionally override it if required by house security.'
}, },
missingPurgePolicy: 'Please specify a Compliance Purge Policy' missingPurgePolicy: 'Please specify a Compliance Purge Policy',
defaultDatasetClassificationMsg: `You haven't set this dataset's security classification,
it's value will be set to default of "${Classification.Internal}"`
}; };
/** /**

View File

@ -18,7 +18,9 @@ enum SuggestionIntent {
enum Classification { enum Classification {
Confidential = 'CONFIDENTIAL', Confidential = 'CONFIDENTIAL',
LimitedDistribution = 'LIMITED_DISTRIBUTION', LimitedDistribution = 'LIMITED_DISTRIBUTION',
HighlyConfidential = 'HIGHLY_CONFIDENTIAL' HighlyConfidential = 'HIGHLY_CONFIDENTIAL',
Internal = 'INTERNAL',
Public = 'PUBLIC'
} }
/** /**

View File

@ -1,4 +1,9 @@
<div id="compliance" class="tab-body"> {{#if complianceAvailabilityTask.isRunning}}
{{pendulum-ellipsis-animation}}
{{else}}
{{#if isCompliancePolicyAvailable}}
<div id="compliance" class="tab-body">
{{#if _hasBadData}} {{#if _hasBadData}}
<div class="alert alert-danger post-action-notification" role="alert"> <div class="alert alert-danger post-action-notification" role="alert">
<p> <p>
@ -101,13 +106,39 @@
{{/if}} {{/if}}
{{#if (or isReadOnly (eq editStep.name editSteps.1.name))}} {{#if (or isReadOnly (eq editStep.name editSteps.1.name))}}
{{#if getPlatformPoliciesTask.isRunning}}
{{pendulum-ellipsis-animation}}
{{/if}}
{{#if getPlatformPoliciesTask.last.isError}}
{{!--todo: swap out with error-state component--}}
{{empty-state
heading="Purge Policies not available"
subHead="Could not find supported purge policies for this platform"
}}
<div class="purge-policy-list__retry-platforms">
<button
class="nacho-button nacho-button--large-inverse"
onclick={{perform getPlatformPoliciesTask}}>
Retry
</button>
</div>
{{/if}}
{{#if getPlatformPoliciesTask.last.isSuccessful}}
{{purge-policy {{purge-policy
isEditable=(not isReadOnly) isEditable=(not isReadOnly)
platform=platform platform=platform
supportedPurgePolicies=supportedPurgePolicies
purgeNote=complianceInfo.compliancePurgeNote purgeNote=complianceInfo.compliancePurgeNote
purgePolicy=(readonly complianceInfo.complianceType) purgePolicy=(readonly complianceInfo.complianceType)
onPolicyChange=(action "onDatasetPurgePolicyChange") onPolicyChange=(action "onDatasetPurgePolicyChange")
}} }}
{{/if}}
{{/if}} {{/if}}
{{#if schemaless}} {{#if schemaless}}
@ -129,6 +160,16 @@
{{/if}} {{/if}}
{{/if}} {{/if}}
</div> </div>
{{else}}
{{empty-state
heading="Compliance Policy not available"
subHead="Compliance Policy is currently not available for this platform"
}}
{{/if}}
{{/if}}
{{yield}} {{yield}}

View File

@ -9,29 +9,6 @@
<ul class="purge-policy-list"> <ul class="purge-policy-list">
{{#if isEditable}} {{#if isEditable}}
{{#if getPlatformPolicies.isRunning}}
{{pendulum-ellipsis-animation}}
{{/if}}
{{#if getPlatformPolicies.last.isError}}
{{!--todo: swap out with error-state component--}}
{{empty-state
heading="Purge Policies not available"
subHead="Could not find supported purge policies for this platform"
}}
<div class="purge-policy-list__retry-platforms">
<button
class="nacho-button nacho-button--large-inverse"
onclick={{perform getPlatformPolicies}}>
Retry
</button>
</div>
{{/if}}
{{#if getPlatformPolicies.last.isSuccessful}}
{{#each-in purgePolicyProps as |purgeType prop|}} {{#each-in purgePolicyProps as |purgeType prop|}}
<li <li
class="purge-policy-list__item {{unless (contains purgeType supportedPurgePolicies) class="purge-policy-list__item {{unless (contains purgeType supportedPurgePolicies)
@ -67,8 +44,6 @@
</li> </li>
{{/each-in}} {{/each-in}}
{{/if}}
{{else}} {{else}}
{{#if purgePolicy}} {{#if purgePolicy}}

View File

@ -0,0 +1,22 @@
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import notificationsStub from 'wherehows-web/tests/stubs/services/notifications';
moduleForComponent('dataset-compliance', 'Integration | Component | dataset compliance', {
integration: true,
beforeEach() {
this.register('service:notifications', notificationsStub);
this.inject.service('notifications');
}
});
test('it renders an empty state component when isCompliancePolicyAvailable is false', function(assert) {
this.render(hbs`{{dataset-compliance}}`);
assert.notOk(this.get('isCompliancePolicyAvailable'));
assert.ok(document.querySelector('empty-state'));
});

View File

@ -1,9 +1,15 @@
import { moduleForComponent, test } from 'ember-qunit'; import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile'; import hbs from 'htmlbars-inline-precompile';
import { triggerEvent, waitUntil, find } from 'ember-native-dom-helpers'; import { triggerEvent } from 'ember-native-dom-helpers';
import sinon from 'sinon'; import sinon from 'sinon';
import { missingPolicyText, purgePolicyProps, exemptPolicy, PurgePolicy } from 'wherehows-web/constants'; import {
missingPolicyText,
purgePolicyProps,
exemptPolicy,
PurgePolicy,
getSupportedPurgePolicies
} from 'wherehows-web/constants';
import { DatasetPlatform } from 'wherehows-web/constants/datasets/platform'; import { DatasetPlatform } from 'wherehows-web/constants/datasets/platform';
import platforms from 'wherehows-web/mirage/fixtures/list-platforms'; import platforms from 'wherehows-web/mirage/fixtures/list-platforms';
import { ApiStatus } from 'wherehows-web/utils/api'; import { ApiStatus } from 'wherehows-web/utils/api';
@ -80,6 +86,7 @@ test('it indicates the currently selected purge policy', async function(assert)
this.set('isEditable', true); this.set('isEditable', true);
this.set('platform', platform); this.set('platform', platform);
this.set('purgePolicy', selectedPolicy); this.set('purgePolicy', selectedPolicy);
this.set('supportedPurgePolicies', getSupportedPurgePolicies(platform, platforms));
this.server.respondWith('GET', '/api/v1/list/platforms', [ this.server.respondWith('GET', '/api/v1/list/platforms', [
200, 200,
@ -87,10 +94,11 @@ test('it indicates the currently selected purge policy', async function(assert)
JSON.stringify({ status: ApiStatus.OK, platforms }) JSON.stringify({ status: ApiStatus.OK, platforms })
]); ]);
this.render(hbs`{{purge-policy isEditable=isEditable purgePolicy=purgePolicy platform=platform}}`); this.render(
hbs`{{purge-policy isEditable=isEditable purgePolicy=purgePolicy platform=platform supportedPurgePolicies=supportedPurgePolicies}}`
);
this.server.respond(); this.server.respond();
await waitUntil(() => find(`${policyList} [type=radio][value=${selectedPolicy}]`));
assert.ok( assert.ok(
document.querySelector(`${policyList} [type=radio][value=${selectedPolicy}]`).checked, document.querySelector(`${policyList} [type=radio][value=${selectedPolicy}]`).checked,
`${selectedPolicy} radio is checked` `${selectedPolicy} radio is checked`

View File

@ -0,0 +1,5 @@
import Service from '@ember/service';
export default class extends Service {
notify = () => void 0;
}