diff --git a/etc/rbac.conf b/etc/rbac.conf new file mode 100644 index 0000000..d038cb0 --- /dev/null +++ b/etc/rbac.conf @@ -0,0 +1,33 @@ +# 表示請求參數 +# role: 角色 +# path: api path +# method: http method +[request_definition] +r = role, path, method + +# 策略的基本參數 +# role: 角色 +# path: api path +# method: http method,可以多個 +# name: 名稱 +[policy_definition] +p = role, path, methods, name + +# 策略配對結果後的應對,只要配對到一個即可成功 +[policy_effect] +e = some(where (p.eft == allow)) + +# 規範角色對應是"用戶"與"角色" +# g=A用戶,管理員 +[role_definition] +g = _, _ + +# 策略配對規則 +# 三個條件必須完全匹配 +# g(r.role, p.role),只要判斷用戶角色(r.role)是否屬於策略角色(p.role),為什麼不寫r.role == p.role,因為role可以有繼承關係所以不能這樣寫 +# keyMatch(r.path, p.path),使用正則表達式分析api path,參考 https://casbin.org/docs/function +# regexMatch(r.method, p.methods),使用正則表達式分析http method,參考 https://casbin.org/docs/function +# 或達到一個條件 +# r.role == admin,用戶 UID 是 GodDog 全開放 +[matchers] +m = g(r.role, p.role) && keyMatch2(r.path, p.path) && regexMatch(r.method, p.methods) || r.role == "GodDog" \ No newline at end of file diff --git a/go.mod b/go.mod index 9d27328..8d75033 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,14 @@ go 1.23.6 require ( code.30cm.net/digimon/library-go/errs v1.2.14 + code.30cm.net/digimon/library-go/mongo v0.0.9 github.com/alicebob/miniredis/v2 v2.34.0 + github.com/casbin/casbin/v2 v2.103.0 github.com/golang-jwt/jwt/v4 v4.5.1 - github.com/pkg/errors v0.9.1 github.com/segmentio/ksuid v1.0.4 github.com/stretchr/testify v1.10.0 github.com/zeromicro/go-zero v1.8.0 + go.mongodb.org/mongo-driver v1.17.2 go.uber.org/mock v0.5.0 google.golang.org/grpc v1.70.0 google.golang.org/protobuf v1.36.5 @@ -18,6 +20,8 @@ require ( require ( github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect + github.com/casbin/govaluate v1.3.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect @@ -34,19 +38,21 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/montanaflynn/stats v0.7.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/openzipkin/zipkin-go v0.4.3 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect @@ -56,34 +62,42 @@ require ( github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/redis/go-redis/v9 v9.7.0 // indirect + github.com/shopspring/decimal v1.4.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/yuin/gopher-lua v1.1.1 // indirect go.etcd.io/etcd/api/v3 v3.5.15 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect go.etcd.io/etcd/client/v3 v3.5.15 // indirect - go.opentelemetry.io/otel v1.32.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 // indirect go.opentelemetry.io/otel/exporters/zipkin v1.24.0 // indirect - go.opentelemetry.io/otel/metric v1.32.0 // indirect - go.opentelemetry.io/otel/sdk v1.32.0 // indirect - go.opentelemetry.io/otel/trace v1.32.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/sdk v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.24.0 // indirect + golang.org/x/crypto v0.32.0 // indirect golang.org/x/net v0.34.0 // indirect golang.org/x/oauth2 v0.24.0 // indirect + golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.29.0 // indirect golang.org/x/term v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.9.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect @@ -95,5 +109,5 @@ require ( k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index c011fc5..e61a8e8 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ code.30cm.net/digimon/library-go/errs v1.2.14 h1:Un9wcIIjjJW8D2i0ISf8ibzp9oNT4OqLsaSKW0T4RJU= code.30cm.net/digimon/library-go/errs v1.2.14/go.mod h1:Hs4v7SbXNggDVBGXSYsFMjkii1qLF+rugrIpWePN4/o= +code.30cm.net/digimon/library-go/mongo v0.0.9 h1:fPciIE5B85tXpLg8aeVQqKVbLnfpVAk9xbMu7pE2tVw= +code.30cm.net/digimon/library-go/mongo v0.0.9/go.mod h1:KBVKz/Ci5IheI77BgZxPUeKkaGvDy8fV8EDHSCOLIO4= github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE= github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/miniredis/v2 v2.34.0 h1:mBFWMaJSNL9RwdGRyEDoAAv8OQc5UlEhLDQggTglU/0= @@ -8,10 +10,16 @@ github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLj github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= +github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/casbin/casbin/v2 v2.103.0 h1:dHElatNXNrr8XcseUov0ZSiWjauwmZZE6YMV3eU1yic= +github.com/casbin/casbin/v2 v2.103.0/go.mod h1:Ee33aqGrmES+GNL17L0h9X28wXuo829wnNUnS0edAco= +github.com/casbin/govaluate v1.3.0 h1:VA0eSY0M2lA86dYd5kPPuNZMUD9QkWnOCnavGrw9myc= +github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -50,10 +58,13 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -66,8 +77,8 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJY github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -76,8 +87,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -99,6 +110,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= +github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= @@ -125,10 +138,12 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -146,9 +161,18 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= github.com/zeromicro/go-zero v1.8.0 h1:4g/8VW+fOyM51HZYPeI3mXIZdEX+Fl6SsdYX2H5PYw4= @@ -159,30 +183,34 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.15 h1:fo0HpWz/KlHGMCC+YejpiCmyWDEuIpnTDzpJLB5 go.etcd.io/etcd/client/pkg/v3 v3.5.15/go.mod h1:mXDI4NAOwEiszrHCb0aqfAYNCrZP4e9hRca3d1YK8EU= go.etcd.io/etcd/client/v3 v3.5.15 h1:23M0eY4Fd/inNv1ZfU3AxrbbOdW79r9V9Rl62Nm6ip4= go.etcd.io/etcd/client/v3 v3.5.15/go.mod h1:CLSJxrYjvLtHsrPKsy7LmZEE+DK2ktfd2bN4RhBMwlU= -go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= -go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.mongodb.org/mongo-driver v1.17.2 h1:gvZyk8352qSfzyZ2UMWcpDpMSGEr1eqE4T793SqyhzM= +go.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 h1:s0PHtIkN+3xrbDOpt2M8OTG92cWqUESvzh2MxiR5xY8= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA= go.opentelemetry.io/otel/exporters/zipkin v1.24.0 h1:3evrL5poBuh1KF51D9gO/S+N/1msnm4DaBqs/rpXUqY= go.opentelemetry.io/otel/exporters/zipkin v1.24.0/go.mod h1:0EHgD8R0+8yRhUYJOGR8Hfg2dpiJQxDOszd5smVO9wM= -go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= -go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= -go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= -go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= -go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= -go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= @@ -198,14 +226,21 @@ go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= @@ -214,40 +249,51 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a h1:OAiGFfOiA0v9MRYsSidp3ubZaBnteRUyn3xB2ZQ5G/E= -google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a h1:hgh8P4EuoxpsuKMXX/To36nOFD7vixReXgn8lPGnt+o= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= @@ -281,5 +327,5 @@ sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMm sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/pkg/domain/entity/permission.go b/pkg/domain/entity/permission.go new file mode 100644 index 0000000..07e033b --- /dev/null +++ b/pkg/domain/entity/permission.go @@ -0,0 +1,22 @@ +package entity + +import ( + "code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/permission" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type Permission struct { + ID primitive.ObjectID `bson:"_id,omitempty"` + Parent string `bson:"parent"` // 父權限的 ID (用字串儲存 ObjectID 的 Hex) + Name string `bson:"name"` + HTTPMethod string `bson:"http_method"` // 視作操作(Action) + HTTPPath string `bson:"http_path"` // 視作資源(Object) + Status permission.Status `bson:"status"` // 例如 1: 啟用, 0: 停用 + Type permission.Type `bson:"type"` + UpdateAt int64 `bson:"update_at"` + CreateAt int64 `bson:"create_at"` +} + +func (c *Permission) Collection() string { + return "permission" +} diff --git a/pkg/domain/entity/policy.go b/pkg/domain/entity/policy.go new file mode 100644 index 0000000..1f8ca6f --- /dev/null +++ b/pkg/domain/entity/policy.go @@ -0,0 +1,28 @@ +package entity + +import ( + "go.mongodb.org/mongo-driver/bson/primitive" +) + +//TODO 未來才實作(要做IAM 時) + +// Policy 表示一個存取控制策略文件 +type Policy struct { + ID primitive.ObjectID `bson:"_id,omitempty"` // MongoDB 的主鍵 + Name string `bson:"name"` // 策略名稱 + Description string `bson:"description,omitempty"` // 策略描述 + Effect string `bson:"effect"` // "allow" 或 "deny" + Condition interface{} `bson:"condition,omitempty"` // 存放 JSON 條件,可以是 map[string]interface{} 或其他符合 BSON 格式的資料 + CreatedAt int64 `bson:"created_at"` // 建立時間 + UpdatedAt int64 `bson:"updated_at"` // 更新時間 +} + +// Resource 表示需要保護的資源 +type Resource struct { + ID primitive.ObjectID `bson:"_id,omitempty"` // MongoDB 的主鍵 + Name string `bson:"name"` // 資源名稱 + Type string `bson:"type"` // 資源類型 + Description string `bson:"description,omitempty"` // 資源描述 + CreatedAt int64 `bson:"created_at"` // 建立時間 + UpdatedAt int64 `bson:"updated_at"` // 更新時間 +} diff --git a/pkg/domain/entity/role.go b/pkg/domain/entity/role.go new file mode 100644 index 0000000..2585395 --- /dev/null +++ b/pkg/domain/entity/role.go @@ -0,0 +1,18 @@ +package entity + +import ( + "code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/permission" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type Role struct { + ID primitive.ObjectID `bson:"_id,omitempty"` + Name string `bson:"name"` + Status permission.Status `bson:"status"` // 例如 1: 啟用, 0: 停用 + CreateAt int64 `bson:"create_at"` + UpdateAt int64 `bson:"update_at"` +} + +func (c *Role) Collection() string { + return "role" +} diff --git a/pkg/domain/entity/role_permission.go b/pkg/domain/entity/role_permission.go new file mode 100644 index 0000000..a28fb08 --- /dev/null +++ b/pkg/domain/entity/role_permission.go @@ -0,0 +1,15 @@ +package entity + +import "go.mongodb.org/mongo-driver/bson/primitive" + +type RolePermission struct { + ID primitive.ObjectID `bson:"_id,omitempty"` + RoleID string `bson:"role_id"` // 對應 Role 的 _id + PermissionID string `bson:"permission_id"` // 對應 Permission 的 _id + CreateAt int64 `bson:"create_at"` + UpdateAt int64 `bson:"update_at"` +} + +func (c *RolePermission) Collection() string { + return "role_permission" +} diff --git a/pkg/domain/entity/user_role.go b/pkg/domain/entity/user_role.go new file mode 100644 index 0000000..d55927e --- /dev/null +++ b/pkg/domain/entity/user_role.go @@ -0,0 +1,15 @@ +package entity + +import "go.mongodb.org/mongo-driver/bson/primitive" + +type UserRole struct { + ID primitive.ObjectID `bson:"_id,omitempty"` + UID string `bson:"uid"` // 對應 User 的 _id + RoleID string `bson:"role_id"` // 對應 Role 的 _id + CreateAt int64 `bson:"create_at"` + UpdateAt int64 `bson:"update_at"` +} + +func (c *UserRole) Collection() string { + return "user_role" +} diff --git a/pkg/domain/error.go b/pkg/domain/error.go index c925ecf..b89ca28 100644 --- a/pkg/domain/error.go +++ b/pkg/domain/error.go @@ -28,6 +28,8 @@ const ( GenerateVerifyCodeRedisErrorCode FailedToCheckVerifyCode AccountPlatformNotCorrectErrorCode + + PermissionDeleteErrorCode ) func TokenError(ec ers.ErrorCode, s ...string) *ers.LibError { diff --git a/pkg/domain/permission/status.go b/pkg/domain/permission/status.go new file mode 100644 index 0000000..728af61 --- /dev/null +++ b/pkg/domain/permission/status.go @@ -0,0 +1,27 @@ +package permission + +type Status int8 + +const ( + Open Status = iota + 1 + Close +) + +const ( + ClosePermission string = "close" + OpenPermission string = "open" +) + +func (s Status) String() string { + status, ok := statusMap[s] + if ok { + return status + } + + return ClosePermission +} + +var statusMap = map[Status]string{ + Open: OpenPermission, + Close: ClosePermission, +} diff --git a/pkg/domain/permission/type.go b/pkg/domain/permission/type.go new file mode 100644 index 0000000..a50e05a --- /dev/null +++ b/pkg/domain/permission/type.go @@ -0,0 +1,8 @@ +package permission + +type Type int8 + +const ( + BackendUser Type = iota + 1 + FrontendUser +) diff --git a/pkg/domain/rbac/rule.go b/pkg/domain/rbac/rule.go new file mode 100644 index 0000000..99a95c4 --- /dev/null +++ b/pkg/domain/rbac/rule.go @@ -0,0 +1,61 @@ +package rbac + +// Rule 最多六個參數,例如 +// +// p, admin, data1, read +// PolicyType:p +// Field0:admin +// Field01:data1 +// Field02:read +type Rule struct { + PolicyType string // p or g + Field0 string + Field1 string + Field2 string + Field3 string + Field4 string + Field5 string +} + +// ToString 轉換成這樣 []string{"p", "admin", "data1", "read"} 讓 casbin 看得懂 +func (rule Rule) ToString() []string { + fields := []string{ + rule.PolicyType, rule.Field0, rule.Field1, + rule.Field2, rule.Field3, rule.Field4, rule.Field5, + } + // 移除空字串,提高效能 + var result []string + for _, field := range fields { + if field != "" { + result = append(result, field) + } + } + + return result +} + +func StringToPolicy(policyType string, rule []string) Rule { + line := Rule{} + + line.PolicyType = policyType + if len(rule) > 0 { + line.Field0 = rule[0] + } + if len(rule) > 1 { + line.Field1 = rule[1] + } + if len(rule) > 2 { + line.Field2 = rule[2] + } + if len(rule) > 3 { + line.Field3 = rule[3] + } + if len(rule) > 4 { + line.Field4 = rule[4] + } + if len(rule) > 5 { + line.Field5 = rule[5] + } + + return line +} diff --git a/pkg/domain/redis.go b/pkg/domain/redis.go index d69da77..ff43511 100644 --- a/pkg/domain/redis.go +++ b/pkg/domain/redis.go @@ -41,3 +41,13 @@ func GetDeviceTokenRedisKey(device string) string { func GetTicketRedisKey(ticket string) string { return TicketRedisKey.With(ticket).ToString() } + +// =================== + +const ( + CasbinRuleRedisKey RedisKey = "casbin_rule" +) + +func GetCasbinRuleRedisKey() string { + return CasbinRuleRedisKey.ToString() +} diff --git a/pkg/domain/repository/casbin_redis_adapter.go b/pkg/domain/repository/casbin_redis_adapter.go new file mode 100644 index 0000000..1d1f107 --- /dev/null +++ b/pkg/domain/repository/casbin_redis_adapter.go @@ -0,0 +1,20 @@ +package repository + +import ( + "github.com/casbin/casbin/v2/persist" +) + +// 角色權限策略 (policy.csv) 以 const 定義 +/* + p, admin, data1, read + p, admin, data1, write + p, user, data1, read + + g, alice, admin + g, bob, user +*/ + +// RBACAdapter 資料接收器,讓 Casbin 讀取規則的,類似上面這種規則 +type RBACAdapter interface { + persist.Adapter +} diff --git a/pkg/domain/repository/permission.go b/pkg/domain/repository/permission.go new file mode 100644 index 0000000..90faa74 --- /dev/null +++ b/pkg/domain/repository/permission.go @@ -0,0 +1,45 @@ +package repository + +import ( + "code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity" + "code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/permission" + "context" + "go.mongodb.org/mongo-driver/mongo" +) + +type PermissionRepository interface { + Insert(ctx context.Context, permission entity.Permission) error + Update(ctx context.Context, id string, req UpdatePermission) error + Delete(ctx context.Context, id string) error + GetPermission + Index +} + +type GetPermission interface { + // GetAll 取得所有權限列表 + GetAll(ctx context.Context, status *permission.Status) ([]entity.Permission, error) + // GetAllByID 以權限 ID 作為鍵取得所有權限的映射 + GetAllByID(ctx context.Context, status *permission.Status) (map[string]entity.Permission, error) + // FindOne 根據查詢條件取得單筆權限資料 + FindOne(ctx context.Context, query PermissionQuery) (entity.Permission, error) + // FindByNames 根據權限名稱列表查詢權限資料 + FindByNames(ctx context.Context, names []string) ([]entity.Permission, error) +} + +type Index interface { + Index20250214UP(ctx context.Context) (*mongo.Cursor, error) +} + +type PermissionQuery struct { + HTTPMethod string + HTTPPath string +} + +type UpdatePermission struct { + ParentID *string + Name *string + HTTPMethod *string + HTTPPath *string + Status *permission.Status + Type *permission.Type +} diff --git a/pkg/domain/repository/role.go b/pkg/domain/repository/role.go new file mode 100644 index 0000000..21535e7 --- /dev/null +++ b/pkg/domain/repository/role.go @@ -0,0 +1,42 @@ +package repository + +import ( + "code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity" + "context" +) + +type RoleRepository interface { + Insert(ctx context.Context, role *entity.Role) error + Update(ctx context.Context, role *entity.Role) error + Delete(ctx context.Context, role *entity.Role) error + List() + ListAll() + Get() + GetByUID() +} + +type RoleRepository interface { + Page(ctx context.Context, filter PageRoleFilter, page, size int) ([]entity.Role, int64, error) + Get(ctx context.Context, id int64) (entity.Role, error) + GetByUID(ctx context.Context, uid string) (entity.Role, error) + All(ctx context.Context, clientID int) ([]entity.Role, error) + IncrementID(ctx context.Context) (int, error) + Create(ctx context.Context, role *entity.Role) (int64, error) + Update(ctx context.Context, role *entity.Role) error + Delete(ctx context.Context, uid string) error +} + +type PageRoleFilter struct { + ClientID int + UID string + Name string + Permissions []string + Status int +} + +type RolePermissionRepository interface { + BindRolePermission() + UnBindRolePermission() + GetRolePermission() + GetByPermissionID() +} diff --git a/pkg/domain/usecase/casbin_redis_rbac.go b/pkg/domain/usecase/casbin_redis_rbac.go new file mode 100644 index 0000000..1fde3ef --- /dev/null +++ b/pkg/domain/usecase/casbin_redis_rbac.go @@ -0,0 +1,20 @@ +package usecase + +import ( + "context" + "time" +) + +type RBACUseCase interface { + Check(ctx context.Context, role, path, method string) (CheckRolePermissionStatus, error) + LoadPolicy(ctx context.Context) error + SyncPolicy(ctx context.Context, cron time.Duration) +} + +type CheckRolePermissionStatus struct { + Allow bool `json:"allow"` + Select struct { + PermissionName string `json:"permission_name"` + PlainCode bool `json:"plain_code"` + } `json:"select"` +} diff --git a/pkg/domain/usecase/permission.go b/pkg/domain/usecase/permission.go new file mode 100644 index 0000000..247fff7 --- /dev/null +++ b/pkg/domain/usecase/permission.go @@ -0,0 +1,42 @@ +package usecase + +import ( + "code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity" + "code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/permission" + "context" +) + +type PermissionUseCase interface { + Insert(ctx context.Context, req CreatePermissionReq) error + Del(ctx context.Context, id string) error + Update(ctx context.Context, req UpdatePermissionReq) error + Get(ctx context.Context, id string) (entity.Permission, error) + List(ctx context.Context, param ListParam) ([]entity.Permission, int64, error) + All(ctx context.Context, status *permission.Status) + // FilterAll 用樹的結構,使得付節點若關閉,子節點也不會顯示 + FilterAll(ctx context.Context) ([]entity.Permission, error) +} + +type CreatePermissionReq struct { + Parent *string // 父權限的 ID (用字串儲存 ObjectID 的 Hex) + Name string // 權限名字 + HTTPMethod string // 視作操作(Action) + HTTPPath string // 視作資源(Object) + Status permission.Status // 例如 1: 啟用, 0: 停用 + Type permission.Type +} + +type UpdatePermissionReq struct { + Parent *string // 父權限的 ID (用字串儲存 ObjectID 的 Hex) + Name *string // 權限名字 + HTTPMethod *string // 視作操作(Action) + HTTPPath *string // 視作資源(Object) + Status *permission.Status // 例如 1: 啟用, 0: 停用 + Type *permission.Type +} + +type ListParam struct { + PageIndex int64 + PageSize int64 + Parent *string +} diff --git a/pkg/repository/casbin_redis_adapter.go b/pkg/repository/casbin_redis_adapter.go new file mode 100644 index 0000000..03891f7 --- /dev/null +++ b/pkg/repository/casbin_redis_adapter.go @@ -0,0 +1,149 @@ +package repository + +import ( + "code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain" + "code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/rbac" + "code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/repository" + "context" + "encoding/json" + "github.com/casbin/casbin/v2/model" + "github.com/casbin/casbin/v2/persist" + "github.com/zeromicro/go-zero/core/stores/redis" + "time" +) + +type RBACAdapterParam struct { + Redis *redis.Redis +} + +type rbacAdapter struct { + redis *redis.Redis +} + +func NewRBACAdapter(param RBACAdapterParam) (repository.RBACAdapter, error) { + return &rbacAdapter{ + redis: param.Redis, + }, nil +} + +// loadPolicyByString 真正將 Rule 存到 casbin 讓他可以使用的地方 +func loadPolicyByString(line rbac.Rule, model model.Model) error { + text := line.ToString() + // 雖然是從Redis 來,不過最終還是有一份會存在記憶體當中,這樣做的原因只是不要存第二次 + err := persist.LoadPolicyArray(text, model) + if err != nil { + return err + } + + return nil +} + +// LoadPolicy 將檔案從記憶體當中載入本地 Casbin +func (adapter *rbacAdapter) LoadPolicy(model model.Model) error { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + rk := domain.GetCasbinRuleRedisKey() + stop, err := adapter.redis.LlenCtx(ctx, rk) + if err != nil { + return err + } + + rules, err := adapter.redis.LrangeCtx(ctx, rk, 0, stop) + if err != nil { + return err + } + + for _, ruleStr := range rules { + var rule rbac.Rule + err := json.Unmarshal([]byte(ruleStr), &rule) + if err != nil { + return err + } + + err = loadPolicyByString(rule, model) + if err != nil { + return err + } + } + + return nil +} + +// SavePolicy saves policy to database. 將 Rule 一次就從資料庫中存到 redis 裡面 -> 打API 初始化 Redis 同步的資料 +func (adapter *rbacAdapter) SavePolicy(model model.Model) error { + rk := domain.GetCasbinRuleRedisKey() + _, err := adapter.redis.Del(rk) + if err != nil { + return err + } + + var texts []string + + for policy, ast := range model["p"] { + for _, rule := range ast.Policy { + line := rbac.StringToPolicy(policy, rule) + text, err := json.Marshal(line) + if err != nil { + return err + } + texts = append(texts, string(text)) + } + } + + for policy, ast := range model["g"] { + for _, rule := range ast.Policy { + line := rbac.StringToPolicy(policy, rule) + text, err := json.Marshal(line) + if err != nil { + return err + } + texts = append(texts, string(text)) + } + } + + _, err = adapter.redis.Rpush(rk, texts) + if err != nil { + return err + } + + return nil +} + +// AddPolicy adds a policy rule to the storage. +func (adapter *rbacAdapter) AddPolicy(_ string, policyType string, rule []string) error { + policy := rbac.StringToPolicy(policyType, rule) + text, err := json.Marshal(policy) + if err != nil { + return err + } + + rk := domain.GetCasbinRuleRedisKey() + _, err = adapter.redis.Rpush(rk, text) + if err != nil { + return err + } + + return nil +} + +func (adapter *rbacAdapter) RemovePolicy(_ string, policyType string, rule []string) error { + policy := rbac.StringToPolicy(policyType, rule) + text, err := json.Marshal(policy) + if err != nil { + return err + } + + rk := domain.GetCasbinRuleRedisKey() + _, err = adapter.redis.Lrem(rk, 1, string(text)) + if err != nil { + return err + } + + return nil +} + +// RemoveFilteredPolicy 沒用到先不實作 +func (adapter *rbacAdapter) RemoveFilteredPolicy(_ string, policyType string, fieldIndex int, fieldValues ...string) error { + return nil +} diff --git a/pkg/repository/casbin_redis_adapter_test.go b/pkg/repository/casbin_redis_adapter_test.go new file mode 100644 index 0000000..add446b --- /dev/null +++ b/pkg/repository/casbin_redis_adapter_test.go @@ -0,0 +1,195 @@ +package repository + +import ( + "code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain" + "code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/rbac" + "context" + "encoding/json" + "github.com/casbin/casbin/v2/model" + "github.com/stretchr/testify/assert" + "testing" +) + +func Test_rbacAdapter_AddPolicy(t *testing.T) { + mr, r := setupMiniRedis() + defer mr.Close() + + adapter, err := NewRBACAdapter(RBACAdapterParam{Redis: r}) + assert.NoError(t, err) + + // 新增策略 + err = adapter.AddPolicy("", "p", []string{"user", "data2", "read"}) + assert.NoError(t, err) + + // 驗證 Redis 內的數據 + rk := domain.GetCasbinRuleRedisKey() + data, err := r.LrangeCtx(context.Background(), rk, 0, -1) + assert.NoError(t, err) + assert.Len(t, data, 1) + + // 解析 JSON 確認內容 + var policy rbac.Rule + err = json.Unmarshal([]byte(data[0]), &policy) + assert.NoError(t, err) + assert.Equal(t, "p", policy.PolicyType) + assert.Equal(t, "user", policy.Field0) + assert.Equal(t, "data2", policy.Field1) + assert.Equal(t, "read", policy.Field2) +} + +// 測試 `RemovePolicy` +func TestRBACAdapter_RemovePolicy(t *testing.T) { + mr, rdb := setupMiniRedis() + defer mr.Close() + + adapter, err := NewRBACAdapter(RBACAdapterParam{Redis: rdb}) + assert.NoError(t, err) + + // 新增策略 + err = adapter.AddPolicy("", "p", []string{"user", "data2", "read"}) + assert.NoError(t, err) + + // 確保策略存在 + rk := domain.GetCasbinRuleRedisKey() + data, err := rdb.LrangeCtx(context.Background(), rk, 0, -1) + assert.NoError(t, err) + assert.Len(t, data, 1) + + // 刪除策略 + err = adapter.RemovePolicy("", "p", []string{"user", "data2", "read"}) + assert.NoError(t, err) + + // 確保 Redis 已經刪除該策略 + data, err = rdb.LrangeCtx(context.Background(), rk, 0, -1) + assert.NoError(t, err) + assert.Len(t, data, 0) +} + +// 測試 SavePolicy: 確保能夠存入 Redis +func TestRBACAdapter_SavePolicy(t *testing.T) { + mr, rdb := setupMiniRedis() + defer mr.Close() + + adapter, err := NewRBACAdapter(RBACAdapterParam{Redis: rdb}) + assert.NoError(t, err) + + // 準備 Casbin Model + m, _ := model.NewModelFromString(` + [request_definition] + r = sub, obj, act + + [policy_definition] + p = sub, obj, act + + [role_definition] + g = _, _ + + [policy_effect] + e = some(where (p.eft == allow)) + + [matchers] + m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act + `) + + // 加入測試策略 + m.AddPolicy("p", "p", []string{"admin", "data1", "read"}) + m.AddPolicy("p", "p", []string{"admin", "data1", "write"}) + m.AddPolicy("p", "g", []string{"alice", "admin"}) + + // 確保 Redis 內最開始是空的 + rk := domain.GetCasbinRuleRedisKey() + rulesBefore, _ := rdb.LrangeCtx(context.Background(), rk, 0, -1) + assert.Len(t, rulesBefore, 0) + + // 執行 SavePolicy + err = adapter.SavePolicy(m) + assert.NoError(t, err) + + // 確保 Redis 內的數據與 `policy` 一致 + rulesAfter, err := rdb.LrangeCtx(context.Background(), rk, 0, -1) + assert.NoError(t, err) + + // 解析 JSON 並確認內容 + expectedPolicies := [][]string{ + {"p", "admin", "data1", "read"}, + {"p", "admin", "data1", "write"}, + {"g", "alice", "admin"}, + } + + for i, ruleStr := range rulesAfter { + var rule rbac.Rule + err := json.Unmarshal([]byte(ruleStr), &rule) + assert.NoError(t, err) + + assert.Equal(t, expectedPolicies[i][0], rule.PolicyType) + assert.Equal(t, expectedPolicies[i][1], rule.Field0) + assert.Equal(t, expectedPolicies[i][2], rule.Field1) + if len(expectedPolicies[i]) > 3 { + assert.Equal(t, expectedPolicies[i][3], rule.Field2) + } + } +} + +// 測試 SavePolicy: Redis 寫入失敗 +func TestRBACAdapter_SavePolicy_RedisError(t *testing.T) { + mr, rdb := setupMiniRedis() + defer mr.Close() + + adapter, err := NewRBACAdapter(RBACAdapterParam{Redis: rdb}) + assert.NoError(t, err) + + m, _ := model.NewModelFromString(` + [policy_definition] + p = sub, obj, act + `) + m.AddPolicy("p", "p", []string{"admin", "data1", "read"}) + + err = adapter.SavePolicy(m) + assert.Error(t, err) // 應該要回傳錯誤 +} + +// 測試 `LoadPolicy` 是否能從 Redis 讀取 `p` 和 `g` 規則 +func TestRBACAdapter_LoadPolicy(t *testing.T) { + mr, rdb := setupMiniRedis() + defer mr.Close() + + adapter, err := NewRBACAdapter(RBACAdapterParam{Redis: rdb}) + assert.NoError(t, err) + + // 預先寫入 Redis 測試數據 + rk := domain.GetCasbinRuleRedisKey() + testRules := []rbac.Rule{ + {PolicyType: "p", Field0: "admin", Field1: "data1", Field2: "read"}, + {PolicyType: "p", Field0: "admin", Field1: "data1", Field2: "write"}, + {PolicyType: "g", Field0: "alice", Field1: "admin"}, + {PolicyType: "g", Field0: "bob", Field1: "user"}, + } + + for _, rule := range testRules { + data, _ := json.Marshal(rule) + _, err := rdb.Rpush(rk, string(data)) + assert.NoError(t, err) + } + + // 創建 Casbin Model + m, _ := model.NewModelFromString(` + [request_definition] + r = sub, obj, act + + [policy_definition] + p = sub, obj, act + + [role_definition] + g = _, _ + + [policy_effect] + e = some(where (p.eft == allow)) + + [matchers] + m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act + `) + + // 測試 `LoadPolicy` + err = adapter.LoadPolicy(m) + assert.NoError(t, err) +} diff --git a/pkg/repository/error.go b/pkg/repository/error.go new file mode 100644 index 0000000..1a5c437 --- /dev/null +++ b/pkg/repository/error.go @@ -0,0 +1,11 @@ +package repository + +import ( + "errors" + "github.com/zeromicro/go-zero/core/stores/mon" +) + +var ( + ErrNotFound = mon.ErrNotFound + ErrInvalidObjectID = errors.New("invalid objectId") +) diff --git a/pkg/repository/permission.go b/pkg/repository/permission.go new file mode 100644 index 0000000..dcdd636 --- /dev/null +++ b/pkg/repository/permission.go @@ -0,0 +1,175 @@ +package repository + +import ( + "code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity" + "code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/permission" + "code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/repository" + mgo "code.30cm.net/digimon/library-go/mongo" + "context" + "github.com/zeromicro/go-zero/core/stores/mon" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "time" +) + +type PermissionRepositoryParam struct { + Conf *mgo.Conf + DBOpts []mon.Option +} + +type PermissionRepository struct { + DB mgo.DocumentDBUseCase +} + +// TODO 量小不需要 Redis 以後有需要再增加 + +func NewPermissionRepository(param PermissionRepositoryParam) repository.PermissionRepository { + e := entity.Permission{} + db, err := mgo.NewDocumentDB(param.Conf, e.Collection(), param.DBOpts...) + if err != nil { + panic(err) + } + + return &PermissionRepository{ + DB: db, + } +} + +func (repo *PermissionRepository) Insert(ctx context.Context, data entity.Permission) error { + if data.ID.IsZero() { + now := time.Now().UTC().UnixNano() + data.ID = primitive.NewObjectID() + data.CreateAt = now + data.UpdateAt = now + } + + _, err := repo.DB.GetClient().InsertOne(ctx, data) + + return err +} + +func (repo *PermissionRepository) Update(ctx context.Context, id string, req repository.UpdatePermission) error { + now := time.Now().UTC().UnixNano() + // 動態構建更新內容 + updateFields := bson.M{ + "update_at": now, // 確保 `updateAt` 總是更新 + } + if req.Name != nil { + updateFields["name"] = *req.Name + } + + if req.HTTPMethod != nil { + updateFields["http_method"] = *req.HTTPMethod + } + + if req.HTTPPath != nil { + updateFields["http_path"] = *req.HTTPPath + } + + if req.Status != nil { + updateFields["status"] = *req.Status + } + + if req.Type != nil { + updateFields["type"] = *req.Type + } + + if req.ParentID != nil { + updateFields["parent"] = *req.ParentID + } + + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return ErrInvalidObjectID + } + + _, err = repo.DB.GetClient().UpdateOne(ctx, bson.M{"_id": oid}, bson.M{"$set": updateFields}) + if err != nil { + return err + } + + return nil +} + +func (repo *PermissionRepository) Delete(ctx context.Context, id string) error { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return ErrInvalidObjectID + } + + _, err = repo.DB.GetClient().DeleteOne(ctx, bson.M{"_id": oid}) + if err != nil { + return err + } + + return nil +} + +func (repo *PermissionRepository) GetAll(ctx context.Context, status *permission.Status) ([]entity.Permission, error) { + filter := bson.M{} + if status != nil { + filter["status"] = status + } + opt := options.Find().SetSort(bson.M{"id": 1}) + + result := make([]entity.Permission, 0) + err := repo.DB.GetClient().Find(ctx, &result, filter, opt) + if err != nil { + return nil, err + } + + return result, nil +} + +func (repo *PermissionRepository) GetAllByID(ctx context.Context, status *permission.Status) (map[string]entity.Permission, error) { + permissions, err := repo.GetAll(ctx, status) + if err != nil { + return nil, err + } + + permissionMap := make(map[string]entity.Permission) + for _, v := range permissions { + permissionMap[v.Name] = v + } + + return permissionMap, nil +} + +func (repo *PermissionRepository) FindOne(ctx context.Context, query repository.PermissionQuery) (entity.Permission, error) { + filter := bson.M{"http_method": query.HTTPMethod, "http_path": query.HTTPPath} + var result entity.Permission + + err := repo.DB.GetClient().FindOne(ctx, &result, filter) + if err != nil { + return entity.Permission{}, err + } + + return result, nil +} + +func (repo *PermissionRepository) FindByNames(ctx context.Context, names []string) ([]entity.Permission, error) { + result := make([]entity.Permission, 0) + // 使用 $in 操作符查詢 name 在 names 切片中的文件 + filter := bson.M{"name": bson.M{"$in": names}} + err := repo.DB.GetClient().FindOne(ctx, &result, filter) + if err != nil { + return []entity.Permission{}, err + } + + return result, nil +} + +func (repo *PermissionRepository) Index20250214UP(ctx context.Context) (*mongo.Cursor, error) { + // 等價於 db.account.createIndex({ "http_method": 1, "http_path": 1}, {unique: false}) + repo.DB.PopulateMultiIndex(ctx, []string{ + "http_method", + "http_path", + }, []int32{1, 1}, false) + + // 等價於 db.account.createIndex({"create_at": 1}) + repo.DB.PopulateIndex(ctx, "name", 1, true) + + return repo.DB.GetClient().Indexes().List(ctx) +} diff --git a/pkg/usecase/casbin_redis_rbac.go b/pkg/usecase/casbin_redis_rbac.go new file mode 100644 index 0000000..4ce4a17 --- /dev/null +++ b/pkg/usecase/casbin_redis_rbac.go @@ -0,0 +1,174 @@ +package usecase + +import ( + "code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/permission" + "code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/repository" + "code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/usecase" + "code.30cm.net/digimon/library-go/errs" + "context" + "fmt" + "github.com/casbin/casbin/v2" + "github.com/casbin/casbin/v2/model" + "github.com/zeromicro/go-zero/core/logx" + "log" + "time" +) + +type RBACUseCaseParam struct { + ModulePath string + permissionRepo repository.PermissionRepository + RBACRedisAdapter repository.RBACAdapter + // role permission 之類的 +} + +type RBACUseCase struct { + permissionRepo repository.PermissionRepository + adapter repository.RBACAdapter + instance *casbin.Enforcer +} + +func NewUseCase(param RBACUseCaseParam) usecase.RBACUseCase { + result := &RBACUseCase{ + adapter: param.RBACRedisAdapter, + permissionRepo: param.permissionRepo, + } + + // 1. 讀取 RBAC 模型 -> + m, err := model.NewModelFromFile(param.ModulePath) + if err != nil { + log.Fatalf("failed to load model: %v", err) + } + + // 3. 創建 Casbin Enforcer + enforcer, err := casbin.NewEnforcer(m, result.adapter) + if err != nil { + log.Fatalf("failed to init Enforcer: %v", err) + } + + result.instance = enforcer + + return result +} + +func (use *RBACUseCase) Check(ctx context.Context, role, path, method string) (usecase.CheckRolePermissionStatus, error) { + ok, p, err := use.instance.EnforceEx(role, path, method) + if err != nil { + e := errs.ForbiddenL(logx.WithContext(ctx), + []logx.LogField{ + {Key: "req", Value: fmt.Sprintf("role: %s, path: %s, method: %s", role, path, method)}, + {Key: "func", Value: "casbin.EnforceEx"}, + {Key: "err", Value: err.Error()}, + }, + "failed to get permission") + + return usecase.CheckRolePermissionStatus{}, e + } + + status := usecase.CheckRolePermissionStatus{ + Allow: ok, + } + + fmt.Println(p) + //// 檢查是否有明碼查詢權限 + //if role == domain.AdminRoleUID { + // status.Select.PlainCode = true + //} else if ok && method == http.MethodGet { + // status.Select.PlainCode = use.rbac.GetModel().HasPolicy("p", "p", []string{ + // role, path, method, p[3] + ".plain_code", + // }) + //} + // + //limit := 4 + //if len(p) >= limit { + // status.Select.PermissionName = p[3] + //} + + return status, nil +} + +func (use *RBACUseCase) LoadPolicy(ctx context.Context) error { + + status := permission.Open + // 取得所有permission -> permission tree 拿到有開啟的節點,如果付節點關閉,子節點也就不顯示了 + permissions, err := use.permissionRepo.GetAll(ctx, &status) + if err != nil { + return fmt.Errorf("permissionRepo.AllStatus error: %w", err) + } + + fmt.Println(permissions) + + // 全部permission + //permissionMap := make(map[int64]entity.Permission, len(permissions)) + //for _, v := range permissions { + // permissionMap[v.ID] = v + //} + + // 全部角色 + //roles, err := r.roleRepo.All(ctx, 1) + //if err != nil { + // return fmt.Errorf("roleRepo.AllStatus error: %w", err) + //} + // + //roleMap := make(map[int64]entity.Role, len(roles)) + //for _, v := range roles { + // roleMap[v.ID] = v + //} + + // 根據角色組合權限表 + //for _, v := range roles { + // rolePermissions, err := r.rolePermissionRepo.Get(ctx, v.ID) + // if err != nil { + // return fmt.Errorf("rolePermissionRepo.Get ID: %d error: %w", v.ID, err) + // } + // + // for _, rp := range rolePermissions { + // role, ok := roleMap[rp.RoleID] + // if !ok { + // logrus.WithFields(logrus.Fields{ + // "role_id": rp.RoleID, + // }).Error("role not found") + // + // continue + // } + // + // permission, ok := permissionMap[rp.PermissionID] + // if !ok { + // logrus.WithFields(logrus.Fields{ + // "permission_id": rp.PermissionID, + // }).Error("permission not found") + // + // continue + // } + // + // if permission.HTTPPath == "" || permission.HTTPMethod == "" { + // continue + // } + // + // // 根據策略model configs/rbac_model.conf填入policy_definition對應參數 + // err := persist.LoadPolicyArray([]string{"p", role.UID, permission.HTTPPath, permission.HTTPMethod, permission.Name}, model) + // if err != nil { + // return fmt.Errorf("persist.LoadPolicyArray error: %w", err) + // } + // } + //} + + return nil +} + +func (use *RBACUseCase) SyncPolicy(ctx context.Context, cron time.Duration) { + t := time.NewTicker(cron) + + for { + select { + case <-t.C: + if err := use.LoadPolicy(ctx); err == nil { + logx.Info("LoadPolicy success") + } + case <-ctx.Done(): + t.Stop() + logx.Info("exit Policy success") + + return + } + } +} diff --git a/pkg/usecase/permiission.go b/pkg/usecase/permiission.go new file mode 100644 index 0000000..aed2454 --- /dev/null +++ b/pkg/usecase/permiission.go @@ -0,0 +1 @@ +package usecase