Skip to content

Commit

Permalink
feat(data-masking): add ui for inner and outer maskers (#12000)
Browse files Browse the repository at this point in the history
* feat: support mask_inner and mask_outer

* chore: buf format -w

* chore: enum type for innner and outer masker

* chore: enum type for inner and outer maskers

* chore: update

* chore: update

* chore: add more tests

* chore: add more tests

* chore: modify behavior of mask algorithm

* feat: substitution

* chore: update

* chore: naming format

* feat(masking-algorithm): inner and outer maskers

* chore: update

* chore: add 'disabled' behavior

* Update frontend/src/components/SensitiveData/components/MaskingAlgorithmsCreateDrawer.vue

Co-authored-by: Liu Ji <2749742+LiuJi-Jim@users.noreply.github.com>

---------

Co-authored-by: Liu Ji <2749742+LiuJi-Jim@users.noreply.github.com>
  • Loading branch information
Azusain and LiuJi-Jim committed May 15, 2024
1 parent cb0a966 commit 84d4096
Show file tree
Hide file tree
Showing 14 changed files with 926 additions and 162 deletions.
21 changes: 16 additions & 5 deletions backend/api/v1/setting_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -1174,11 +1174,8 @@ func validateMaskingAlgorithm(algorithm *v1pb.MaskingAlgorithmSetting_Algorithm)
}
switch m := algorithm.Mask.(type) {
case *v1pb.MaskingAlgorithmSetting_Algorithm_FullMask_:
if m.FullMask.Substitution == "" {
return status.Errorf(codes.InvalidArgument, "the substitution for full mask is required")
}
if len(m.FullMask.Substitution) > 16 {
return status.Errorf(codes.InvalidArgument, "the substitution should less than 16 bytes")
if err := checkSubstitution(m.FullMask.Substitution); err != nil {
return err
}
case *v1pb.MaskingAlgorithmSetting_Algorithm_RangeMask_:
for i, slice := range m.RangeMask.Slices {
Expand All @@ -1196,6 +1193,10 @@ func validateMaskingAlgorithm(algorithm *v1pb.MaskingAlgorithmSetting_Algorithm)
return status.Errorf(codes.InvalidArgument, "the slice range cannot overlap: [%d,%d) and [%d,%d)", pre.Start, pre.End, slice.Start, slice.End)
}
}
case *v1pb.MaskingAlgorithmSetting_Algorithm_InnerOuterMask_:
if err := checkSubstitution(m.InnerOuterMask.Substitution); err != nil {
return err
}
default:
return status.Errorf(codes.InvalidArgument, "mismatch masking algorithm category and mask type: %T, %s", algorithm.Mask, algorithm.Category)
}
Expand All @@ -1214,3 +1215,13 @@ func validateMaskingAlgorithm(algorithm *v1pb.MaskingAlgorithmSetting_Algorithm)

return nil
}

