From df1a41eb219756c957f7ff9055093f6365bff029 Mon Sep 17 00:00:00 2001 From: loveuer Date: Fri, 1 Nov 2024 15:18:21 +0800 Subject: [PATCH] =?UTF-8?q?drop:=20=E8=80=83=E8=99=91=E5=8A=A0=E5=85=A5=20?= =?UTF-8?q?uauth=20=E4=BD=9C=E4=B8=BA=E8=AE=A4=E8=AF=81=E4=B8=AD=E5=BF=83?= =?UTF-8?q?=E5=81=9A=E4=B8=80=E4=B8=AA=E6=95=B4=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cache/cache.go | 55 ++++++++++++++- cache/redis.go | 99 ++++++++++++++++++++++++++ cache/scanner.go | 20 ++++++ example/basic/main.go | 12 ++++ go.mod | 19 +++-- go.sum | 48 ++++++++++--- group.go | 11 --- internal/tool/cert.go | 104 +++++++++++++++++++++++++++ internal/tool/ctx.go | 38 ++++++++++ internal/tool/file.go | 30 ++++++++ internal/tool/human.go | 24 +++++++ internal/tool/must.go | 9 +++ internal/tool/password.go | 80 +++++++++++++++++++++ internal/tool/password_test.go | 11 +++ internal/tool/random.go | 54 ++++++++++++++ internal/tool/slice.go | 5 ++ internal/tool/slice_test.go | 1 + internal/tool/table.go | 124 +++++++++++++++++++++++++++++++++ internal/tool/time.go | 13 ++++ privilege.go | 57 ++++++++++++--- readme.md | 11 +++ role.go | 57 +++++++++++---- scope.go | 28 ++++++++ sqlite.db | Bin 0 -> 40960 bytes store/sqlite.go | 6 ++ store/store.go | 7 ++ urbac.go | 37 +++++++++- user.go | 13 ---- 28 files changed, 910 insertions(+), 63 deletions(-) create mode 100644 cache/redis.go create mode 100644 cache/scanner.go create mode 100644 example/basic/main.go delete mode 100644 group.go create mode 100644 internal/tool/cert.go create mode 100644 internal/tool/ctx.go create mode 100644 internal/tool/file.go create mode 100644 internal/tool/human.go create mode 100644 internal/tool/must.go create mode 100644 internal/tool/password.go create mode 100644 internal/tool/password_test.go create mode 100644 internal/tool/random.go create mode 100644 internal/tool/slice.go create mode 100644 internal/tool/slice_test.go create mode 100644 internal/tool/table.go create mode 100644 internal/tool/time.go create mode 100644 readme.md create mode 100644 scope.go create mode 100644 sqlite.db delete mode 100644 user.go diff --git a/cache/cache.go b/cache/cache.go index 47d38df..d3150de 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -1,3 +1,56 @@ package cache -type Cache interface{} +import ( + "context" + "encoding/json" + "errors" + "time" +) + +var ( + ErrorKeyNotFound = errors.New("key not found") +) + +type Cache interface { + Get(ctx context.Context, key string) ([]byte, error) + GetScan(ctx context.Context, key string) Scanner + GetEx(ctx context.Context, key string, duration time.Duration) ([]byte, error) + GetExScan(ctx context.Context, key string, duration time.Duration) Scanner + // Set value 会被序列化, 优先使用 MarshalBinary 方法, 没有则执行 json.Marshal + Set(ctx context.Context, key string, value any) error + // SetEx value 会被序列化, 优先使用 MarshalBinary 方法, 没有则执行 json.Marshal + SetEx(ctx context.Context, key string, value any, duration time.Duration) error + Del(ctx context.Context, keys ...string) error +} + +type Scanner interface { + Scan(model any) error +} + +type encoded_value interface { + MarshalBinary() ([]byte, error) +} + +type decoded_value interface { + UnmarshalBinary(bs []byte) error +} + +func handleValue(value any) ([]byte, error) { + var ( + bs []byte + err error + ) + + switch value.(type) { + case []byte: + return value.([]byte), nil + } + + if imp, ok := value.(encoded_value); ok { + bs, err = imp.MarshalBinary() + } else { + bs, err = json.Marshal(value) + } + + return bs, err +} diff --git a/cache/redis.go b/cache/redis.go new file mode 100644 index 0000000..0b4acab --- /dev/null +++ b/cache/redis.go @@ -0,0 +1,99 @@ +package cache + +import ( + "context" + "errors" + "fmt" + "net/url" + "time" + + "github.com/go-redis/redis/v8" + "github.com/loveuer/urbac/internal/tool" +) + +func NewRedisCache(uri string) (Cache, error) { + ins, err := url.Parse(uri) + if err != nil { + return nil, err + } + + addr := ins.Host + username := ins.User.Username() + password, _ := ins.User.Password() + + var rc *redis.Client + rc = redis.NewClient(&redis.Options{ + Addr: addr, + Username: username, + Password: password, + }) + + if err = rc.Ping(tool.Timeout(5)).Err(); err != nil { + return nil, fmt.Errorf("test redis failed: %w", err) + } + + return &_redis{client: rc}, nil +} + +type _redis struct { + client *redis.Client +} + +func (r *_redis) Get(ctx context.Context, key string) ([]byte, error) { + result, err := r.client.Get(ctx, key).Result() + if err != nil { + if errors.Is(err, redis.Nil) { + return nil, ErrorKeyNotFound + } + + return nil, err + } + + return []byte(result), nil +} + +func (r *_redis) GetScan(ctx context.Context, key string) Scanner { + return newScanner(r.Get(ctx, key)) +} + +func (r *_redis) GetEx(ctx context.Context, key string, duration time.Duration) ([]byte, error) { + result, err := r.client.GetEx(ctx, key, duration).Result() + if err != nil { + if errors.Is(err, redis.Nil) { + return nil, ErrorKeyNotFound + } + + return nil, err + } + + return []byte(result), nil +} + +func (r *_redis) GetExScan(ctx context.Context, key string, duration time.Duration) Scanner { + return newScanner(r.GetEx(ctx, key, duration)) +} + +func (r *_redis) Set(ctx context.Context, key string, value any) error { + bs, err := handleValue(value) + if err != nil { + return err + } + + _, err = r.client.Set(ctx, key, bs, redis.KeepTTL).Result() + return err +} + +func (r *_redis) SetEx(ctx context.Context, key string, value any, duration time.Duration) error { + bs, err := handleValue(value) + if err != nil { + return err + } + + _, err = r.client.SetEX(ctx, key, bs, duration).Result() + + return err +} + +func (r *_redis) Del(ctx context.Context, keys ...string) error { + return r.client.Del(ctx, keys...).Err() +} diff --git a/cache/scanner.go b/cache/scanner.go new file mode 100644 index 0000000..c65d267 --- /dev/null +++ b/cache/scanner.go @@ -0,0 +1,20 @@ +package cache + +import "encoding/json" + +type scanner struct { + err error + bs []byte +} + +func (s *scanner) Scan(model any) error { + if s.err != nil { + return s.err + } + + return json.Unmarshal(s.bs, model) +} + +func newScanner(bs []byte, err error) *scanner { + return &scanner{bs: bs, err: err} +} diff --git a/example/basic/main.go b/example/basic/main.go new file mode 100644 index 0000000..9e05426 --- /dev/null +++ b/example/basic/main.go @@ -0,0 +1,12 @@ +package main + +import "github.com/loveuer/urbac" + +func main() { + rbac, err := urbac.New() + if err != nil { + panic(err) + } + + _ = rbac +} diff --git a/go.mod b/go.mod index 189aab0..1b2bb38 100644 --- a/go.mod +++ b/go.mod @@ -4,22 +4,33 @@ go 1.20 require ( github.com/glebarez/sqlite v1.11.0 + github.com/go-redis/redis/v8 v8.11.5 github.com/jackc/pgtype v1.14.4 + github.com/jedib0t/go-pretty/v6 v6.6.1 + github.com/loveuer/nf v0.2.12 + github.com/samber/lo v1.47.0 github.com/spf13/cast v1.7.0 + golang.org/x/crypto v0.23.0 gorm.io/gorm v1.25.12 ) require ( + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fatih/color v1.17.0 // indirect github.com/glebarez/go-sqlite v1.21.2 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - golang.org/x/sys v0.17.0 // indirect - golang.org/x/text v0.14.0 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.16.0 // indirect modernc.org/libc v1.22.5 // indirect modernc.org/mathutil v1.5.0 // indirect modernc.org/memory v1.5.0 // indirect diff --git a/go.sum b/go.sum index 97c6eae..7c5abb3 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -7,22 +9,29 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= @@ -73,6 +82,8 @@ github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0f github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jedib0t/go-pretty/v6 v6.6.1 h1:iJ65Xjb680rHcikRj6DSIbzCex2huitmc7bDtxYVWyc= +github.com/jedib0t/go-pretty/v6 v6.6.1/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= @@ -91,24 +102,38 @@ github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/loveuer/nf v0.2.12 h1:1Og+ORHsOWKFmy9kKJhjvXDkdbaurH82HjIxuGA3nNM= +github.com/loveuer/nf v0.2.12/go.mod h1:M6reF17/kJBis30H4DxR5hrtgo/oJL4AV4cBe4HzJLw= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= +github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -128,8 +153,8 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -154,8 +179,9 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= @@ -170,6 +196,7 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/sync v0.0.0-20190423024810-112230192c58/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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -189,9 +216,11 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc 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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 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= @@ -206,8 +235,9 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -228,7 +258,9 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/group.go b/group.go deleted file mode 100644 index 7ba9840..0000000 --- a/group.go +++ /dev/null @@ -1,11 +0,0 @@ -package urbac - -type Group struct { - Id uint64 `json:"id" gorm:"primaryKey;column:id"` - CreatedAt int64 `json:"created_at" gorm:"column:created_at;autoCreateTime:milli"` - UpdatedAt int64 `json:"updated_at" gorm:"column:updated_at;autoUpdateTime:milli"` - DeletedAt int64 `json:"deleted_at" gorm:"index;column:deleted_at;default:0"` - - Label string - ParentId uint64 -} diff --git a/internal/tool/cert.go b/internal/tool/cert.go new file mode 100644 index 0000000..029cfc7 --- /dev/null +++ b/internal/tool/cert.go @@ -0,0 +1,104 @@ +package tool + +import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "net" + "time" +) + +func GenerateTlsConfig() (serverTLSConf *tls.Config, clientTLSConf *tls.Config, err error) { + ca := &x509.Certificate{ + SerialNumber: big.NewInt(2019), + Subject: pkix.Name{ + Organization: []string{"Company, INC."}, + Country: []string{"US"}, + Province: []string{"California"}, + Locality: []string{"San Francisco"}, + StreetAddress: []string{"Golden Gate Bridge"}, + PostalCode: []string{"94016"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(99, 0, 0), + IsCA: true, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + BasicConstraintsValid: true, + } + // create our private and public key + caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + return nil, nil, err + } + // create the CA + caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey) + if err != nil { + return nil, nil, err + } + // pem encode + caPEM := new(bytes.Buffer) + pem.Encode(caPEM, &pem.Block{ + Type: "CERTIFICATE", + Bytes: caBytes, + }) + caPrivKeyPEM := new(bytes.Buffer) + pem.Encode(caPrivKeyPEM, &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey), + }) + // set up our server certificate + cert := &x509.Certificate{ + SerialNumber: big.NewInt(2019), + Subject: pkix.Name{ + Organization: []string{"Company, INC."}, + Country: []string{"US"}, + Province: []string{"California"}, + Locality: []string{"San Francisco"}, + StreetAddress: []string{"Golden Gate Bridge"}, + PostalCode: []string{"94016"}, + }, + IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback}, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(1, 0, 0), + SubjectKeyId: []byte{1, 2, 3, 4, 6}, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature, + } + certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + return nil, nil, err + } + certBytes, err := x509.CreateCertificate(rand.Reader, cert, ca, &certPrivKey.PublicKey, caPrivKey) + if err != nil { + return nil, nil, err + } + certPEM := new(bytes.Buffer) + pem.Encode(certPEM, &pem.Block{ + Type: "CERTIFICATE", + Bytes: certBytes, + }) + certPrivKeyPEM := new(bytes.Buffer) + pem.Encode(certPrivKeyPEM, &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey), + }) + serverCert, err := tls.X509KeyPair(certPEM.Bytes(), certPrivKeyPEM.Bytes()) + if err != nil { + return nil, nil, err + } + serverTLSConf = &tls.Config{ + Certificates: []tls.Certificate{serverCert}, + } + certpool := x509.NewCertPool() + certpool.AppendCertsFromPEM(caPEM.Bytes()) + clientTLSConf = &tls.Config{ + RootCAs: certpool, + } + return +} diff --git a/internal/tool/ctx.go b/internal/tool/ctx.go new file mode 100644 index 0000000..82242a3 --- /dev/null +++ b/internal/tool/ctx.go @@ -0,0 +1,38 @@ +package tool + +import ( + "context" + "time" +) + +func Timeout(seconds ...int) (ctx context.Context) { + var ( + duration time.Duration + ) + + if len(seconds) > 0 && seconds[0] > 0 { + duration = time.Duration(seconds[0]) * time.Second + } else { + duration = time.Duration(30) * time.Second + } + + ctx, _ = context.WithTimeout(context.Background(), duration) + + return +} + +func TimeoutCtx(ctx context.Context, seconds ...int) context.Context { + var ( + duration time.Duration + ) + + if len(seconds) > 0 && seconds[0] > 0 { + duration = time.Duration(seconds[0]) * time.Second + } else { + duration = time.Duration(30) * time.Second + } + + nctx, _ := context.WithTimeout(ctx, duration) + + return nctx +} diff --git a/internal/tool/file.go b/internal/tool/file.go new file mode 100644 index 0000000..cefa36d --- /dev/null +++ b/internal/tool/file.go @@ -0,0 +1,30 @@ +package tool + +import ( + "io" + "os" +) + +func CopyFile(src string, dst string) (err error) { + // Open the source file + sourceFile, err := os.Open(src) + if err != nil { + return err + } + defer sourceFile.Close() + + // Create the destination file + destinationFile, err := os.Create(dst) + if err != nil { + return err + } + defer destinationFile.Close() + + // Copy the contents from source to destination + _, err = io.Copy(destinationFile, sourceFile) + if err != nil { + return err + } + + return nil +} diff --git a/internal/tool/human.go b/internal/tool/human.go new file mode 100644 index 0000000..2c7ce71 --- /dev/null +++ b/internal/tool/human.go @@ -0,0 +1,24 @@ +package tool + +import "fmt" + +func HumanDuration(nano int64) string { + duration := float64(nano) + unit := "ns" + if duration >= 1000 { + duration /= 1000 + unit = "us" + } + + if duration >= 1000 { + duration /= 1000 + unit = "ms" + } + + if duration >= 1000 { + duration /= 1000 + unit = " s" + } + + return fmt.Sprintf("%6.2f%s", duration, unit) +} diff --git a/internal/tool/must.go b/internal/tool/must.go new file mode 100644 index 0000000..53f1d19 --- /dev/null +++ b/internal/tool/must.go @@ -0,0 +1,9 @@ +package tool + +func Must(errs ...error) { + for _, err := range errs { + if err != nil { + panic(err.Error()) + } + } +} diff --git a/internal/tool/password.go b/internal/tool/password.go new file mode 100644 index 0000000..9886bd4 --- /dev/null +++ b/internal/tool/password.go @@ -0,0 +1,80 @@ +package tool + +import ( + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "regexp" + "strconv" + "strings" + + "golang.org/x/crypto/pbkdf2" +) + +const ( + EncryptHeader string = "pbkdf2:sha256" // 用户密码加密 +) + +func NewPassword(password string) string { + return EncryptPassword(password, RandomString(8), int(RandomInt(50000)+100000)) +} + +func ComparePassword(in, db string) bool { + strs := strings.Split(db, "$") + if len(strs) != 3 { + return false + } + + encs := strings.Split(strs[0], ":") + if len(encs) != 3 { + return false + } + + encIteration, err := strconv.Atoi(encs[2]) + if err != nil { + return false + } + + return EncryptPassword(in, strs[1], encIteration) == db +} + +func EncryptPassword(password, salt string, iter int) string { + hash := pbkdf2.Key([]byte(password), []byte(salt), iter, 32, sha256.New) + encrypted := hex.EncodeToString(hash) + return fmt.Sprintf("%s:%d$%s$%s", EncryptHeader, iter, salt, encrypted) +} + +func CheckPassword(password string) error { + if len(password) < 8 || len(password) > 32 { + return errors.New("密码长度不符合") + } + + var ( + err error + match bool + patternList = []string{`[0-9]+`, `[a-z]+`, `[A-Z]+`, `[!@#%]+`} //, `[~!@#$%^&*?_-]+`} + matchAccount = 0 + tips = []string{"缺少数字", "缺少小写字母", "缺少大写字母", "缺少'!@#%'"} + locktips = make([]string, 0) + ) + + for idx, pattern := range patternList { + match, err = regexp.MatchString(pattern, password) + if err != nil { + return errors.New("密码强度不够") + } + + if match { + matchAccount++ + } else { + locktips = append(locktips, tips[idx]) + } + } + + if matchAccount < 3 { + return fmt.Errorf("密码强度不够, 可能 %s", strings.Join(locktips, ", ")) + } + + return nil +} diff --git a/internal/tool/password_test.go b/internal/tool/password_test.go new file mode 100644 index 0000000..aabd667 --- /dev/null +++ b/internal/tool/password_test.go @@ -0,0 +1,11 @@ +package tool + +import "testing" + +func TestEncPassword(t *testing.T) { + password := "123456" + + result := EncryptPassword(password, RandomString(8), 50000) + + t.Logf("sum => %s", result) +} diff --git a/internal/tool/random.go b/internal/tool/random.go new file mode 100644 index 0000000..266cb4c --- /dev/null +++ b/internal/tool/random.go @@ -0,0 +1,54 @@ +package tool + +import ( + "crypto/rand" + "math/big" +) + +var ( + letters = []byte("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + letterNum = []byte("0123456789") + letterLow = []byte("abcdefghijklmnopqrstuvwxyz") + letterCap = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ") + letterSyb = []byte("!@#$%^&*()_+-=") +) + +func RandomInt(max int64) int64 { + num, _ := rand.Int(rand.Reader, big.NewInt(max)) + return num.Int64() +} + +func RandomString(length int) string { + result := make([]byte, length) + for i := 0; i < length; i++ { + num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letters)))) + result[i] = letters[num.Int64()] + } + return string(result) +} + +func RandomPassword(length int, withSymbol bool) string { + result := make([]byte, length) + kind := 3 + if withSymbol { + kind++ + } + + for i := 0; i < length; i++ { + switch i % kind { + case 0: + num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letterNum)))) + result[i] = letterNum[num.Int64()] + case 1: + num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letterLow)))) + result[i] = letterLow[num.Int64()] + case 2: + num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letterCap)))) + result[i] = letterCap[num.Int64()] + case 3: + num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letterSyb)))) + result[i] = letterSyb[num.Int64()] + } + } + return string(result) +} diff --git a/internal/tool/slice.go b/internal/tool/slice.go new file mode 100644 index 0000000..05a7dd5 --- /dev/null +++ b/internal/tool/slice.go @@ -0,0 +1,5 @@ +package tool + +func Bulk[T any](slice []T, size int) { + // todo +} diff --git a/internal/tool/slice_test.go b/internal/tool/slice_test.go new file mode 100644 index 0000000..05b1676 --- /dev/null +++ b/internal/tool/slice_test.go @@ -0,0 +1 @@ +package tool diff --git a/internal/tool/table.go b/internal/tool/table.go new file mode 100644 index 0000000..ffaaf31 --- /dev/null +++ b/internal/tool/table.go @@ -0,0 +1,124 @@ +package tool + +import ( + "encoding/json" + "fmt" + "github.com/jedib0t/go-pretty/v6/table" + "github.com/loveuer/nf/nft/log" + "io" + "os" + "reflect" + "strings" +) + +func TablePrinter(data any, writers ...io.Writer) { + var w io.Writer = os.Stdout + if len(writers) > 0 && writers[0] != nil { + w = writers[0] + } + + t := table.NewWriter() + structPrinter(t, "", data) + _, _ = fmt.Fprintln(w, t.Render()) +} + +func structPrinter(w table.Writer, prefix string, item any) { +Start: + rv := reflect.ValueOf(item) + if rv.IsZero() { + return + } + + for rv.Type().Kind() == reflect.Pointer { + rv = rv.Elem() + } + + switch rv.Type().Kind() { + case reflect.Invalid, + reflect.Uintptr, + reflect.Chan, + reflect.Func, + reflect.UnsafePointer: + case reflect.Bool, + reflect.Int, + reflect.Int8, + reflect.Int16, + reflect.Int32, + reflect.Int64, + reflect.Uint, + reflect.Uint8, + reflect.Uint16, + reflect.Uint32, + reflect.Uint64, + reflect.Float32, + reflect.Float64, + reflect.Complex64, + reflect.Complex128, + reflect.Interface: + w.AppendRow(table.Row{strings.TrimPrefix(prefix, "."), rv.Interface()}) + case reflect.String: + val := rv.String() + if len(val) <= 160 { + w.AppendRow(table.Row{strings.TrimPrefix(prefix, "."), val}) + return + } + + w.AppendRow(table.Row{strings.TrimPrefix(prefix, "."), val[0:64] + "..." + val[len(val)-64:]}) + case reflect.Array, reflect.Slice: + for i := 0; i < rv.Len(); i++ { + p := strings.Join([]string{prefix, fmt.Sprintf("[%d]", i)}, ".") + structPrinter(w, p, rv.Index(i).Interface()) + } + case reflect.Map: + for _, k := range rv.MapKeys() { + structPrinter(w, fmt.Sprintf("%s.{%v}", prefix, k), rv.MapIndex(k).Interface()) + } + case reflect.Pointer: + goto Start + case reflect.Struct: + for i := 0; i < rv.NumField(); i++ { + p := fmt.Sprintf("%s.%s", prefix, rv.Type().Field(i).Name) + field := rv.Field(i) + + //log.Debug("TablePrinter: prefix: %s, field: %v", p, rv.Field(i)) + + if !field.CanInterface() { + return + } + + structPrinter(w, p, field.Interface()) + } + } +} + +func TableMapPrinter(data []byte) { + m := make(map[string]any) + if err := json.Unmarshal(data, &m); err != nil { + log.Warn(err.Error()) + return + } + + t := table.NewWriter() + addRow(t, "", m) + fmt.Println(t.Render()) +} + +func addRow(w table.Writer, prefix string, m any) { + rv := reflect.ValueOf(m) + switch rv.Type().Kind() { + case reflect.Map: + for _, k := range rv.MapKeys() { + key := k.String() + if prefix != "" { + key = strings.Join([]string{prefix, k.String()}, ".") + } + addRow(w, key, rv.MapIndex(k).Interface()) + } + case reflect.Slice, reflect.Array: + for i := 0; i < rv.Len(); i++ { + addRow(w, fmt.Sprintf("%s[%d]", prefix, i), rv.Index(i).Interface()) + } + default: + w.AppendRow(table.Row{prefix, m}) + } +} diff --git a/internal/tool/time.go b/internal/tool/time.go new file mode 100644 index 0000000..a193f20 --- /dev/null +++ b/internal/tool/time.go @@ -0,0 +1,13 @@ +package tool + +import "time" + +// TodayMidnight 返回今日凌晨 +func TodayMidnight() (midnight time.Time) { + now := time.Now() + + year, month, day := now.Date() + midnight = time.Date(year, month, day, 0, 0, 0, 0, time.Local) + + return +} diff --git a/privilege.go b/privilege.go index a70564a..45f7d4f 100644 --- a/privilege.go +++ b/privilege.go @@ -1,16 +1,53 @@ package urbac +import ( + "context" + "fmt" + "strings" +) + type Privilege struct { - Id uint64 `json:"id" gorm:"primaryKey;column:id"` - CreatedAt int64 `json:"created_at" gorm:"column:created_at;autoCreateTime:milli"` - UpdatedAt int64 `json:"updated_at" gorm:"column:updated_at;autoUpdateTime:milli"` - DeletedAt int64 `json:"deleted_at" gorm:"index;column:deleted_at;default:0"` - Code string - Label string - ParentId uint64 - ScopeGroup uint64 + CreatedAt int64 `json:"created_at" gorm:"column:created_at;autoCreateTime:milli"` + UpdatedAt int64 `json:"updated_at" gorm:"column:updated_at;autoUpdateTime:milli"` + DeletedAt int64 `json:"deleted_at" gorm:"index;column:deleted_at;default:0"` + Code string `json:"code" gorm:"column:code;primaryKey"` + Label string + ParentId uint64 + Scope string } -func (u *Urbac) NewPrivilege(code, label string, parentId, scopeGroup uint64) *Privilege { - panic("todo") +func (u *Urbac) newPrivilege(ctx context.Context, code, label string, parentId uint64, scope string) (*Privilege, error) { + p := &Privilege{Code: code, Label: label, ParentId: parentId, Scope: scope} + + codes := strings.SplitN(code, ":", 4) + if len(codes) != 4 { + return nil, fmt.Errorf("invalid code format") + } + + wailcard := false + for _, item := range codes { + if item == "*" { + wailcard = true + } + + if wailcard && item != "*" { + return nil, fmt.Errorf("invalid code format") + } + + if len(item) > 8 { + return nil, fmt.Errorf("invalid code format: code snippet too long") + } + } + + if codes[0] != "*" { + if _, err := u.GetScopeGroup(ctx, codes[0]); err != nil { + return nil, err + } + } + + if err := u.store.Session(ctx).Create(p).Error; err != nil { + return nil, err + } + + return p, nil } diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..0cd3cb4 --- /dev/null +++ b/readme.md @@ -0,0 +1,11 @@ +### privilege code 设计 + +> {scope}:{module}:{category}:{action} + +example: + +- `*:*:*:*` +- `pro:*:*:*` +- `pro:topic:*:*` +- `admin:audit:flow:*` +- `admin:user:user:create` diff --git a/role.go b/role.go index 8de5310..a013a42 100644 --- a/role.go +++ b/role.go @@ -1,22 +1,53 @@ package urbac -import "github.com/loveuer/urbac/internal/sqlType" +import ( + "context" + + "github.com/loveuer/urbac/internal/sqlType" + "github.com/samber/lo" +) type Role struct { - Id uint64 `json:"id" gorm:"primaryKey;column:id"` - CreatedAt int64 `json:"created_at" gorm:"column:created_at;autoCreateTime:milli"` - UpdatedAt int64 `json:"updated_at" gorm:"column:updated_at;autoUpdateTime:milli"` - DeletedAt int64 `json:"deleted_at" gorm:"index;column:deleted_at;default:0"` - Name string - Label string - ParentId uint64 - Privileges sqlType.NumSlice[uint64] + CreatedAt int64 `json:"created_at" gorm:"column:created_at;autoCreateTime:milli"` + UpdatedAt int64 `json:"updated_at" gorm:"column:updated_at;autoUpdateTime:milli"` + DeletedAt int64 `json:"deleted_at" gorm:"index;column:deleted_at;default:0"` + Name string `json:"name" gorm:"primaryKey;column:name"` + Label string `json:"label" gorm:"column:label"` + Parent string `json:"parent" gorm:"column:parent"` + PrivilegeCodes sqlType.StrSlice `json:"privilege_codes" gorm:"column:privilege_codes"` } -func (r *Role) Grant(privileges ...*Privilege) error { - panic("todo") +func (u *Urbac) newRole(ctx context.Context, name, label, parent string, privileges ...*Privilege) (*Role, error) { + ps := lo.FilterMap( + privileges, + func(p *Privilege, _ int) (string, bool) { + if p == nil { + return "", false + } + + return p.Code, p.Code != "" + }, + ) + + r := &Role{ + Name: name, + Label: label, + Parent: parent, + PrivilegeCodes: ps, + } + + if err := u.store.Session(ctx).Create(r).Error; err != nil { + return nil, err + } + + return r, nil } -func (r *Role) Revoke(privileges ...*Privilege) error { - panic("todo") +func (u *Urbac) GetRole(ctx context.Context, name string) (*Role, error) { + var r Role + if err := u.store.Session(ctx).Take(&r, "name = ?", name).Error; err != nil { + return nil, err + } + + return &r, nil } diff --git a/scope.go b/scope.go new file mode 100644 index 0000000..1631d96 --- /dev/null +++ b/scope.go @@ -0,0 +1,28 @@ +package urbac + +import "context" + +type Scope struct { + CreatedAt int64 `json:"created_at" gorm:"column:created_at;autoCreateTime:milli"` + UpdatedAt int64 `json:"updated_at" gorm:"column:updated_at;autoUpdateTime:milli"` + DeletedAt int64 `json:"deleted_at" gorm:"index;column:deleted_at;default:0"` + Name string `json:"name" gorm:"primaryKey;column:name"` + Label string `json:"label" gorm:"column:label"` + Parent string `json:"parent" gorm:"column:parent"` +} + +func (u *Urbac) newScope(ctx context.Context, name, label, parent string) (*Scope, error) { + s := &Scope{Name: name, Label: label, Parent: parent} + if err := u.store.Session(ctx).Create(s).Error; err != nil { + return nil, err + } + + return s, nil +} + +func (u *Urbac) GetScopeGroup(ctx context.Context, name string) (*Scope, error) { + scope := new(Scope) + err := u.store.Session(ctx).Where("name = ?", name).Take(scope).Error + + return scope, err +} diff --git a/sqlite.db b/sqlite.db new file mode 100644 index 0000000000000000000000000000000000000000..1efbe66d6e7e69a8fa1e153aeb5b97a8f16d26f6 GIT binary patch literal 40960 zcmeI&Pfyce9Ki9Gv8_W!@zg9F?2HEn#7KxSAu%{*8X|(?GpNI*UE#HUj>GM4OwNIZ<8vg} z!^lSZ^6=#FzTZSb009ILKmY**5I_Kd{}(8wlG=4mZpPh;?QFaDwo$K^orYmMWyf=D z!}3IZ=*~(ZUo7YgOY?Q2QwtkvBux9oRgtX6HO(QbaYvhX0k@?5`Pct zzgxEbOivCZw7JY_vcUx#)4?|8|BGqO@1=Ar8*ODI1Q0*~0R#|0009ILKmY**5a?F{ z@q(ZEf4|;d#)SX^2q1s}0tg_000IagfPfHS{?CJe00IagfB*srAbx8#3F2q1s}0tg_000IagfB*srAka?&L$R?``&WQq{(m5;2mLfa#)1F>2q1s} z0tg_000IagfB*uaKrE)Dg7yEqlDZqVvPA#^1Q0*~0R#|0009ILKmdW>5x622|7(}h z7yV`bm8Yd37pwmFK7ZVM^ZLiz_b*=s@BjBDb-#B;#+4#~00IagfB*srAbSu{UIZb400IagfB*srAb