mirror of
https://github.com/eolinker/apinto
synced 2025-09-26 21:01:19 +08:00
数据脱敏插件完成
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/eolinker/apinto/drivers/discovery/eureka"
|
||||
"github.com/eolinker/apinto/drivers/discovery/nacos"
|
||||
"github.com/eolinker/apinto/drivers/discovery/static"
|
||||
data_mask_strategy "github.com/eolinker/apinto/drivers/strategy/data-mask-strategy"
|
||||
|
||||
"github.com/eolinker/apinto/application/auth"
|
||||
"github.com/eolinker/apinto/drivers/discovery/polaris"
|
||||
@@ -74,6 +75,7 @@ func driverRegister(extenderRegister eosc.IExtenderDriverRegister) {
|
||||
grey_strategy.Register(extenderRegister)
|
||||
visit_strategy.Register(extenderRegister)
|
||||
fuse_strategy.Register(extenderRegister)
|
||||
data_mask_strategy.Register(extenderRegister)
|
||||
|
||||
// 编码器
|
||||
protocbuf.Register(extenderRegister)
|
||||
|
@@ -21,6 +21,7 @@ import (
|
||||
response_filter "github.com/eolinker/apinto/drivers/plugins/response-filter"
|
||||
response_rewrite_v2 "github.com/eolinker/apinto/drivers/plugins/response-rewrite_v2"
|
||||
rsa_filter "github.com/eolinker/apinto/drivers/plugins/rsa-filter"
|
||||
data_mask "github.com/eolinker/apinto/drivers/plugins/strategy/data-mask"
|
||||
|
||||
access_log "github.com/eolinker/apinto/drivers/plugins/access-log"
|
||||
body_check "github.com/eolinker/apinto/drivers/plugins/body-check"
|
||||
@@ -57,6 +58,7 @@ func pluginRegister(extenderRegister eosc.IExtenderDriverRegister) {
|
||||
grey.Register(extenderRegister)
|
||||
visit.Register(extenderRegister)
|
||||
fuse.Register(extenderRegister)
|
||||
data_mask.Register(extenderRegister)
|
||||
|
||||
// Dubbo协议相关插件
|
||||
dubbo2_proxy_rewrite.Register(extenderRegister)
|
||||
|
@@ -165,6 +165,12 @@ func ApintoProfession() []*eosc.ProfessionConfig {
|
||||
Label: "熔断策略",
|
||||
Desc: "熔断策略",
|
||||
},
|
||||
{
|
||||
Id: "eolinker.com:apinto:strategy-data_mask",
|
||||
Name: "data_mask",
|
||||
Label: "数据脱敏策略",
|
||||
Desc: "数据脱敏策略",
|
||||
},
|
||||
},
|
||||
Mod: eosc.ProfessionConfig_Worker,
|
||||
},
|
||||
|
@@ -12,12 +12,13 @@ import (
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ohler55/ojg/jp"
|
||||
"github.com/ohler55/ojg/oj"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ohler55/ojg/jp"
|
||||
"github.com/ohler55/ojg/oj"
|
||||
)
|
||||
|
||||
type jwtToken struct {
|
||||
@@ -83,18 +84,18 @@ func (m *signingMethod) Verify(signingString, signature string, key interface{})
|
||||
if !ok {
|
||||
return errInvalidKeyType
|
||||
}
|
||||
|
||||
|
||||
// Decode signature, for comparison
|
||||
sig, err := decodeSegment(signature)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
// Can we use the specified hashing method?
|
||||
if !m.Hash.Available() {
|
||||
return errHashUnavailable
|
||||
}
|
||||
|
||||
|
||||
// This signing method is symmetric, so we validate the signature
|
||||
// by reproducing the signature from the signing string and key, then
|
||||
// comparing that against the provided signature.
|
||||
@@ -103,47 +104,47 @@ func (m *signingMethod) Verify(signingString, signature string, key interface{})
|
||||
if !hmac.Equal(sig, hasher.Sum(nil)) {
|
||||
return errSignatureInvalid
|
||||
}
|
||||
|
||||
|
||||
// No validation errors. Signature is good.
|
||||
return nil
|
||||
}
|
||||
case "RS256", "RS384", "RS512":
|
||||
{
|
||||
var err error
|
||||
|
||||
|
||||
// Decode the signature
|
||||
var sig []byte
|
||||
if sig, err = decodeSegment(signature); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
var rsaKey *rsa.PublicKey
|
||||
var ok bool
|
||||
|
||||
|
||||
if rsaKey, ok = key.(*rsa.PublicKey); !ok {
|
||||
return errInvalidKeyType
|
||||
}
|
||||
|
||||
|
||||
// Create hasher
|
||||
if !m.Hash.Available() {
|
||||
return errHashUnavailable
|
||||
}
|
||||
hasher := m.Hash.New()
|
||||
hasher.Write([]byte(signingString))
|
||||
|
||||
|
||||
// Verify the signature
|
||||
return rsa.VerifyPKCS1v15(rsaKey, m.Hash, hasher.Sum(nil), sig)
|
||||
}
|
||||
case "ES256", "ES384", "ES512":
|
||||
{
|
||||
var err error
|
||||
|
||||
|
||||
// Decode the signature
|
||||
var sig []byte
|
||||
if sig, err = decodeSegment(signature); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
// GetEmployee the key
|
||||
var ecdsaKey *ecdsa.PublicKey
|
||||
switch k := key.(type) {
|
||||
@@ -152,26 +153,26 @@ func (m *signingMethod) Verify(signingString, signature string, key interface{})
|
||||
default:
|
||||
return errInvalidKeyType
|
||||
}
|
||||
|
||||
|
||||
if len(sig) != 2*m.KeySize {
|
||||
return errECDSAVerification
|
||||
}
|
||||
|
||||
|
||||
r := big.NewInt(0).SetBytes(sig[:m.KeySize])
|
||||
s := big.NewInt(0).SetBytes(sig[m.KeySize:])
|
||||
|
||||
|
||||
// Create hasher
|
||||
if !m.Hash.Available() {
|
||||
return errHashUnavailable
|
||||
}
|
||||
hasher := m.Hash.New()
|
||||
hasher.Write([]byte(signingString))
|
||||
|
||||
|
||||
// Verify the signature
|
||||
if verifystatus := ecdsa.Verify(ecdsaKey, hasher.Sum(nil), r, s); verifystatus == true {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
return errECDSAVerification
|
||||
}
|
||||
default:
|
||||
@@ -187,33 +188,33 @@ func (m *signingMethod) Sign(signingString string, key interface{}) (string, err
|
||||
if !m.Hash.Available() {
|
||||
return "", errHashUnavailable
|
||||
}
|
||||
|
||||
|
||||
hasher := hmac.New(m.Hash.New, keyBytes)
|
||||
hasher.Write([]byte(signingString))
|
||||
|
||||
|
||||
return encodeSegment(hasher.Sum(nil)), nil
|
||||
}
|
||||
|
||||
|
||||
return "", errInvalidKeyType
|
||||
}
|
||||
case "RS256", "RS384", "RS512":
|
||||
{
|
||||
var rsaKey *rsa.PrivateKey
|
||||
var ok bool
|
||||
|
||||
|
||||
// Validate type of key
|
||||
if rsaKey, ok = key.(*rsa.PrivateKey); !ok {
|
||||
return "", errInvalidKey
|
||||
}
|
||||
|
||||
|
||||
// Create the hasher
|
||||
if !m.Hash.Available() {
|
||||
return "", errHashUnavailable
|
||||
}
|
||||
|
||||
|
||||
hasher := m.Hash.New()
|
||||
hasher.Write([]byte(signingString))
|
||||
|
||||
|
||||
// Sign the string and return the encoded bytes
|
||||
if sigBytes, err := rsa.SignPKCS1v15(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil)); err == nil {
|
||||
return encodeSegment(sigBytes), nil
|
||||
@@ -231,41 +232,41 @@ func (m *signingMethod) Sign(signingString string, key interface{}) (string, err
|
||||
default:
|
||||
return "", errInvalidKeyType
|
||||
}
|
||||
|
||||
|
||||
// Create the hasher
|
||||
if !m.Hash.Available() {
|
||||
return "", errHashUnavailable
|
||||
}
|
||||
|
||||
|
||||
hasher := m.Hash.New()
|
||||
hasher.Write([]byte(signingString))
|
||||
|
||||
|
||||
// Sign the string and return r, s
|
||||
if r, s, err := ecdsa.Sign(rand.Reader, ecdsaKey, hasher.Sum(nil)); err == nil {
|
||||
curveBits := ecdsaKey.Curve.Params().BitSize
|
||||
|
||||
|
||||
if m.CurveBits != curveBits {
|
||||
return "", errInvalidKey
|
||||
}
|
||||
|
||||
|
||||
keyBytes := curveBits / 8
|
||||
if curveBits%8 > 0 {
|
||||
keyBytes++
|
||||
}
|
||||
|
||||
|
||||
// We serialize the outpus (r and s) into big-endian byte arrays and pad
|
||||
// them with zeros on the left to make sure the sizes work out. Both arrays
|
||||
// must be keyBytes long, and the output must be 2*keyBytes long.
|
||||
rBytes := r.Bytes()
|
||||
rBytesPadded := make([]byte, keyBytes)
|
||||
copy(rBytesPadded[keyBytes-len(rBytes):], rBytes)
|
||||
|
||||
|
||||
sBytes := s.Bytes()
|
||||
sBytesPadded := make([]byte, keyBytes)
|
||||
copy(sBytesPadded[keyBytes-len(sBytes):], sBytes)
|
||||
|
||||
|
||||
out := append(rBytesPadded, sBytesPadded...)
|
||||
|
||||
|
||||
return encodeSegment(out), nil
|
||||
} else {
|
||||
return "", err
|
||||
@@ -290,7 +291,7 @@ func decodeSegment(seg string) ([]byte, error) {
|
||||
if l := len(seg) % 4; l > 0 {
|
||||
seg += strings.Repeat("=", 4-l)
|
||||
}
|
||||
|
||||
|
||||
return base64.URLEncoding.DecodeString(seg)
|
||||
}
|
||||
|
||||
@@ -299,16 +300,16 @@ func encodeSegment(seg []byte) string {
|
||||
return strings.TrimRight(base64.URLEncoding.EncodeToString(seg), "=")
|
||||
}
|
||||
|
||||
//ParseRSAPublicKeyFromPEM parse PEM encoded PKCS1 or PKCS8 public key
|
||||
// ParseRSAPublicKeyFromPEM parse PEM encoded PKCS1 or PKCS8 public key
|
||||
func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) {
|
||||
var err error
|
||||
|
||||
|
||||
// parse PEM block
|
||||
var block *pem.Block
|
||||
if block, _ = pem.Decode(key); block == nil {
|
||||
return nil, errKeyMustBePEMEncoded
|
||||
}
|
||||
|
||||
|
||||
// parse the key
|
||||
var parsedKey interface{}
|
||||
if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil {
|
||||
@@ -318,26 +319,26 @@ func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var pkey *rsa.PublicKey
|
||||
var ok bool
|
||||
if pkey, ok = parsedKey.(*rsa.PublicKey); !ok {
|
||||
return nil, errNotRSAPublicKey
|
||||
}
|
||||
|
||||
|
||||
return pkey, nil
|
||||
}
|
||||
|
||||
//ParseECPublicKeyFromPEM parse PEM encoded PKCS1 or PKCS8 public key
|
||||
// ParseECPublicKeyFromPEM parse PEM encoded PKCS1 or PKCS8 public key
|
||||
func ParseECPublicKeyFromPEM(key []byte) (*ecdsa.PublicKey, error) {
|
||||
var err error
|
||||
|
||||
|
||||
// parse PEM block
|
||||
var block *pem.Block
|
||||
if block, _ = pem.Decode(key); block == nil {
|
||||
return nil, errKeyMustBePEMEncoded
|
||||
}
|
||||
|
||||
|
||||
// parse the key
|
||||
var parsedKey interface{}
|
||||
if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil {
|
||||
@@ -347,13 +348,13 @@ func ParseECPublicKeyFromPEM(key []byte) (*ecdsa.PublicKey, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var pkey *ecdsa.PublicKey
|
||||
var ok bool
|
||||
if pkey, ok = parsedKey.(*ecdsa.PublicKey); !ok {
|
||||
return nil, errNotECPublicKey
|
||||
}
|
||||
|
||||
|
||||
return pkey, nil
|
||||
}
|
||||
|
||||
@@ -365,7 +366,7 @@ func b64Decode(input string) (string, error) {
|
||||
padlen := 4 - remainder
|
||||
input = input + strings.Repeat("=", padlen)
|
||||
}
|
||||
// 将原字符串中的"_","-"分别用"/"和"+"替换
|
||||
// 将原字符串中的"_","-"分别用"/"和"+"替换x
|
||||
input = strings.Replace(strings.Replace(input, "_", "/", -1), "-", "+", -1)
|
||||
result, err := base64.StdEncoding.DecodeString(input)
|
||||
return string(result), err
|
||||
@@ -377,7 +378,7 @@ func tokenize(token string) []string {
|
||||
if len(parts) == 3 {
|
||||
return parts
|
||||
}
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -396,7 +397,7 @@ func decodeToken(token string) (*jwtToken, error) {
|
||||
if err != nil {
|
||||
return nil, errors.New("[jwt_auth] Invalid base64 encoded JSON")
|
||||
}
|
||||
|
||||
|
||||
if err = json.Unmarshal([]byte(headerD64), &header); err != nil {
|
||||
return nil, errors.New("[jwt_auth] Invalid JSON")
|
||||
}
|
||||
@@ -426,9 +427,9 @@ func decodeToken(token string) (*jwtToken, error) {
|
||||
return &jwtToken{Token: token, Header64: header64, Claims64: claims64, Signature64: signature64, Header: header, Claims: claims, Signature: signature}, nil
|
||||
}
|
||||
|
||||
//verifySignature 验证签名
|
||||
// verifySignature 验证签名
|
||||
func verifySignature(token *jwtToken, key string) error {
|
||||
|
||||
|
||||
var k interface{}
|
||||
switch token.Header["alg"].(string) {
|
||||
case "HS256", "HS384", "HS512":
|
||||
@@ -459,23 +460,23 @@ func verifySignature(token *jwtToken, key string) error {
|
||||
return newSigningMethod(token.Header["alg"].(string)).Verify(token.Header64+"."+token.Claims64, token.Signature64, k)
|
||||
}
|
||||
|
||||
//verifyRegisteredClaims 验证签发字段
|
||||
// verifyRegisteredClaims 验证签发字段
|
||||
func verifyRegisteredClaims(token *jwtToken, claimsToVerify []string) error {
|
||||
if claimsToVerify == nil {
|
||||
claimsToVerify = []string{}
|
||||
}
|
||||
|
||||
|
||||
for _, claimName := range claimsToVerify {
|
||||
var claim int64 = 0
|
||||
if _, ok := token.Claims[claimName]; ok {
|
||||
|
||||
|
||||
if typeOfData(token.Claims[claimName]) == reflect.Float64 {
|
||||
claimFloat64, success := token.Claims[claimName].(float64)
|
||||
if success {
|
||||
claim = int64(claimFloat64)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
if claim < 1 {
|
||||
return errors.New("[jwt_auth] " + claimName + " must be a number")
|
||||
@@ -496,7 +497,7 @@ func verifyRegisteredClaims(token *jwtToken, claimsToVerify []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
//获取数据的类型
|
||||
// 获取数据的类型
|
||||
func typeOfData(data interface{}) reflect.Kind {
|
||||
value := reflect.ValueOf(data)
|
||||
valueType := value.Kind()
|
||||
@@ -506,13 +507,13 @@ func typeOfData(data interface{}) reflect.Kind {
|
||||
return valueType
|
||||
}
|
||||
|
||||
//doJWTAuthentication 进行JWT鉴权
|
||||
// doJWTAuthentication 进行JWT鉴权
|
||||
func (j *jwt) doJWTAuthentication(tokenStr string) (string, error) {
|
||||
token, err := decodeToken(tokenStr)
|
||||
if err != nil {
|
||||
return "", errors.New("Bad token; " + err.Error())
|
||||
}
|
||||
|
||||
|
||||
key := ""
|
||||
keyClaimName := "iss"
|
||||
if _, ok := token.Claims[keyClaimName]; ok {
|
||||
@@ -520,11 +521,11 @@ func (j *jwt) doJWTAuthentication(tokenStr string) (string, error) {
|
||||
} else if _, ok = token.Header[keyClaimName]; ok {
|
||||
key = token.Header[keyClaimName].(string)
|
||||
}
|
||||
|
||||
|
||||
if key == "" {
|
||||
return "", errors.New("no mandatory " + keyClaimName + " in claims")
|
||||
}
|
||||
|
||||
|
||||
algorithm := token.Header["alg"].(string)
|
||||
if j.cfg.Algorithm != algorithm {
|
||||
return "", fmt.Errorf("no match algorithm,need %s,now %s", j.cfg.Algorithm, algorithm)
|
||||
@@ -541,20 +542,20 @@ func (j *jwt) doJWTAuthentication(tokenStr string) (string, error) {
|
||||
} else {
|
||||
jwtSecretValue = j.cfg.RsaPublicKey
|
||||
}
|
||||
|
||||
|
||||
if jwtSecretValue == "" {
|
||||
return "", errors.New("invalid key/secret")
|
||||
}
|
||||
|
||||
|
||||
if err = verifySignature(token, jwtSecretValue); err != nil {
|
||||
return "", errors.New("[jwt_auth] Invalid signature")
|
||||
}
|
||||
if err = verifyRegisteredClaims(token, j.cfg.ClaimsToVerify); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
||||
data, _ := json.Marshal(token.Claims)
|
||||
|
||||
|
||||
return getUserByPath(data, j.cfg.Path)
|
||||
}
|
||||
|
||||
|
@@ -27,6 +27,7 @@ type Config struct {
|
||||
PartitionKey string `json:"partition_key" yaml:"partition_key" switch:"partition_type==='hash'"`
|
||||
Type string `json:"type" yaml:"type" enum:"json,line" label:"输出格式"`
|
||||
ContentResize []ContentResize `json:"content_resize" yaml:"content_resize" label:"内容截断配置" switch:"type===json"`
|
||||
Filters []*Filter `json:"filters" yaml:"conditions" label:"过滤条件"`
|
||||
Formatter eosc.FormatterConfig `json:"formatter" yaml:"formatter" label:"格式化配置"`
|
||||
}
|
||||
|
||||
@@ -35,6 +36,15 @@ type ContentResize struct {
|
||||
Suffix string `json:"suffix" label:"匹配标签后缀"`
|
||||
}
|
||||
|
||||
const (
|
||||
equalCondition = "equal"
|
||||
)
|
||||
|
||||
type Filter struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type ProducerConfig struct {
|
||||
Scopes []string `json:"scopes" yaml:"scopes"`
|
||||
Address []string `json:"address" yaml:"address"`
|
||||
@@ -46,6 +56,7 @@ type ProducerConfig struct {
|
||||
Type string `json:"type" yaml:"type"`
|
||||
ContentResize []ContentResize `json:"content_resize" yaml:"content_resize"`
|
||||
Formatter eosc.FormatterConfig `json:"formatter" yaml:"formatter"`
|
||||
Filters []*Filter `json:"filters" yaml:"conditions" label:"过滤条件"`
|
||||
}
|
||||
|
||||
func (c *Config) doCheck() (*ProducerConfig, error) {
|
||||
@@ -115,5 +126,6 @@ func (c *Config) doCheck() (*ProducerConfig, error) {
|
||||
p.ContentResize = conf.ContentResize
|
||||
p.Formatter = conf.Formatter
|
||||
p.Conf = s
|
||||
p.Filters = conf.Filters
|
||||
return p, nil
|
||||
}
|
||||
|
@@ -1,7 +1,12 @@
|
||||
package kafka
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/eolinker/apinto/checker"
|
||||
"github.com/eolinker/eosc/log"
|
||||
|
||||
scope_manager "github.com/eolinker/apinto/scope-manager"
|
||||
|
||||
@@ -14,15 +19,58 @@ import (
|
||||
var _ output.IEntryOutput = (*Output)(nil)
|
||||
var _ eosc.IWorker = (*Output)(nil)
|
||||
|
||||
type filter struct {
|
||||
key string
|
||||
checker.Checker
|
||||
}
|
||||
|
||||
func parseFilters(filters []*Filter) []*filter {
|
||||
result := make([]*filter, 0, len(filters))
|
||||
for _, f := range filters {
|
||||
c, err := checker.Parse(f.Value)
|
||||
if err != nil {
|
||||
log.Errorf("parse filter value(%s) error: %v", f.Value, err)
|
||||
continue
|
||||
}
|
||||
result = append(result, &filter{
|
||||
key: f.Key,
|
||||
Checker: c,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type Output struct {
|
||||
drivers.WorkerBase
|
||||
producer Producer
|
||||
//scopes []string
|
||||
producer Producer
|
||||
filters []*filter
|
||||
config *ProducerConfig
|
||||
isRunning bool
|
||||
}
|
||||
|
||||
func (o *Output) Output(entry eosc.IEntry) error {
|
||||
for _, f := range o.filters {
|
||||
val := entry.Read(f.key)
|
||||
switch v := val.(type) {
|
||||
case string:
|
||||
ok := f.Check(v, true)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
case bool:
|
||||
ok := f.Check(strconv.FormatBool(v), true)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
case int, int64:
|
||||
ok := f.Check(fmt.Sprintf("%d", v), true)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
p := o.producer
|
||||
if p != nil {
|
||||
return p.output(entry)
|
||||
@@ -44,6 +92,7 @@ func (o *Output) Start() error {
|
||||
return err
|
||||
}
|
||||
o.producer = p
|
||||
o.filters = parseFilters(o.config.Filters)
|
||||
scope_manager.Set(o.Id(), o, o.config.Scopes...)
|
||||
return nil
|
||||
}
|
||||
@@ -65,12 +114,13 @@ func (o *Output) Reset(conf interface{}, workers map[eosc.RequireId]eosc.IWorker
|
||||
if p == nil {
|
||||
p = newTProducer(o.config)
|
||||
}
|
||||
err = p.reset(o.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//err = p.reset(o.config)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
o.producer = p
|
||||
}
|
||||
o.filters = parseFilters(cfg.Filters)
|
||||
scope_manager.Set(o.Id(), o, o.config.Scopes...)
|
||||
return nil
|
||||
}
|
||||
|
15
drivers/plugins/strategy/data-mask/driver.go
Normal file
15
drivers/plugins/strategy/data-mask/driver.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package data_mask
|
||||
|
||||
import (
|
||||
"github.com/eolinker/apinto/drivers"
|
||||
"github.com/eolinker/eosc"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
}
|
||||
|
||||
func Create(id, name string, conf *Config, workers map[eosc.RequireId]eosc.IWorker) (eosc.IWorker, error) {
|
||||
return &Strategy{
|
||||
WorkerBase: drivers.Worker(id, name),
|
||||
}, nil
|
||||
}
|
17
drivers/plugins/strategy/data-mask/factory.go
Normal file
17
drivers/plugins/strategy/data-mask/factory.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package data_mask
|
||||
|
||||
import (
|
||||
"github.com/eolinker/apinto/drivers"
|
||||
"github.com/eolinker/eosc"
|
||||
)
|
||||
|
||||
const (
|
||||
Name = "strategy-plugin-data_mask"
|
||||
)
|
||||
|
||||
func Register(register eosc.IExtenderDriverRegister) {
|
||||
register.RegisterExtenderDriver(Name, NewFactory())
|
||||
}
|
||||
func NewFactory() eosc.IExtenderDriverFactory {
|
||||
return drivers.NewFactory[Config](Create)
|
||||
}
|
36
drivers/plugins/strategy/data-mask/strategy.go
Normal file
36
drivers/plugins/strategy/data-mask/strategy.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package data_mask
|
||||
|
||||
import (
|
||||
"github.com/eolinker/apinto/drivers"
|
||||
data_mask_strategy "github.com/eolinker/apinto/drivers/strategy/data-mask-strategy"
|
||||
"github.com/eolinker/eosc"
|
||||
eoscContext "github.com/eolinker/eosc/eocontext"
|
||||
)
|
||||
|
||||
type Strategy struct {
|
||||
drivers.WorkerBase
|
||||
}
|
||||
|
||||
func (s *Strategy) DoFilter(ctx eoscContext.EoContext, next eoscContext.IChain) (err error) {
|
||||
return data_mask_strategy.DoStrategy(ctx, next)
|
||||
}
|
||||
|
||||
func (s *Strategy) Destroy() {
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Strategy) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) Reset(conf interface{}, workers map[eosc.RequireId]eosc.IWorker) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) CheckSkill(skill string) bool {
|
||||
return eoscContext.FilterSkillName == skill
|
||||
}
|
106
drivers/strategy/data-mask-strategy/actuator.go
Normal file
106
drivers/strategy/data-mask-strategy/actuator.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package data_mask_strategy
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
http_service "github.com/eolinker/eosc/eocontext/http-context"
|
||||
|
||||
"github.com/eolinker/apinto/strategy"
|
||||
"github.com/eolinker/eosc/eocontext"
|
||||
)
|
||||
|
||||
var (
|
||||
actuatorSet ActuatorSet
|
||||
)
|
||||
|
||||
func init() {
|
||||
actuator := newtActuator()
|
||||
actuatorSet = actuator
|
||||
}
|
||||
|
||||
type ActuatorSet interface {
|
||||
strategy.IStrategyHandler
|
||||
Set(string, *handler)
|
||||
Del(id string)
|
||||
}
|
||||
|
||||
type tActuator struct {
|
||||
lock sync.RWMutex
|
||||
all map[string]*handler
|
||||
handlers []*handler
|
||||
}
|
||||
|
||||
func (a *tActuator) Destroy() {
|
||||
|
||||
}
|
||||
|
||||
func (a *tActuator) Set(id string, val *handler) {
|
||||
// 调用来源有锁
|
||||
a.all[id] = val
|
||||
a.rebuild()
|
||||
|
||||
}
|
||||
|
||||
func (a *tActuator) Del(id string) {
|
||||
// 调用来源有锁
|
||||
delete(a.all, id)
|
||||
a.rebuild()
|
||||
}
|
||||
|
||||
func (a *tActuator) rebuild() {
|
||||
|
||||
handlers := make([]*handler, 0, len(a.all))
|
||||
for _, h := range a.all {
|
||||
handlers = append(handlers, h)
|
||||
}
|
||||
sort.Slice(handlers, func(i, j int) bool {
|
||||
return handlers[i].priority < handlers[j].priority
|
||||
})
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
a.handlers = handlers
|
||||
}
|
||||
func newtActuator() *tActuator {
|
||||
return &tActuator{
|
||||
all: make(map[string]*handler),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *tActuator) Strategy(ctx eocontext.EoContext, next eocontext.IChain) error {
|
||||
|
||||
httpCtx, err := http_service.Assert(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if next != nil {
|
||||
err = next.DoChain(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
a.lock.RLock()
|
||||
handlers := a.handlers
|
||||
a.lock.RUnlock()
|
||||
//var execHandler *handler
|
||||
for _, h := range handlers {
|
||||
// 匹配Filter
|
||||
if !h.filter.Check(httpCtx) {
|
||||
// 未命中,下一条规则
|
||||
continue
|
||||
}
|
||||
err = h.ResponseExec(httpCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//execHandler = h
|
||||
// 匹配中后,跳出循环
|
||||
break
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func DoStrategy(ctx eocontext.EoContext, next eocontext.IChain) error {
|
||||
return actuatorSet.Strategy(ctx, next)
|
||||
}
|
15
drivers/strategy/data-mask-strategy/config.go
Normal file
15
drivers/strategy/data-mask-strategy/config.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package data_mask_strategy
|
||||
|
||||
import (
|
||||
"github.com/eolinker/apinto/drivers/strategy/data-mask-strategy/mask"
|
||||
"github.com/eolinker/apinto/strategy"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Name string `json:"name" skip:"skip"`
|
||||
Description string `json:"description" skip:"skip"`
|
||||
//Stop bool `json:"stop"`
|
||||
Priority int `json:"priority" label:"优先级" description:"1-999"`
|
||||
Filters strategy.FilterConfig `json:"filters" label:"过滤规则"`
|
||||
DataMask mask.DataMask `json:"data_mask" label:"规则"`
|
||||
}
|
81
drivers/strategy/data-mask-strategy/controller.go
Normal file
81
drivers/strategy/data-mask-strategy/controller.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package data_mask_strategy
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/eolinker/eosc"
|
||||
)
|
||||
|
||||
var (
|
||||
controller = NewController()
|
||||
_ eosc.ISetting = controller
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
profession string
|
||||
driver string
|
||||
all map[string]struct{}
|
||||
}
|
||||
|
||||
func (c *Controller) Store(id string) {
|
||||
c.all[id] = struct{}{}
|
||||
}
|
||||
|
||||
func (c *Controller) Del(id string) {
|
||||
delete(c.all, id)
|
||||
}
|
||||
|
||||
func (c *Controller) ConfigType() reflect.Type {
|
||||
return configType
|
||||
}
|
||||
|
||||
func (c *Controller) Set(conf interface{}) (err error) {
|
||||
return eosc.ErrorUnsupportedKind
|
||||
}
|
||||
|
||||
func (c *Controller) Get() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) Mode() eosc.SettingMode {
|
||||
return eosc.SettingModeBatch
|
||||
}
|
||||
|
||||
func (c *Controller) Check(cfg interface{}) (profession, name, driver, desc string, err error) {
|
||||
conf, ok := cfg.(*Config)
|
||||
if !ok {
|
||||
err = eosc.ErrorConfigIsNil
|
||||
return
|
||||
}
|
||||
if empty(conf.Name) {
|
||||
err = eosc.ErrorConfigFieldUnknown
|
||||
return
|
||||
}
|
||||
err = checkConfig(conf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return c.profession, conf.Name, c.driver, conf.Description, nil
|
||||
|
||||
}
|
||||
func empty(vs ...string) bool {
|
||||
for _, v := range vs {
|
||||
if len(v) == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (c *Controller) AllWorkers() []string {
|
||||
ws := make([]string, 0, len(c.all))
|
||||
for id := range c.all {
|
||||
ws = append(ws, id)
|
||||
}
|
||||
return ws
|
||||
}
|
||||
|
||||
func NewController() *Controller {
|
||||
return &Controller{
|
||||
all: map[string]struct{}{},
|
||||
}
|
||||
}
|
69
drivers/strategy/data-mask-strategy/driver.go
Normal file
69
drivers/strategy/data-mask-strategy/driver.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package data_mask_strategy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/eolinker/apinto/drivers/strategy/data-mask-strategy/mask"
|
||||
"github.com/eolinker/apinto/strategy"
|
||||
"github.com/eolinker/eosc"
|
||||
)
|
||||
|
||||
func checkConfig(conf *Config) error {
|
||||
_, err := strategy.ParseFilter(conf.Filters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(conf.DataMask.Rules) < 1 {
|
||||
return fmt.Errorf("at least one rule is required")
|
||||
}
|
||||
for _, rule := range conf.DataMask.Rules {
|
||||
if rule.Match == nil {
|
||||
return fmt.Errorf("match is required")
|
||||
}
|
||||
if rule.Mask == nil {
|
||||
return fmt.Errorf("mask is required")
|
||||
}
|
||||
if rule.Mask.Begin < 0 {
|
||||
return fmt.Errorf("begin must be greater than or equal to 0")
|
||||
}
|
||||
if rule.Mask.Length < -1 {
|
||||
return fmt.Errorf("length must be greater than or equal to -1")
|
||||
}
|
||||
if rule.Match.Type == mask.MatchInner && (rule.Match.Value == mask.MatchInnerValueDate || rule.Match.Value == mask.MatchInnerValueAmount) && rule.Mask.Type == mask.MaskShuffling {
|
||||
return fmt.Errorf("date and amount cannot be shuffled")
|
||||
}
|
||||
if rule.Mask.Type == mask.MaskReplacement && rule.Mask.Replace == nil {
|
||||
return fmt.Errorf("replace is required")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Check(cfg *Config, workers map[eosc.RequireId]eosc.IWorker) error {
|
||||
|
||||
return checkConfig(cfg)
|
||||
}
|
||||
|
||||
func Create(id, name string, v *Config, workers map[eosc.RequireId]eosc.IWorker) (eosc.IWorker, error) {
|
||||
if err := Check(v, workers); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lg := &executor{
|
||||
id: id,
|
||||
name: name,
|
||||
}
|
||||
|
||||
err := lg.reset(v, workers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
controller.Store(id)
|
||||
return lg, nil
|
||||
}
|
75
drivers/strategy/data-mask-strategy/exector.go
Normal file
75
drivers/strategy/data-mask-strategy/exector.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package data_mask_strategy
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/eolinker/eosc"
|
||||
)
|
||||
|
||||
var (
|
||||
_ eosc.IWorker = (*executor)(nil)
|
||||
_ eosc.IWorkerDestroy = (*executor)(nil)
|
||||
)
|
||||
|
||||
type executor struct {
|
||||
id string
|
||||
name string
|
||||
config *Config
|
||||
//handler *handler
|
||||
}
|
||||
|
||||
func (l *executor) Destroy() error {
|
||||
controller.Del(l.id)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *executor) Id() string {
|
||||
return l.id
|
||||
}
|
||||
|
||||
func (l *executor) Start() error {
|
||||
//if l.isRunning == 0 {
|
||||
// l.isRunning = 1
|
||||
//}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *executor) Reset(v interface{}, workers map[eosc.RequireId]eosc.IWorker) error {
|
||||
conf, ok := v.(*Config)
|
||||
if !ok {
|
||||
return eosc.ErrorConfigType
|
||||
}
|
||||
return l.reset(conf, workers)
|
||||
}
|
||||
func (l *executor) reset(conf *Config, workers map[eosc.RequireId]eosc.IWorker) error {
|
||||
|
||||
confCore := conf
|
||||
if reflect.DeepEqual(l.config, confCore) {
|
||||
return nil
|
||||
}
|
||||
|
||||
h, err := newHandler(confCore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.config = confCore
|
||||
//l.handler = h
|
||||
//if l.isRunning != 0 {
|
||||
actuatorSet.Set(l.id, h)
|
||||
//}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *executor) Stop() error {
|
||||
//if l.isRunning != 0 {
|
||||
// l.isRunning = 0
|
||||
//}
|
||||
actuatorSet.Del(l.id)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *executor) CheckSkill(skill string) bool {
|
||||
return false
|
||||
}
|
50
drivers/strategy/data-mask-strategy/factory.go
Normal file
50
drivers/strategy/data-mask-strategy/factory.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package data_mask_strategy
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/eolinker/apinto/drivers/strategy/data-mask-strategy/mask/inner"
|
||||
json_path "github.com/eolinker/apinto/drivers/strategy/data-mask-strategy/mask/json-path"
|
||||
"github.com/eolinker/apinto/drivers/strategy/data-mask-strategy/mask/keyword"
|
||||
"github.com/eolinker/apinto/drivers/strategy/data-mask-strategy/mask/regex"
|
||||
|
||||
"github.com/eolinker/apinto/drivers"
|
||||
"github.com/eolinker/eosc"
|
||||
"github.com/eolinker/eosc/setting"
|
||||
)
|
||||
|
||||
const Name = "strategy-data_mask"
|
||||
|
||||
var (
|
||||
configType = reflect.TypeOf((*Config)(nil))
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// Register 注册http路由驱动工厂
|
||||
func Register(register eosc.IExtenderDriverRegister) {
|
||||
register.RegisterExtenderDriver(Name, newFactory())
|
||||
setting.RegisterSetting("strategies-data_mask", controller)
|
||||
}
|
||||
|
||||
type factory struct {
|
||||
eosc.IExtenderDriverFactory
|
||||
}
|
||||
|
||||
func newFactory() eosc.IExtenderDriverFactory {
|
||||
return &factory{
|
||||
IExtenderDriverFactory: drivers.NewFactory[Config](Create, Check),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *factory) Create(profession string, name string, label string, desc string, params map[string]interface{}) (eosc.IExtenderDriver, error) {
|
||||
once.Do(func() {
|
||||
inner.Register()
|
||||
json_path.Register()
|
||||
keyword.Register()
|
||||
regex.Register()
|
||||
})
|
||||
controller.driver = name
|
||||
controller.profession = profession
|
||||
return f.IExtenderDriverFactory.Create(profession, name, label, desc, params)
|
||||
}
|
93
drivers/strategy/data-mask-strategy/handler.go
Normal file
93
drivers/strategy/data-mask-strategy/handler.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package data_mask_strategy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/eolinker/apinto/drivers/strategy/data-mask-strategy/mask"
|
||||
"github.com/eolinker/apinto/strategy"
|
||||
"github.com/eolinker/eosc/eocontext"
|
||||
http_context "github.com/eolinker/eosc/eocontext/http-context"
|
||||
"github.com/eolinker/eosc/log"
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
name string
|
||||
filter strategy.IFilter
|
||||
priority int
|
||||
maskExecutors []mask.IMaskDriver
|
||||
}
|
||||
|
||||
func newHandler(conf *Config) (*handler, error) {
|
||||
filter, err := strategy.ParseFilter(conf.Filters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
maskExecutors := make([]mask.IMaskDriver, 0, len(conf.DataMask.Rules))
|
||||
for _, rule := range conf.DataMask.Rules {
|
||||
maskFunc, err := mask.GenMaskFunc(rule.Mask)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fac, has := mask.GetMaskFactory(rule.Match.Type)
|
||||
if !has {
|
||||
return nil, fmt.Errorf("match type not found: %s", rule.Match.Type)
|
||||
}
|
||||
e, err := fac.Create(rule, maskFunc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
maskExecutors = append(maskExecutors, e)
|
||||
}
|
||||
return &handler{
|
||||
name: conf.Name,
|
||||
filter: filter,
|
||||
priority: conf.Priority,
|
||||
maskExecutors: maskExecutors,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *handler) RequestExec(ctx eocontext.EoContext) error {
|
||||
|
||||
httpCtx, err := http_context.Assert(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body, err := httpCtx.Proxy().Body().RawBody()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
contentType := httpCtx.Proxy().ContentType()
|
||||
|
||||
for _, e := range h.maskExecutors {
|
||||
body, err = e.Exec(body)
|
||||
if err != nil {
|
||||
log.Errorf("request mask exec error: (%v),handler name: (%s),rule: (%s)", err, h.name, e.String())
|
||||
continue
|
||||
}
|
||||
}
|
||||
httpCtx.Proxy().Body().SetRaw(contentType, body)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *handler) ResponseExec(ctx eocontext.EoContext) error {
|
||||
httpCtx, err := http_context.Assert(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body := httpCtx.Response().GetBody()
|
||||
if len(body) < 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, e := range h.maskExecutors {
|
||||
body, err = e.Exec(body)
|
||||
if err != nil {
|
||||
log.Errorf("response mask exec error: (%v),handler name: (%s),rule: (%s)", err, h.name, e.String())
|
||||
continue
|
||||
}
|
||||
}
|
||||
httpCtx.Response().SetBody(body)
|
||||
return nil
|
||||
}
|
22
drivers/strategy/data-mask-strategy/mask/entity.go
Normal file
22
drivers/strategy/data-mask-strategy/mask/entity.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package mask
|
||||
|
||||
type DataMask struct {
|
||||
Rules []*Rule `json:"rules"`
|
||||
}
|
||||
|
||||
type Rule struct {
|
||||
Match *BasicItem `json:"match"`
|
||||
Mask *Mask `json:"mask"`
|
||||
}
|
||||
|
||||
type BasicItem struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type Mask struct {
|
||||
Type string `json:"type"`
|
||||
Begin int `json:"begin"`
|
||||
Length int `json:"length"`
|
||||
Replace *BasicItem `json:"replace"`
|
||||
}
|
63
drivers/strategy/data-mask-strategy/mask/inner/bank-card.go
Normal file
63
drivers/strategy/data-mask-strategy/mask/inner/bank-card.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package inner
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"unicode"
|
||||
|
||||
"github.com/eolinker/apinto/drivers/strategy/data-mask-strategy/mask"
|
||||
)
|
||||
|
||||
var cardRegex = regexp.MustCompile(`[a-zA-Z0-9]{0,1}\d{16,19}[a-zA-Z0-9]{0,1}`)
|
||||
|
||||
func validateLuhn(cardNumber string) bool {
|
||||
var sum int
|
||||
// 反向遍历卡号字符串
|
||||
nDigits := len(cardNumber)
|
||||
parity := nDigits % 2
|
||||
|
||||
for i, r := range cardNumber {
|
||||
if !unicode.IsDigit(r) {
|
||||
return false // 如果包含非数字字符,则为无效卡号
|
||||
}
|
||||
|
||||
digit := int(r - '0') // 将字符转为数字
|
||||
|
||||
// 根据Luhn算法,当索引与奇偶性不同时,数字需要翻倍
|
||||
if i%2 == parity {
|
||||
digit *= 2
|
||||
}
|
||||
|
||||
// 如果数字大于9,减去9
|
||||
if digit > 9 {
|
||||
digit -= 9
|
||||
}
|
||||
|
||||
sum += digit
|
||||
}
|
||||
|
||||
// 卡号有效性取决于sum是否可以被10整除
|
||||
return sum%10 == 0
|
||||
}
|
||||
|
||||
func newBankCardMaskDriver(maskFunc mask.MaskFunc) mask.IInnerMask {
|
||||
return newCommonMaskDriver(func(origin string) string {
|
||||
// 判断是否是合法的银行卡号,若非合法,则不做处理,避免误判
|
||||
if !validateLuhn(origin) {
|
||||
return origin
|
||||
}
|
||||
size := len(origin)
|
||||
if size > 19 {
|
||||
return origin
|
||||
}
|
||||
arr := []byte{
|
||||
origin[0],
|
||||
origin[1],
|
||||
}
|
||||
for _, o := range arr {
|
||||
if o < 48 || o > 57 {
|
||||
return origin
|
||||
}
|
||||
}
|
||||
return maskFunc(origin)
|
||||
}, []*regexp.Regexp{cardRegex})
|
||||
}
|
63
drivers/strategy/data-mask-strategy/mask/inner/id-card.go
Normal file
63
drivers/strategy/data-mask-strategy/mask/inner/id-card.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package inner
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/eolinker/apinto/drivers/strategy/data-mask-strategy/mask"
|
||||
)
|
||||
|
||||
var (
|
||||
idCardRegx = regexp.MustCompile(`[a-zA-Z0-9]{0,1}[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx][a-zA-Z0-9]{0,1}`)
|
||||
//idCardRegx = regexp.MustCompile(`[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]`)
|
||||
)
|
||||
|
||||
// 权重系数
|
||||
var weight = []int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}
|
||||
|
||||
// 校验码对应表
|
||||
var checkCodeMap = []string{"1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"}
|
||||
|
||||
// 验证身份证号的最后一位校验码
|
||||
func validateIDCard(id string) bool {
|
||||
// 身份证必须为18位
|
||||
if len(id) != 18 {
|
||||
return false
|
||||
}
|
||||
|
||||
// 前17位必须都是数字
|
||||
id17 := id[:17]
|
||||
if _, err := strconv.Atoi(id17); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 计算加权和
|
||||
sum := 0
|
||||
for i := 0; i < 17; i++ {
|
||||
num, _ := strconv.Atoi(string(id[i]))
|
||||
sum += num * weight[i]
|
||||
}
|
||||
|
||||
// 求模11的余数
|
||||
mod := sum % 11
|
||||
|
||||
// 校验码
|
||||
checkCode := checkCodeMap[mod]
|
||||
|
||||
// 比较第18位是否匹配
|
||||
return strings.ToUpper(string(id[17])) == checkCode
|
||||
}
|
||||
|
||||
func newIDCardMaskDriver(maskFunc mask.MaskFunc) mask.IInnerMask {
|
||||
return newCommonMaskDriver(func(origin string) string {
|
||||
if len(origin) != 18 {
|
||||
return origin
|
||||
}
|
||||
// 判断是否是合法的身份证号,若非合法,则不做处理,避免误判
|
||||
if !validateIDCard(origin) {
|
||||
return origin
|
||||
}
|
||||
return maskFunc(origin)
|
||||
}, []*regexp.Regexp{idCardRegx})
|
||||
}
|
124
drivers/strategy/data-mask-strategy/mask/inner/inner.go
Normal file
124
drivers/strategy/data-mask-strategy/mask/inner/inner.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package inner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/eolinker/apinto/drivers/strategy/data-mask-strategy/mask"
|
||||
)
|
||||
|
||||
func Register() {
|
||||
mask.RegisterMaskFactory(mask.MatchInner, &factory{})
|
||||
}
|
||||
|
||||
type factory struct {
|
||||
}
|
||||
|
||||
func (i *factory) Create(cfg *mask.Rule, maskFunc mask.MaskFunc) (mask.IMaskDriver, error) {
|
||||
return newDriver(cfg, maskFunc)
|
||||
}
|
||||
|
||||
var _ mask.IMaskDriver = (*driver)(nil)
|
||||
|
||||
type driver struct {
|
||||
value string
|
||||
maskCfg *mask.Mask
|
||||
mask.IInnerMask
|
||||
}
|
||||
|
||||
func newDriver(cfg *mask.Rule, maskFunc mask.MaskFunc) (mask.IMaskDriver, error) {
|
||||
var innerMask mask.IInnerMask
|
||||
switch cfg.Match.Value {
|
||||
case mask.MatchInnerValueName:
|
||||
innerMask = NewNameMaskDriver(maskFunc)
|
||||
case mask.MatchInnerValuePhone:
|
||||
innerMask = newPhoneMaskDriver(maskFunc)
|
||||
case mask.MatchInnerValueIDCard:
|
||||
innerMask = newIDCardMaskDriver(maskFunc)
|
||||
case mask.MatchInnerValueBankCard:
|
||||
innerMask = newBankCardMaskDriver(maskFunc)
|
||||
case mask.MatchInnerValueAmount:
|
||||
innerMask = newAmountMaskDriver(maskFunc)
|
||||
case mask.MatchInnerValueDate:
|
||||
innerMask = newDateMaskDriver(maskFunc)
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid inner value: %s", cfg.Match.Value)
|
||||
}
|
||||
return &driver{
|
||||
value: cfg.Match.Value,
|
||||
maskCfg: cfg.Mask,
|
||||
IInnerMask: innerMask,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (k *driver) Exec(body []byte) ([]byte, error) {
|
||||
if k.IInnerMask == nil {
|
||||
return body, nil
|
||||
}
|
||||
return k.IInnerMask.Exec(body)
|
||||
}
|
||||
|
||||
func (k *driver) String() string {
|
||||
return fmt.Sprintf("mask driver: inner,value: %s,detail: %v", k.value, k.maskCfg)
|
||||
}
|
||||
|
||||
var (
|
||||
moneyRegex = regexp.MustCompile(`^(-?\d+)(\.\d{1,2})?$`)
|
||||
dateTimeRegex = regexp.MustCompile(`^(\d{4})[-/.](0[1-9]|1[0-2])[-/.](0[1-9]|[12][0-9]|3[01])(?:[\sT](\d{2}):([0-5][0-9])(:([0-5][0-9]))?)?$`)
|
||||
)
|
||||
|
||||
func newAmountMaskDriver(maskFunc mask.MaskFunc) mask.IInnerMask {
|
||||
return newCommonMaskDriver(maskFunc, []*regexp.Regexp{moneyRegex})
|
||||
}
|
||||
|
||||
func newDateMaskDriver(maskFunc mask.MaskFunc) mask.IInnerMask {
|
||||
return newCommonMaskDriver(maskFunc, []*regexp.Regexp{dateTimeRegex})
|
||||
}
|
||||
|
||||
type commonMaskDriver struct {
|
||||
mask.MaskFunc
|
||||
regexps []*regexp.Regexp
|
||||
}
|
||||
|
||||
func newCommonMaskDriver(maskFunc mask.MaskFunc, regexps []*regexp.Regexp) *commonMaskDriver {
|
||||
return &commonMaskDriver{MaskFunc: maskFunc, regexps: regexps}
|
||||
}
|
||||
|
||||
func (i *commonMaskDriver) Exec(body []byte) ([]byte, error) {
|
||||
if i.MaskFunc == nil || i.regexps == nil {
|
||||
return body, nil
|
||||
}
|
||||
for _, re := range i.regexps {
|
||||
|
||||
body = re.ReplaceAllFunc(body, func(bytes []byte) []byte {
|
||||
return []byte(i.MaskFunc(string(bytes)))
|
||||
})
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// 定义递归函数来遍历并修改包含 `key` 字段的部分
|
||||
func traverseAndModify(data interface{}, keys []string, f mask.MaskFunc) {
|
||||
// 根据不同类型处理
|
||||
switch value := data.(type) {
|
||||
case map[string]interface{}:
|
||||
// 如果是 map,检查是否包含指定字段
|
||||
for _, key := range keys {
|
||||
if val, exist := value[key]; exist {
|
||||
if valStr, ok := val.(string); ok {
|
||||
value[key] = f(valStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 继续遍历嵌套的字段
|
||||
for _, v := range value {
|
||||
traverseAndModify(v, keys, f)
|
||||
}
|
||||
case []interface{}:
|
||||
// 如果是数组,遍历每个元素
|
||||
for _, v := range value {
|
||||
traverseAndModify(v, keys, f)
|
||||
}
|
||||
}
|
||||
}
|
246
drivers/strategy/data-mask-strategy/mask/inner/inner_test.go
Normal file
246
drivers/strategy/data-mask-strategy/mask/inner/inner_test.go
Normal file
@@ -0,0 +1,246 @@
|
||||
package inner
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func mockMaskFunc(input string) string {
|
||||
if input == "" {
|
||||
return ""
|
||||
}
|
||||
return "MASKED"
|
||||
}
|
||||
|
||||
func TestNameMaskDriver(t *testing.T) {
|
||||
driver := NewNameMaskDriver(mockMaskFunc)
|
||||
|
||||
tests := []struct {
|
||||
input string
|
||||
output string
|
||||
}{
|
||||
{`{"name": "John Doe"}`, `{"name":"MASKED"}`},
|
||||
{`{"cname": "Jane Doe"}`, `{"cname":"MASKED"}`},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
masked, err := driver.Exec([]byte(tt.input))
|
||||
assert.NoError(t, err)
|
||||
assert.JSONEq(t, tt.output, string(masked))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPhoneMaskDriver(t *testing.T) {
|
||||
driver := newPhoneMaskDriver(mockMaskFunc)
|
||||
|
||||
tests := []struct {
|
||||
input string
|
||||
output string
|
||||
}{
|
||||
{`12345678901`, "12345678901"},
|
||||
{`13456278901`, "MASKED"},
|
||||
{`19876543210`, "MASKED"},
|
||||
{`198765432102`, "198765432102"},
|
||||
{`19876543210a`, "19876543210a"},
|
||||
|
||||
{"normal text", "normal text"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
masked, err := driver.Exec([]byte(tt.input))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.output, string(masked))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIDCardMaskDriver(t *testing.T) {
|
||||
driver := newIDCardMaskDriver(mockMaskFunc)
|
||||
|
||||
tests := []struct {
|
||||
input string
|
||||
output string
|
||||
}{
|
||||
{`12345678901234567X`, "12345678901234567X"}, // Assuming valid ID for mock
|
||||
{`invalid-idformat`, "invalid-idformat"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
masked, err := driver.Exec([]byte(tt.input))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.output, string(masked))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBankCardMaskDriver(t *testing.T) {
|
||||
driver := newBankCardMaskDriver(mockMaskFunc)
|
||||
|
||||
tests := []struct {
|
||||
input string
|
||||
output string
|
||||
}{
|
||||
{`1234567890123456`, "1234567890123456"}, // Assuming valid card number for mock
|
||||
{`invalid-card`, "invalid-card"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
masked, err := driver.Exec([]byte(tt.input))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.output, string(masked))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAmountMaskDriver(t *testing.T) {
|
||||
driver := newAmountMaskDriver(mockMaskFunc)
|
||||
|
||||
tests := []struct {
|
||||
input string
|
||||
output string
|
||||
}{
|
||||
{`123.45`, "MASKED"}, // Assuming this is valid for testing
|
||||
{`not-amount`, "not-amount"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
masked, err := driver.Exec([]byte(tt.input))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.output, string(masked))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDateMaskDriver(t *testing.T) {
|
||||
driver := newDateMaskDriver(mockMaskFunc)
|
||||
|
||||
tests := []struct {
|
||||
input string
|
||||
output string
|
||||
}{
|
||||
{`2023-01-01`, "MASKED"}, // Assuming this is valid for testing
|
||||
{`not-date`, "not-date"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
masked, err := driver.Exec([]byte(tt.input))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.output, string(masked))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameMaskDriver_LongText(t *testing.T) {
|
||||
driver := NewNameMaskDriver(mockMaskFunc)
|
||||
|
||||
tests := []struct {
|
||||
input string
|
||||
output string
|
||||
}{
|
||||
{`{"name": "Johnathon Maximillian Doe the Third"}`, `{"name":"MASKED"}`},
|
||||
{`{"normal_text": "This is a sentence with a name Johnathan Does embedded"}`, `{"normal_text":"This is a sentence with a name Johnathan Does embedded"}`},
|
||||
{`{"name": "", "cname": ""}`, `{"name":"", "cname":""}`},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
masked, err := driver.Exec([]byte(tt.input))
|
||||
assert.NoError(t, err)
|
||||
assert.JSONEq(t, tt.output, string(masked))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPhoneMaskDriver_LongText(t *testing.T) {
|
||||
driver := newPhoneMaskDriver(mockMaskFunc)
|
||||
|
||||
tests := []struct {
|
||||
input string
|
||||
output string
|
||||
}{
|
||||
{strings.Repeat("1", 50) + "3987654321" + strings.Repeat("0", 50), strings.Repeat("1", 50) + "3987654321" + strings.Repeat("0", 50)},
|
||||
{"Contact numbers: 13987654321, 15800001111", "Contact numbers: MASKED, MASKED"},
|
||||
{strings.Repeat("normal-text", 100), strings.Repeat("normal-text", 100)},
|
||||
{"Dialing sequence: 08005555555", "Dialing sequence: 08005555555"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
masked, err := driver.Exec([]byte(tt.input))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.output, string(masked))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIDCardMaskDriver_LongText(t *testing.T) {
|
||||
driver := newIDCardMaskDriver(mockMaskFunc)
|
||||
|
||||
tests := []struct {
|
||||
input string
|
||||
output string
|
||||
}{
|
||||
{strings.Repeat("1", 50) + "12345678901234567X" + strings.Repeat("X", 50), strings.Repeat("1", 50) + "12345678901234567X" + strings.Repeat("X", 50)},
|
||||
{strings.Repeat("normal-text", 100), strings.Repeat("normal-text", 100)},
|
||||
{"Incorrect format: 123-456-789-00", "Incorrect format: 123-456-789-00"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
masked, err := driver.Exec([]byte(tt.input))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.output, string(masked))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBankCardMaskDriver_LongText(t *testing.T) {
|
||||
driver := newBankCardMaskDriver(mockMaskFunc)
|
||||
|
||||
tests := []struct {
|
||||
input string
|
||||
output string
|
||||
}{
|
||||
{strings.Repeat("1", 50) + "1234567890123456" + strings.Repeat("9", 50), strings.Repeat("1", 50) + "1234567890123456" + strings.Repeat("9", 50)},
|
||||
{"Valid card numbers: dasdw, 8765432187654321", "Valid card numbers: MASKED, MASKED"},
|
||||
{strings.Repeat("normal-text", 100), strings.Repeat("normal-text", 100)},
|
||||
{"Fake card number: 1111-2222-3333-4444", "Fake card number: 1111-2222-3333-4444"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
masked, err := driver.Exec([]byte(tt.input))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.output, string(masked))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAmountMaskDriver_LongText(t *testing.T) {
|
||||
driver := newAmountMaskDriver(mockMaskFunc)
|
||||
|
||||
tests := []struct {
|
||||
input string
|
||||
output string
|
||||
}{
|
||||
{"Invoice total: $1234567890.99", "MASKED"},
|
||||
{"Transaction amounts: $12345.67, fee: $0.02", "MASKED, fee: MASKED"},
|
||||
{strings.Repeat("normal-text", 100), strings.Repeat("normal-text", 100)},
|
||||
{"Plain text values: twenty five dollars", "Plain text values: twenty five dollars"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
masked, err := driver.Exec([]byte(tt.input))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.output, string(masked))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDateMaskDriver_LongText(t *testing.T) {
|
||||
driver := newDateMaskDriver(mockMaskFunc)
|
||||
|
||||
tests := []struct {
|
||||
input string
|
||||
output string
|
||||
}{
|
||||
{"Event date is 2077-12-31 extended overlap text", "MASKED extended overlap text"},
|
||||
{"Date strings: 2025-09-24T13:45:00, obsolete: 1912-04-15T00:00:00", "MASKED, obsolete: MASKED"},
|
||||
{strings.Repeat("normal-text", 100), strings.Repeat("normal-text", 100)},
|
||||
{"Formatted text: the year nineteen ninety nine", "Formatted text: the year nineteen ninety nine"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
masked, err := driver.Exec([]byte(tt.input))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.output, string(masked))
|
||||
}
|
||||
}
|
32
drivers/strategy/data-mask-strategy/mask/inner/name.go
Normal file
32
drivers/strategy/data-mask-strategy/mask/inner/name.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package inner
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/eolinker/apinto/drivers/strategy/data-mask-strategy/mask"
|
||||
)
|
||||
|
||||
type nameMaskDriver struct {
|
||||
maskFunc mask.MaskFunc
|
||||
}
|
||||
|
||||
func NewNameMaskDriver(maskFunc mask.MaskFunc) mask.IInnerMask {
|
||||
return &nameMaskDriver{maskFunc: maskFunc}
|
||||
}
|
||||
|
||||
func (i *nameMaskDriver) Exec(body []byte) ([]byte, error) {
|
||||
if i.maskFunc == nil {
|
||||
// 未设置脱敏方法,不做处理
|
||||
return body, nil
|
||||
}
|
||||
var jsonData interface{}
|
||||
err := json.Unmarshal(body, &jsonData)
|
||||
if err == nil {
|
||||
traverseAndModify(jsonData, []string{
|
||||
"name", "cname",
|
||||
}, i.maskFunc)
|
||||
|
||||
return json.Marshal(jsonData)
|
||||
}
|
||||
return body, nil
|
||||
}
|
37
drivers/strategy/data-mask-strategy/mask/inner/phone.go
Normal file
37
drivers/strategy/data-mask-strategy/mask/inner/phone.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package inner
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/eolinker/apinto/drivers/strategy/data-mask-strategy/mask"
|
||||
)
|
||||
|
||||
var (
|
||||
phoneRegx = regexp.MustCompile(`1[3-9]\d{9}`)
|
||||
fatherPhoneRegx = regexp.MustCompile(`[a-zA-Z0-9]{0,1}\d{11}[a-zA-Z0-9]{0,1}`)
|
||||
)
|
||||
|
||||
func newPhoneMaskDriver(maskFunc mask.MaskFunc) mask.IInnerMask {
|
||||
return &phoneMaskDriver{
|
||||
MaskFunc: maskFunc,
|
||||
}
|
||||
}
|
||||
|
||||
type phoneMaskDriver struct {
|
||||
mask.MaskFunc
|
||||
}
|
||||
|
||||
func (i *phoneMaskDriver) Exec(body []byte) ([]byte, error) {
|
||||
if i.MaskFunc == nil {
|
||||
return body, nil
|
||||
}
|
||||
|
||||
body = fatherPhoneRegx.ReplaceAllFunc(body, func(b []byte) []byte {
|
||||
if len(b) > 11 || !phoneRegx.Match(b) {
|
||||
return b
|
||||
}
|
||||
|
||||
return []byte(i.MaskFunc(string(b)))
|
||||
})
|
||||
return body, nil
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
package json_path
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/eolinker/apinto/drivers/strategy/data-mask-strategy/mask"
|
||||
|
||||
"github.com/ohler55/ojg/jp"
|
||||
"github.com/ohler55/ojg/oj"
|
||||
)
|
||||
|
||||
func Register() {
|
||||
mask.RegisterMaskFactory(mask.MatchJsonPath, &factory{})
|
||||
|
||||
}
|
||||
|
||||
type factory struct {
|
||||
}
|
||||
|
||||
func (j *factory) Create(cfg *mask.Rule, maskFunc mask.MaskFunc) (mask.IMaskDriver, error) {
|
||||
return newDriver(cfg, maskFunc)
|
||||
}
|
||||
|
||||
var _ mask.IMaskDriver = (*driver)(nil)
|
||||
|
||||
type driver struct {
|
||||
value string
|
||||
maskCfg *mask.Mask
|
||||
mask.MaskFunc
|
||||
expr jp.Expr
|
||||
}
|
||||
|
||||
func newDriver(cfg *mask.Rule, maskFunc mask.MaskFunc) (mask.IMaskDriver, error) {
|
||||
expr, err := jp.ParseString(cfg.Match.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &driver{
|
||||
value: cfg.Match.Value,
|
||||
maskCfg: cfg.Mask,
|
||||
MaskFunc: maskFunc,
|
||||
expr: expr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (k *driver) Exec(body []byte) ([]byte, error) {
|
||||
if k.MaskFunc == nil || k.expr == nil {
|
||||
return body, nil
|
||||
}
|
||||
n, err := oj.Parse(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := k.expr.Get(n)
|
||||
if len(result) > 0 {
|
||||
val, ok := result[0].(string)
|
||||
if ok {
|
||||
k.expr.Set(n, k.MaskFunc(val))
|
||||
}
|
||||
}
|
||||
|
||||
return oj.Marshal(n)
|
||||
}
|
||||
|
||||
func (k *driver) String() string {
|
||||
return fmt.Sprintf("mask driver: json-path,value: %s,detail: %v", k.value, k.maskCfg)
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
package json_path
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestJsonPath(t *testing.T) {
|
||||
|
||||
}
|
47
drivers/strategy/data-mask-strategy/mask/keyword/keyword.go
Normal file
47
drivers/strategy/data-mask-strategy/mask/keyword/keyword.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package keyword
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/eolinker/apinto/drivers/strategy/data-mask-strategy/mask"
|
||||
)
|
||||
|
||||
func Register() {
|
||||
mask.RegisterMaskFactory(mask.MatchKeyword, &factory{})
|
||||
}
|
||||
|
||||
type factory struct {
|
||||
}
|
||||
|
||||
func (k *factory) Create(cfg *mask.Rule, maskFunc mask.MaskFunc) (mask.IMaskDriver, error) {
|
||||
return NewKeywordMaskDriver(cfg, maskFunc)
|
||||
}
|
||||
|
||||
var _ mask.IMaskDriver = (*driver)(nil)
|
||||
|
||||
type driver struct {
|
||||
value string
|
||||
maskCfg *mask.Mask
|
||||
mask.MaskFunc
|
||||
}
|
||||
|
||||
func NewKeywordMaskDriver(cfg *mask.Rule, maskFunc mask.MaskFunc) (*driver, error) {
|
||||
return &driver{
|
||||
value: cfg.Match.Value,
|
||||
maskCfg: cfg.Mask,
|
||||
MaskFunc: maskFunc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (k *driver) Exec(body []byte) ([]byte, error) {
|
||||
if k.MaskFunc == nil {
|
||||
return body, nil
|
||||
}
|
||||
target := strings.Replace(string(body), k.value, k.MaskFunc(k.value), -1)
|
||||
return []byte(target), nil
|
||||
}
|
||||
|
||||
func (k *driver) String() string {
|
||||
return fmt.Sprintf("mask driver: keyword,value: %s,detail: %v", k.value, k.maskCfg)
|
||||
}
|
43
drivers/strategy/data-mask-strategy/mask/manager.go
Normal file
43
drivers/strategy/data-mask-strategy/mask/manager.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package mask
|
||||
|
||||
import (
|
||||
"github.com/eolinker/eosc"
|
||||
)
|
||||
|
||||
var (
|
||||
maskManager = NewMaskManager()
|
||||
)
|
||||
|
||||
type IMaskFactory interface {
|
||||
Create(cfg *Rule, maskFunc MaskFunc) (IMaskDriver, error)
|
||||
}
|
||||
|
||||
type MaskManager struct {
|
||||
factories eosc.Untyped[string, IMaskFactory]
|
||||
}
|
||||
|
||||
func NewMaskManager() *MaskManager {
|
||||
return &MaskManager{
|
||||
factories: eosc.BuildUntyped[string, IMaskFactory](),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MaskManager) Register(name string, driver IMaskFactory) {
|
||||
m.factories.Set(name, driver)
|
||||
}
|
||||
|
||||
func (m *MaskManager) Get(name string) (IMaskFactory, bool) {
|
||||
return m.factories.Get(name)
|
||||
}
|
||||
|
||||
func (m *MaskManager) Del(name string) {
|
||||
m.factories.Del(name)
|
||||
}
|
||||
|
||||
func RegisterMaskFactory(name string, factory IMaskFactory) {
|
||||
maskManager.Register(name, factory)
|
||||
}
|
||||
|
||||
func GetMaskFactory(name string) (IMaskFactory, bool) {
|
||||
return maskManager.Get(name)
|
||||
}
|
188
drivers/strategy/data-mask-strategy/mask/mask.go
Normal file
188
drivers/strategy/data-mask-strategy/mask/mask.go
Normal file
@@ -0,0 +1,188 @@
|
||||
package mask
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type IMaskDriver interface {
|
||||
Exec(body []byte) ([]byte, error)
|
||||
String() string
|
||||
}
|
||||
|
||||
type MaskFunc func(origin string) string
|
||||
|
||||
var (
|
||||
maskByte = '*'
|
||||
)
|
||||
|
||||
const (
|
||||
MaskPartialDisplay = "partial-display"
|
||||
MaskPartialMask = "partial-masking"
|
||||
MaskTruncation = "truncation"
|
||||
MaskReplacement = "replacement"
|
||||
MaskShuffling = "shuffling"
|
||||
)
|
||||
|
||||
func GenMaskFunc(cfg *Mask) (MaskFunc, error) {
|
||||
switch cfg.Type {
|
||||
case MaskPartialDisplay:
|
||||
return partialDisplay(cfg.Begin, cfg.Length), nil
|
||||
case MaskPartialMask:
|
||||
return partialMasking(cfg.Begin, cfg.Length), nil
|
||||
case MaskTruncation:
|
||||
return truncation(cfg.Begin, cfg.Length), nil
|
||||
case MaskReplacement:
|
||||
if cfg.Replace == nil {
|
||||
return nil, fmt.Errorf("replace is nil")
|
||||
}
|
||||
return replacement(cfg.Replace.Type, cfg.Replace.Value)
|
||||
case MaskShuffling:
|
||||
return shuffling(cfg.Begin, cfg.Length), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown mask type %s", cfg.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func partialDisplay(begin int, length int) MaskFunc {
|
||||
return func(origin string) string {
|
||||
target := strings.Builder{}
|
||||
runes := []rune(origin)
|
||||
size := len(runes)
|
||||
|
||||
if begin > size {
|
||||
for i := 0; i < size; i++ {
|
||||
target.WriteRune(maskByte)
|
||||
}
|
||||
} else if length == -1 || begin+length > size {
|
||||
for i := 0; i < begin; i++ {
|
||||
target.WriteRune(maskByte)
|
||||
}
|
||||
for i := begin; i < size; i++ {
|
||||
target.WriteRune(runes[i])
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < begin; i++ {
|
||||
target.WriteRune(maskByte)
|
||||
}
|
||||
for i := begin; i < begin+length; i++ {
|
||||
target.WriteRune(runes[i])
|
||||
}
|
||||
for i := begin + length; i < size; i++ {
|
||||
target.WriteRune(maskByte)
|
||||
}
|
||||
}
|
||||
return target.String()
|
||||
}
|
||||
}
|
||||
|
||||
func partialMasking(begin int, length int) MaskFunc {
|
||||
return func(origin string) string {
|
||||
target := strings.Builder{}
|
||||
runes := []rune(origin)
|
||||
size := len(runes)
|
||||
if begin > size {
|
||||
return origin
|
||||
} else if length == -1 || begin+length > size {
|
||||
for i := 0; i < begin; i++ {
|
||||
target.WriteRune(runes[i])
|
||||
}
|
||||
for i := begin; i < size; i++ {
|
||||
target.WriteRune(maskByte)
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < begin; i++ {
|
||||
target.WriteRune(runes[i])
|
||||
}
|
||||
for i := begin; i < begin+length; i++ {
|
||||
target.WriteRune(maskByte)
|
||||
}
|
||||
for i := begin + length; i < size; i++ {
|
||||
target.WriteRune(runes[i])
|
||||
}
|
||||
}
|
||||
return target.String()
|
||||
}
|
||||
}
|
||||
|
||||
func truncation(begin int, length int) MaskFunc {
|
||||
return func(origin string) string {
|
||||
target := strings.Builder{}
|
||||
runes := []rune(origin)
|
||||
size := len(runes)
|
||||
if begin > size {
|
||||
return ""
|
||||
} else if length == -1 || begin+length > size {
|
||||
for i := begin; i < size; i++ {
|
||||
target.WriteRune(runes[i])
|
||||
}
|
||||
} else {
|
||||
for i := begin; i < begin+length; i++ {
|
||||
target.WriteRune(runes[i])
|
||||
}
|
||||
}
|
||||
return target.String()
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
ReplaceRandom = "random"
|
||||
ReplaceCustom = "custom"
|
||||
)
|
||||
|
||||
func replacement(replaceType string, value string) (MaskFunc, error) {
|
||||
switch replaceType {
|
||||
case ReplaceRandom:
|
||||
return func(origin string) string {
|
||||
return replaceWithRandomString(origin)
|
||||
}, nil
|
||||
case ReplaceCustom:
|
||||
return func(origin string) string {
|
||||
return value
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown replace type %s", replaceType)
|
||||
}
|
||||
}
|
||||
|
||||
const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
|
||||
// 随机生成指定长度的字符串
|
||||
func randomString(length int) string {
|
||||
// 随机种子
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
// 创建一个切片,用于存储生成的随机字符
|
||||
result := make([]byte, length)
|
||||
|
||||
// 根据长度逐个生成随机字符
|
||||
for i := range result {
|
||||
result[i] = charset[rand.Intn(len(charset))]
|
||||
}
|
||||
|
||||
return string(result)
|
||||
}
|
||||
|
||||
// 将原字符串替换为随机生成的小写字母和数字的字符串
|
||||
func replaceWithRandomString(text string) string {
|
||||
// 计算原字符串的长度
|
||||
length := len([]rune(text)) // 处理多字节字符
|
||||
// 生成与原字符串长度一致的随机字符串
|
||||
return randomString(length)
|
||||
}
|
||||
|
||||
func shuffling(begin int, length int) MaskFunc {
|
||||
return func(origin string) string {
|
||||
runes := []rune(origin)
|
||||
// 设置随机数种子,以保证每次运行结果不同
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
// 使用 Shuffle 函数打乱 nums 切片的顺序
|
||||
rand.Shuffle(len(runes), func(i, j int) {
|
||||
runes[i], runes[j] = runes[j], runes[i] // 交换切片中第 i 和 j 个元素
|
||||
})
|
||||
return string(runes)
|
||||
}
|
||||
}
|
113
drivers/strategy/data-mask-strategy/mask/mask_test.go
Normal file
113
drivers/strategy/data-mask-strategy/mask/mask_test.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package mask
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPartialDisplay(t *testing.T) {
|
||||
tests := []struct {
|
||||
origin string
|
||||
begin int
|
||||
length int
|
||||
expected string
|
||||
}{
|
||||
{"abcdef", 2, 3, "**cde*"},
|
||||
{"abcdef", 0, 4, "abcd**"},
|
||||
{"abcdef", 10, 2, "******"},
|
||||
{"abcdef", 2, -1, "**cdef"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
f := partialDisplay(test.begin, test.length)
|
||||
result := f(test.origin)
|
||||
if result != test.expected {
|
||||
t.Errorf("Expected %s but got %s", test.expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPartialMask(t *testing.T) {
|
||||
tests := []struct {
|
||||
origin string
|
||||
begin int
|
||||
length int
|
||||
expected string
|
||||
}{
|
||||
{"abcdef", 2, 3, "ab***f"},
|
||||
{"abcdef", 0, 4, "****ef"},
|
||||
{"abcdef", 10, 2, "abcdef"},
|
||||
{"abcdef", 2, -1, "ab****"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
f := partialMasking(test.begin, test.length)
|
||||
result := f(test.origin)
|
||||
if result != test.expected {
|
||||
t.Errorf("Expected %s but got %s", test.expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTruncation(t *testing.T) {
|
||||
tests := []struct {
|
||||
origin string
|
||||
begin int
|
||||
length int
|
||||
expected string
|
||||
}{
|
||||
{"abcdef", 2, 3, "cde"},
|
||||
{"abcdef", 0, 4, "abcd"},
|
||||
{"abcdef", 10, 2, ""},
|
||||
{"abcdef", 2, -1, "cdef"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
f := truncation(test.begin, test.length)
|
||||
result := f(test.origin)
|
||||
if result != test.expected {
|
||||
t.Errorf("Expected %s but got %s", test.expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplacement(t *testing.T) {
|
||||
randomFunc, _ := replacement(ReplaceRandom, "")
|
||||
customFunc, _ := replacement(ReplaceCustom, "custom")
|
||||
|
||||
tests := []struct {
|
||||
origin string
|
||||
maskFunc MaskFunc
|
||||
funcType string
|
||||
expected string
|
||||
}{
|
||||
{"abcdef", randomFunc, ReplaceRandom, ""},
|
||||
{"abcdef", customFunc, ReplaceCustom, "custom"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
result := test.maskFunc(test.origin)
|
||||
// For random, just check length
|
||||
if test.funcType == ReplaceRandom {
|
||||
if len(result) != len(test.origin) {
|
||||
t.Errorf("Expected length %d but got %d", len(test.origin), len(result))
|
||||
}
|
||||
} else {
|
||||
if result != test.expected {
|
||||
t.Errorf("Expected %s but got %s", test.expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestShuffling(t *testing.T) {
|
||||
origin := "abcdef"
|
||||
f := shuffling(0, len(origin))
|
||||
result := f(origin)
|
||||
if len(result) != len(origin) {
|
||||
t.Errorf("Expected length %d but got %d", len(origin), len(result))
|
||||
}
|
||||
|
||||
// Check that all original characters are present
|
||||
for _, char := range origin {
|
||||
if strings.Count(result, string(char)) != strings.Count(origin, string(char)) {
|
||||
t.Errorf("Character %c count mismatch", char)
|
||||
}
|
||||
}
|
||||
}
|
21
drivers/strategy/data-mask-strategy/mask/match.go
Normal file
21
drivers/strategy/data-mask-strategy/mask/match.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package mask
|
||||
|
||||
const (
|
||||
MatchInner = "inner"
|
||||
MatchKeyword = "keyword"
|
||||
MatchRegex = "regex"
|
||||
MatchJsonPath = "json_path"
|
||||
)
|
||||
|
||||
const (
|
||||
MatchInnerValueName = "name"
|
||||
MatchInnerValuePhone = "phone"
|
||||
MatchInnerValueIDCard = "id-card"
|
||||
MatchInnerValueBankCard = "bank-card"
|
||||
MatchInnerValueDate = "date"
|
||||
MatchInnerValueAmount = "amount"
|
||||
)
|
||||
|
||||
type IInnerMask interface {
|
||||
Exec(body []byte) ([]byte, error)
|
||||
}
|
49
drivers/strategy/data-mask-strategy/mask/regex/regex.go
Normal file
49
drivers/strategy/data-mask-strategy/mask/regex/regex.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package regex
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/eolinker/apinto/drivers/strategy/data-mask-strategy/mask"
|
||||
)
|
||||
|
||||
func Register() {
|
||||
mask.RegisterMaskFactory(mask.MatchRegex, &factory{})
|
||||
}
|
||||
|
||||
type factory struct {
|
||||
}
|
||||
|
||||
func (r *factory) Create(cfg *mask.Rule, maskFunc mask.MaskFunc) (mask.IMaskDriver, error) {
|
||||
return newDriver(cfg, maskFunc)
|
||||
}
|
||||
|
||||
var _ mask.IMaskDriver = (*driver)(nil)
|
||||
|
||||
type driver struct {
|
||||
value string
|
||||
maskCfg *mask.Mask
|
||||
mask.MaskFunc
|
||||
regexp *regexp.Regexp
|
||||
}
|
||||
|
||||
func newDriver(cfg *mask.Rule, maskFunc mask.MaskFunc) (*driver, error) {
|
||||
return &driver{
|
||||
value: cfg.Match.Value,
|
||||
maskCfg: cfg.Mask,
|
||||
MaskFunc: maskFunc,
|
||||
regexp: regexp.MustCompile(cfg.Match.Value),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (k *driver) Exec(body []byte) ([]byte, error) {
|
||||
if k.MaskFunc == nil || k.regexp == nil {
|
||||
return body, nil
|
||||
}
|
||||
target := k.regexp.ReplaceAllStringFunc(string(body), k.MaskFunc)
|
||||
return []byte(target), nil
|
||||
}
|
||||
|
||||
func (k *driver) String() string {
|
||||
return fmt.Sprintf("mask driver: regex,value: %s,detail: %v", k.value, k.maskCfg)
|
||||
}
|
3
go.mod
3
go.mod
@@ -27,6 +27,7 @@ require (
|
||||
github.com/polarismesh/polaris-go v1.1.0
|
||||
github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f
|
||||
github.com/soheilhy/cmux v0.1.5
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/urfave/cli/v2 v2.23.4
|
||||
github.com/valyala/fasthttp v1.47.0
|
||||
golang.org/x/crypto v0.21.0
|
||||
@@ -79,6 +80,7 @@ require (
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/pelletier/go-toml v1.7.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/prometheus/statsd_exporter v0.21.0 // indirect
|
||||
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b // indirect
|
||||
@@ -94,6 +96,7 @@ require (
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
||||
gopkg.in/ini.v1 v1.66.2 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
|
Reference in New Issue
Block a user