diff --git a/data/core.yaml b/data/core.yaml index e39a1419b..8cf627c5a 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -1295,6 +1295,11 @@ en: title: Outdated Tags message: '{feature} has outdated tags' tip: "Some tags change over time and should be updated." + private_data: + title: Private Information + contact: + message: '{feature} might be tagged with private contact information' + tip: "Sensitive data like personal phone numbers should not be tagged." tag_suggests_area: title: Lines Tagged as Areas message: '{feature} should be a closed area based on the tag "{tag}"' @@ -1327,6 +1332,8 @@ en: remove_generic_name: title: Remove the name annotation: Removed a generic name. + remove_private_info: + annotation: Removed private information. remove_tag: title: Remove the tag annotation: Removed tag. diff --git a/dist/locales/en.json b/dist/locales/en.json index c02940efc..f00506a36 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -1592,6 +1592,13 @@ "message": "{feature} has outdated tags", "tip": "Some tags change over time and should be updated." }, + "private_data": { + "title": "Private Information", + "contact": { + "message": "{feature} might be tagged with private contact information" + }, + "tip": "Sensitive data like personal phone numbers should not be tagged." + }, "tag_suggests_area": { "title": "Lines Tagged as Areas", "message": "{feature} should be a closed area based on the tag \"{tag}\"", @@ -1636,6 +1643,9 @@ "title": "Remove the name", "annotation": "Removed a generic name." }, + "remove_private_info": { + "annotation": "Removed private information." + }, "remove_tag": { "title": "Remove the tag", "annotation": "Removed tag." diff --git a/modules/validations/index.js b/modules/validations/index.js index 8c5be3e92..b38ff0d97 100644 --- a/modules/validations/index.js +++ b/modules/validations/index.js @@ -8,5 +8,6 @@ export { validationMissingRole } from './missing_role'; export { validationMissingTag } from './missing_tag'; export { validationOldMultipolygon } from './old_multipolygon'; export { validationOutdatedTags } from './outdated_tags'; +export { validationPrivateData } from './private_data'; export { validationTagSuggestsArea } from './tag_suggests_area'; export { validationUnknownRoad } from './unknown_road'; diff --git a/modules/validations/private_data.js b/modules/validations/private_data.js new file mode 100644 index 000000000..d9a5f807a --- /dev/null +++ b/modules/validations/private_data.js @@ -0,0 +1,95 @@ +import _clone from 'lodash-es/clone'; + +import { actionChangeTags } from '../actions'; +import { t } from '../util/locale'; +import { utilDisplayLabel } from '../util'; +import { validationIssue, validationIssueFix } from '../core/validator'; + + +export function validationPrivateData() { + var type = 'private_data'; + + // assume that some buildings are private + var privateBuildingValues = { + detached: true, + farm: true, + house: true, + residential: true, + semidetached_house: true, + static_caravan: true + }; + + // but they might be public if they have one of these other tags + var okayModifierKeys = { + amenity: true, + craft: true, + historic: true, + leisure: true, + shop: true, + tourism: true + }; + + // these tags may contain personally identifying info + var personalTags = { + 'contact:email': true, + 'contact:fax': true, + 'contact:phone': true, + 'contact:website': true, + email: true, + fax: true, + phone: true, + website: true + }; + + function privateDataKeys(entity) { + var tags = entity.tags; + if (!tags.building || !privateBuildingValues[tags.building]) return []; + var privateKeys = []; + for (var key in tags) { + if (okayModifierKeys[key]) return []; + if (personalTags[key]) privateKeys.push(key); + } + return privateKeys; + } + + var validation = function(entity, context) { + + var privateKeys = privateDataKeys(entity); + + if (privateKeys.length === 0) return []; + + var fixID = privateKeys.length === 1 ? 'remove_tag' : 'remove_tags'; + return [new validationIssue({ + type: type, + severity: 'warning', + message: t('issues.private_data.contact.message', { + feature: utilDisplayLabel(entity, context), + }), + tooltip: t('issues.private_data.tip'), + entities: [entity], + info: { privateKeys: privateKeys }, + fixes: [ + new validationIssueFix({ + icon: 'iD-operation-delete', + title: t('issues.fix.' + fixID + '.title'), + onClick: function() { + var entity = this.issue.entities[0]; + var tags = _clone(entity.tags); + var privateKeys = this.issue.info.privateKeys; + for (var index in privateKeys) { + delete tags[privateKeys[index]]; + } + context.perform( + actionChangeTags(entity.id, tags), + t('issues.fix.remove_private_info.annotation') + ); + } + }) + ] + })]; + }; + + validation.type = type; + + return validation; +}