func checkSubstitution(substitution string) error {
if substitution == "" {
return status.Errorf(codes.InvalidArgument, "the substitution for inner or outer masks is required")
}
if len(substitution) > 16 {
return status.Errorf(codes.InvalidArgument, "the substitution should less than 16 bytes")
}
return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,116 @@
/>
</div>
</template>
<template v-if="state.maskingType === 'inner-outer-mask'">
<label class="textlabel">
{{
$t("settings.sensitive-data.algorithms.inner-outer-mask.type")
}}
<span class="text-red-600 mr-2">*</span>
<p class="textinfolabel">
{{
state.innerOuterMask.type ==
MaskingAlgorithmSetting_Algorithm_InnerOuterMask_MaskType.INNER
? $t(
"settings.sensitive-data.algorithms.inner-outer-mask.inner-label"
)
: $t(
"settings.sensitive-data.algorithms.inner-outer-mask.outer-label"
)
}}
</p>
</label>
<NRadioGroup
v-model:value="state.innerOuterMask.type"
:disabled="state.processing || readonly"
>
<NRadio
:value="
MaskingAlgorithmSetting_Algorithm_InnerOuterMask_MaskType.INNER
"
>
{{
$t(
"settings.sensitive-data.algorithms.inner-outer-mask.inner-mask"
)
}}
</NRadio>
<NRadio
:value="
MaskingAlgorithmSetting_Algorithm_InnerOuterMask_MaskType.OUTER
"
>
{{
$t(
"settings.sensitive-data.algorithms.inner-outer-mask.outer-mask"
)
}}
</NRadio>
</NRadioGroup>
<div class="flex space-x-2 items-center">
<div class="flex-none flex flex-col gap-y-1">
<label for="slice-start" class="textlabel flex">
{{
$t(
"settings.sensitive-data.algorithms.inner-outer-mask.prefix-length"
)
}}
<span class="text-red-600 mr-2">*</span>
</label>
<NInputNumber
:value="state.innerOuterMask.prefixLen"
:placeholder="
t(
'settings.sensitive-data.algorithms.inner-outer-mask.prefix-length'
)
"
:disabled="state.processing || readonly"
style="width: 5.5rem"
@update:value="onPrefixChange($event as number)"
/>
</div>
<div class="flex-none flex flex-col gap-y-1">
<label for="slice-end" class="textlabel">
{{
$t(
"settings.sensitive-data.algorithms.inner-outer-mask.suffix-length"
)
}}
<span class="text-red-600 mr-2">*</span>
</label>
<NInputNumber
:value="state.innerOuterMask.suffixLen"
:placeholder="
t(
'settings.sensitive-data.algorithms.inner-outer-mask.suffix-length'
)
"
:disabled="state.processing || readonly"
style="width: 5.5rem"
@update:value="onSuffixChange($event as number)"
/>
</div>
<div class="flex-1 flex flex-col gap-y-1">
<label for="substitution" class="textlabel">
{{
$t(
"settings.sensitive-data.algorithms.range-mask.substitution"
)
}}
<span class="text-red-600 mr-2">*</span>
</label>
<NInput
v-model:value="state.innerOuterMask.substitution"
:placeholder="
t(
'settings.sensitive-data.algorithms.range-mask.substitution'
)
"
:disabled="state.processing || readonly"
/>
</div>
</div>
</template>
</div>
</div>
<template #footer>
Expand Down Expand Up @@ -213,7 +323,7 @@
<script setup lang="ts">
import { cloneDeep } from "lodash-es";
import { TrashIcon } from "lucide-vue-next";
import { NInput, NInputNumber } from "naive-ui";
import { NInput, NInputNumber, NRadio } from "naive-ui";
import { computed, watch, reactive } from "vue";
import { useI18n } from "vue-i18n";
import type { RadioGridOption, RadioGridItem } from "@/components/v2";
Expand All @@ -229,7 +339,9 @@ import {
MaskingAlgorithmSetting_Algorithm_FullMask as FullMask,
MaskingAlgorithmSetting_Algorithm_RangeMask as RangeMask,
MaskingAlgorithmSetting_Algorithm_MD5Mask as MD5Mask,
MaskingAlgorithmSetting_Algorithm_InnerOuterMask as InnerOuterMask,
MaskingAlgorithmSetting_Algorithm_RangeMask_Slice as RangeMask_Slice,
MaskingAlgorithmSetting_Algorithm_InnerOuterMask_MaskType,
} from "@/types/proto/v1/setting_service";
import type { MaskingType } from "./utils";
import { getMaskingType } from "./utils";
Expand All @@ -247,6 +359,7 @@ interface LocalState {
fullMask: FullMask;
rangeMask: RangeMask;
md5Mask: MD5Mask;
innerOuterMask: InnerOuterMask;
}
const props = defineProps<{
Expand All @@ -271,6 +384,15 @@ const defaultRangeMask = computed(() =>
})
);
const defaultInnerOuterMask = computed(() =>
InnerOuterMask.fromPartial({
prefixLen: 0,
suffixLen: 0,
type: MaskingAlgorithmSetting_Algorithm_InnerOuterMask_MaskType.INNER,
substitution: "*",
})
);
const state = reactive<LocalState>({
processing: false,
maskingType: "full-mask",
Expand All @@ -279,7 +401,9 @@ const state = reactive<LocalState>({
fullMask: FullMask.fromPartial({}),
rangeMask: cloneDeep(defaultRangeMask.value),
md5Mask: MD5Mask.fromPartial({}),
innerOuterMask: cloneDeep(defaultInnerOuterMask.value),
});
const { t } = useI18n();
const settingStore = useSettingV1Store();
Expand All @@ -296,6 +420,10 @@ const maskingTypeList = computed((): MaskingTypeOption[] => [
value: "md5-mask",
label: t("settings.sensitive-data.algorithms.md5-mask.self"),
},
{
value: "inner-outer-mask",
label: t("settings.sensitive-data.algorithms.inner-outer-mask.self"),
},
]);
const algorithmList = computed((): Algorithm[] => {
Expand All @@ -314,6 +442,8 @@ watch(
state.fullMask = algorithm.fullMask ?? FullMask.fromPartial({});
state.rangeMask = algorithm.rangeMask ?? cloneDeep(defaultRangeMask.value);
state.md5Mask = algorithm.md5Mask ?? MD5Mask.fromPartial({});
state.innerOuterMask =
algorithm.innerOuterMask ?? cloneDeep(defaultInnerOuterMask.value);
}
);
Expand All @@ -335,6 +465,8 @@ const maskingAlgorithm = computed((): Algorithm => {
case "md5-mask":
result.md5Mask = state.md5Mask;
break;
case "inner-outer-mask":
result.innerOuterMask = state.innerOuterMask;
}
return result;
Expand Down Expand Up @@ -395,6 +527,18 @@ const errorMessage = computed(() => {
return "";
case "range-mask":
return rangeMaskErrorMessage.value;
case "inner-outer-mask":
if (!state.innerOuterMask.substitution) {
return t(
"settings.sensitive-data.algorithms.error.substitution-required"
);
}
if (state.innerOuterMask.substitution.length > 16) {
return t(
"settings.sensitive-data.algorithms.error.substitution-length"
);
}
return "";
}
return "";
});
Expand Down Expand Up @@ -459,6 +603,9 @@ const onMaskingTypeChange = (maskingType: MaskingType) => {
case "md5-mask":
state.md5Mask = MD5Mask.fromPartial({});
break;
case "inner-outer-mask":
state.innerOuterMask = cloneDeep(defaultInnerOuterMask.value);
break;
}
state.maskingType = maskingType;
};
Expand Down Expand Up @@ -496,4 +643,19 @@ const onSliceEndChange = (index: number, val: number | null) => {
const slice = state.rangeMask.slices[index];
slice.end = val;
};
const onPrefixChange = (val: number) => {
if (val === null || Number.isNaN(val) || val < 0) {
return;
}
state.innerOuterMask.prefixLen = val;
};
const onSuffixChange = (val: number) => {
if (val === null || Number.isNaN(val) || val < 0) {
return;
}
state.innerOuterMask.suffixLen = val;
};
</script>
4 changes: 3 additions & 1 deletion frontend/src/components/SensitiveData/components/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export const factorOperatorOverrideMap = new Map<Factor, Operator[]>([
["project_id", uniq([...EqualityOperatorList, ...CollectionOperatorList])],
]);

export type MaskingType = "full-mask" | "range-mask" | "md5-mask";
export type MaskingType = "full-mask" | "range-mask" | "md5-mask" | "inner-outer-mask";

export const getMaskingType = (
algorithm: MaskingAlgorithmSetting_Algorithm
Expand All @@ -94,6 +94,8 @@ export const getMaskingType = (
return "full-mask";
} else if (algorithm.rangeMask) {
return "range-mask";
} else if (algorithm.innerOuterMask) {
return "inner-outer-mask"
}
break;
default:
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,16 @@
"self": "MD5 mask",
"salt": "Salt",
"salt-label": "Salt is used to generate a hash from the original value."
},
"inner-outer-mask": {
"self": "Inner / Outer mask",
"type": "Mask Type",
"inner-label": "Inner mask masks the interior part of a string. The prefix and suffix represent the unmasked parts of the data.",
"inner-mask": "Inner Mask",
"outer-label": "Outer mask masks the left and right ends of a string. The prefix and suffix represent the masked parts of the data.",
"outer-mask": "Outer Mask",
"prefix-length": "Prefix Length",
"suffix-length": "Suffix Length"
}
},
"masking-level": {
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/locales/es-ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,16 @@
"self": "Máscara MD5",
"salt": "Sal (Salt)",
"salt-label": "La sal (Salt) se utiliza para generar un hash a partir del valor original."
},
"inner-outer-mask": {
"self": "Máscara interna / externa",
"type": "Tipo de máscara",
"inner-label": "La máscara interna enmascara la parte interior de una cadena. El prefijo y el sufijo representan las partes no enmascaradas de los datos.",
"inner-mask": "Mascarilla interior",
"outer-label": "La máscara externa enmascara los extremos izquierdo y derecho de una cadena. El prefijo y el sufijo representan las partes enmascaradas de los datos.",
"outer-mask": "Mascarilla exterior",
"prefix-length": "Longitud del prefijo",
"suffix-length": "Longitud del sufijo"
}
},
"masking-level": {
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/locales/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,16 @@
"self": "MD5ハッシュ",
"salt": "",
"salt-label": "ソルトは、元の値からハッシュ値を生成するために使用されます。"
},
"inner-outer-mask": {
"self": "内部/外部マスク",
"type": "マスクタイプ",
"inner-label": "内部マスクは文字列の内部部分をマスクします。プレフィックスとサフィックスはデータのマスクされていない部分を表します。",
"inner-mask": "インナーマスク",
"outer-label": "外部マスクは文字列の左右の端をマスクします。プレフィックスとサフィックスはデータのマスクされた部分を表します。",
"outer-mask": "アウターマスク",
"prefix-length": "プレフィックスの長さ",
"suffix-length": "サフィックスの長さ"
}
},
"masking-level": {
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/locales/vi-VN.json
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,16 @@
"self": "mặt nạ MD5",
"salt": "Muối",
"salt-label": "Muối được sử dụng để tạo ra hàm băm từ giá trị ban đầu."
},
"inner-outer-mask": {
"self": "Mặt nạ trong / ngoài",
"type": "Loại mặt nạ",
"inner-label": "Mặt nạ bên trong che phần bên trong của một chuỗi. Tiền tố và hậu tố đại diện cho các phần không được che của dữ liệu.",
"inner-mask": "Mặt nạ bên trong",
"outer-label": "Mặt nạ bên ngoài che các đầu trái và phải của một chuỗi. Tiền tố và hậu tố đại diện cho các phần được che của dữ liệu.",
"outer-mask": "Mặt nạ bên ngoài",
"prefix-length": "Độ dài tiền tố",
"suffix-length": "Độ dài hậu tố"
}
},
"masking-level": {
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,16 @@
"self": "MD5 哈希",
"salt": "盐值 (Salt)",
"salt-label": "盐值 (Salt) 用于通过原始值生成哈希值。"
},
"inner-outer-mask": {
"self": "内/外遮掩",
"type": "遮掩类型",
"inner-label": "内遮掩遮蔽字符串的内部部分。前缀和后缀代表数据的未遮蔽部分。",
"inner-mask": "内遮掩",
"outer-label": "外遮掩遮蔽字符串的左右两端。前缀和后缀代表数据的遮蔽部分。",
"outer-mask": "外遮掩",
"prefix-length": "前缀长度",
"suffix-length": "后缀长度"
}
},
"masking-level": {
Expand Down

0 comments on commit 84d4096

Please sign in to comment.