mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-09-08 11:53:07 -04:00
Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41a9ad809d | ||
|
|
0f97fc5e60 | ||
|
|
f060ca4660 | ||
|
|
3112d136ff | ||
|
|
4414bd0d08 | ||
|
|
8593a1409a | ||
|
|
6d9d2baa80 | ||
|
|
d6c6340705 | ||
|
|
9915fc8ea7 | ||
|
|
6dff206e8a | ||
|
|
a478f59c50 | ||
|
|
48a9448ba7 |
No files matched your search
@@ -33,8 +33,9 @@ require (
|
||||
github.com/go-micro/plugins/v4/store/nats-js-kv v0.0.0-20240726082623-6831adfdcdc4
|
||||
github.com/go-micro/plugins/v4/wrapper/monitoring/prometheus v1.2.0
|
||||
github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry v1.2.0
|
||||
github.com/go-playground/validator/v10 v10.30.2
|
||||
github.com/go-playground/validator/v10 v10.30.3
|
||||
github.com/go-resty/resty/v2 v2.17.2
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/golang/protobuf v1.5.4
|
||||
github.com/google/go-cmp v0.7.0
|
||||
@@ -52,7 +53,6 @@ require (
|
||||
github.com/leonelquinteros/gotext v1.7.3-0.20260422134830-b012b4ccae69
|
||||
github.com/libregraph/idm v0.5.0
|
||||
github.com/libregraph/lico v0.66.0
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/mna/pigeon v1.3.0
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
||||
github.com/nats-io/nats-server/v2 v2.14.0
|
||||
@@ -64,7 +64,7 @@ require (
|
||||
github.com/open-policy-agent/opa v1.15.2
|
||||
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89
|
||||
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260310090739-853d972b282d
|
||||
github.com/opencloud-eu/reva/v2 v2.46.2-0.20260602061404-0e975e5456eb
|
||||
github.com/opencloud-eu/reva/v2 v2.46.2
|
||||
github.com/opensearch-project/opensearch-go/v4 v4.6.0
|
||||
github.com/orcaman/concurrent-map v1.0.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
@@ -102,7 +102,7 @@ require (
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0
|
||||
go.opentelemetry.io/otel/sdk v1.44.0
|
||||
go.opentelemetry.io/otel/trace v1.44.0
|
||||
golang.org/x/crypto v0.51.0
|
||||
golang.org/x/crypto v0.52.0
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f
|
||||
golang.org/x/image v0.40.0
|
||||
golang.org/x/net v0.55.0
|
||||
@@ -223,7 +223,6 @@ require (
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/go-test/deep v1.1.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
@@ -315,7 +314,7 @@ require (
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
|
||||
github.com/pablodz/inotifywaitgo v0.0.9 // indirect
|
||||
github.com/pablodz/inotifywaitgo v0.0.12 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
@@ -413,5 +412,4 @@ exclude github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
||||
|
||||
replace github.com/go-micro/plugins/v4/store/nats-js-kv => github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a
|
||||
|
||||
// to get the logger injection (https://github.com/pablodz/inotifywaitgo/pull/11)
|
||||
replace github.com/pablodz/inotifywaitgo v0.0.9 => github.com/opencloud-eu/inotifywaitgo v0.0.0-20251111171128-a390bae3c5e9
|
||||
replace github.com/opencloud-eu/reva/v2 => github.com/opencloud-eu/reva/v2 v2.46.3-0.20260609111154-079ecc53abaa
|
||||
@@ -449,8 +449,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ=
|
||||
github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc=
|
||||
github.com/go-playground/validator/v10 v10.30.3 h1:4MU6YkEwx7GbcPJOZxrtbu+QfF3pJLJuaYTeAH0DYy8=
|
||||
github.com/go-playground/validator/v10 v10.30.3/go.mod h1:4Axh7oCNGcoGkqLoE4YWt6n20mcEIsPRlB7vPk3lpyc=
|
||||
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-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
|
||||
@@ -466,8 +466,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
|
||||
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
@@ -857,8 +857,6 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mna/pigeon v1.3.0 h1:/3fzVrl1C2RK3x04tyL+ribn+3S3VSEFFbCFLmRPAoc=
|
||||
@@ -948,12 +946,10 @@ github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-202505121527
|
||||
github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a/go.mod h1:pjcozWijkNPbEtX5SIQaxEW/h8VAVZYTLx+70bmB3LY=
|
||||
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89 h1:W1ms+lP5lUUIzjRGDg93WrQfZJZCaV1ZP3KeyXi8bzY=
|
||||
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89/go.mod h1:vigJkNss1N2QEceCuNw/ullDehncuJNFB6mEnzfq9UI=
|
||||
github.com/opencloud-eu/inotifywaitgo v0.0.0-20251111171128-a390bae3c5e9 h1:dIftlX03Bzfbujhp9B54FbgER0VBDWJi/w8RBxJlzxU=
|
||||
github.com/opencloud-eu/inotifywaitgo v0.0.0-20251111171128-a390bae3c5e9/go.mod h1:JWyDC6H+5oZRdUJUgKuaye+8Ph5hEs6HVzVoPKzWSGI=
|
||||
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260310090739-853d972b282d h1:JcqGDiyrcaQwVyV861TUyQgO7uEmsjkhfm7aQd84dOw=
|
||||
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260310090739-853d972b282d/go.mod h1:pzatilMEHZFT3qV7C/X3MqOa3NlRQuYhlRhZTL+hN6Q=
|
||||
github.com/opencloud-eu/reva/v2 v2.46.2-0.20260602061404-0e975e5456eb h1:7LXIqaeVQqOghfTw44kyLSak+cR0TIBl55M4+Tb08f8=
|
||||
github.com/opencloud-eu/reva/v2 v2.46.2-0.20260602061404-0e975e5456eb/go.mod h1:V1bwCQXM1pGmswtjdSJVIweA9Vv5EmtUZr/7Cm5086A=
|
||||
github.com/opencloud-eu/reva/v2 v2.46.3-0.20260609111154-079ecc53abaa h1:MSS7EwbPh74IpH0Tidn6JoeMNF1D1A66MaWk0Ugqnv0=
|
||||
github.com/opencloud-eu/reva/v2 v2.46.3-0.20260609111154-079ecc53abaa/go.mod h1:RoFQt+u7edxwzHr1IZ2Y6VaDinMiRPQupAvMBy3WVmE=
|
||||
github.com/opencloud-eu/secure v0.0.0-20260312082735-b6f5cb2244e4 h1:l2oB/RctH+t8r7QBj5p8thfEHCM/jF35aAY3WQ3hADI=
|
||||
github.com/opencloud-eu/secure v0.0.0-20260312082735-b6f5cb2244e4/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
@@ -972,6 +968,8 @@ github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CF
|
||||
github.com/ovh/go-ovh v1.1.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
|
||||
github.com/pablodz/inotifywaitgo v0.0.12 h1:DVJJTFMDlSAdO46yisPF2vzozs/r+4k6ih8MVghq78M=
|
||||
github.com/pablodz/inotifywaitgo v0.0.12/go.mod h1:JWyDC6H+5oZRdUJUgKuaye+8Ph5hEs6HVzVoPKzWSGI=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
@@ -1362,8 +1360,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
||||
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package revaconfig
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/services/app-registry/pkg/config"
|
||||
)
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package defaults
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/config/defaults"
|
||||
"github.com/opencloud-eu/opencloud/pkg/shared"
|
||||
"github.com/opencloud-eu/opencloud/pkg/structs"
|
||||
"github.com/opencloud-eu/opencloud/services/auth-basic/pkg/config"
|
||||
@@ -38,8 +35,7 @@ func DefaultConfig() *config.Config {
|
||||
AuthProvider: "ldap",
|
||||
AuthProviders: config.AuthProviders{
|
||||
LDAP: config.LDAPProvider{
|
||||
URI: "ldaps://localhost:9235",
|
||||
CACert: filepath.Join(defaults.BaseDataPath(), "idm", "ldap.crt"),
|
||||
URI: "ldap://localhost:9236",
|
||||
Insecure: false,
|
||||
UserBaseDN: "ou=users,o=libregraph-idm",
|
||||
GroupBaseDN: "ou=groups,o=libregraph-idm",
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package defaults
|
||||
|
||||
import (
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/config/defaults"
|
||||
"github.com/opencloud-eu/opencloud/pkg/shared"
|
||||
"github.com/opencloud-eu/opencloud/pkg/structs"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/config"
|
||||
@@ -79,9 +77,8 @@ func DefaultConfig() *config.Config {
|
||||
Identity: config.Identity{
|
||||
Backend: "ldap",
|
||||
LDAP: config.LDAP{
|
||||
URI: "ldaps://localhost:9235",
|
||||
URI: "ldap://localhost:9236",
|
||||
Insecure: false,
|
||||
CACert: path.Join(defaults.BaseDataPath(), "idm", "ldap.crt"),
|
||||
BindDN: "uid=libregraph,ou=sysusers,o=libregraph-idm",
|
||||
UseServerUUID: false,
|
||||
UsePasswordModExOp: true,
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package defaults
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/config/defaults"
|
||||
"github.com/opencloud-eu/opencloud/pkg/shared"
|
||||
"github.com/opencloud-eu/opencloud/pkg/structs"
|
||||
"github.com/opencloud-eu/opencloud/services/groups/pkg/config"
|
||||
@@ -38,8 +35,7 @@ func DefaultConfig() *config.Config {
|
||||
Driver: "ldap",
|
||||
Drivers: config.Drivers{
|
||||
LDAP: config.LDAPDriver{
|
||||
URI: "ldaps://localhost:9235",
|
||||
CACert: filepath.Join(defaults.BaseDataPath(), "idm", "ldap.crt"),
|
||||
URI: "ldap://localhost:9236",
|
||||
Insecure: false,
|
||||
UserBaseDN: "ou=users,o=libregraph-idm",
|
||||
GroupBaseDN: "ou=groups,o=libregraph-idm",
|
||||
|
||||
@@ -8,10 +8,11 @@ import (
|
||||
"html/template"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/config/configlog"
|
||||
pkgcrypto "github.com/opencloud-eu/opencloud/pkg/crypto"
|
||||
"github.com/opencloud-eu/opencloud/pkg/config/defaults"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/pkg/runner"
|
||||
"github.com/opencloud-eu/opencloud/services/idm"
|
||||
@@ -47,23 +48,25 @@ func Server(cfg *config.Config) *cobra.Command {
|
||||
gr := runner.NewGroup()
|
||||
{
|
||||
servercfg := server.Config{
|
||||
Logger: log.LogrusWrap(logger.Logger),
|
||||
LDAPHandler: "boltdb",
|
||||
LDAPSListenAddr: cfg.IDM.LDAPSAddr,
|
||||
TLSCertFile: cfg.IDM.Cert,
|
||||
TLSKeyFile: cfg.IDM.Key,
|
||||
LDAPBaseDN: "o=libregraph-idm",
|
||||
LDAPAdminDN: "uid=libregraph,ou=sysusers,o=libregraph-idm",
|
||||
Logger: log.LogrusWrap(logger.Logger),
|
||||
LDAPHandler: "boltdb",
|
||||
LDAPListenAddr: cfg.IDM.LDAPAddr,
|
||||
LDAPBaseDN: "o=libregraph-idm",
|
||||
LDAPAdminDN: "uid=libregraph,ou=sysusers,o=libregraph-idm",
|
||||
|
||||
BoltDBFile: cfg.IDM.DatabasePath,
|
||||
}
|
||||
|
||||
if cfg.IDM.LDAPSAddr != "" {
|
||||
// Generate a self-signing cert if no certificate is present
|
||||
if err := pkgcrypto.GenCert(cfg.IDM.Cert, cfg.IDM.Key, logger); err != nil {
|
||||
logger.Fatal().Err(err).Msgf("Could not generate test-certificate")
|
||||
}
|
||||
servercfg.LDAPSListenAddr = cfg.IDM.LDAPSAddr
|
||||
servercfg.TLSCertFile = cfg.IDM.Cert
|
||||
servercfg.TLSKeyFile = cfg.IDM.Key
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(path.Join(defaults.BaseDataPath(), "idm"), 0700); err != nil {
|
||||
logger.Fatal().Err(err).Msgf("Could not create data directory for idm")
|
||||
}
|
||||
|
||||
if _, err := os.Stat(servercfg.BoltDBFile); errors.Is(err, os.ErrNotExist) {
|
||||
logger.Debug().Msg("Bootstrapping IDM database")
|
||||
if err = bootstrap(logger, cfg, servercfg); err != nil {
|
||||
|
||||
@@ -27,6 +27,7 @@ type Config struct {
|
||||
|
||||
type Settings struct {
|
||||
LDAPSAddr string `yaml:"ldaps_addr" env:"IDM_LDAPS_ADDR" desc:"Listen address for the LDAPS listener (ip-addr:port)." introductionVersion:"1.0.0"`
|
||||
LDAPAddr string
|
||||
Cert string `yaml:"cert" env:"IDM_LDAPS_CERT" desc:"File name of the TLS server certificate for the LDAPS listener. If not defined, the root directory derives from $OC_BASE_DATA_PATH/idm." introductionVersion:"1.0.0"`
|
||||
Key string `yaml:"key" env:"IDM_LDAPS_KEY" desc:"File name for the TLS certificate key for the server certificate. If not defined, the root directory derives from $OC_BASE_DATA_PATH/idm." introductionVersion:"1.0.0"`
|
||||
DatabasePath string `yaml:"database" env:"IDM_DATABASE_PATH" desc:"Full path to the IDM backend database. If not defined, the root directory derives from $OC_BASE_DATA_PATH/idm." introductionVersion:"1.0.0"`
|
||||
|
||||
@@ -30,9 +30,7 @@ func DefaultConfig() *config.Config {
|
||||
CreateDemoUsers: false,
|
||||
DemoUsersIssuerUrl: "https://localhost:9200",
|
||||
IDM: config.Settings{
|
||||
LDAPSAddr: "127.0.0.1:9235",
|
||||
Cert: path.Join(defaults.BaseDataPath(), "idm", "ldap.crt"),
|
||||
Key: path.Join(defaults.BaseDataPath(), "idm", "ldap.key"),
|
||||
LDAPAddr: "127.0.0.1:9236",
|
||||
DatabasePath: path.Join(defaults.BaseDataPath(), "idm", "idm.boltdb"),
|
||||
},
|
||||
}
|
||||
@@ -51,5 +49,5 @@ func EnsureDefaults(cfg *config.Config) {
|
||||
|
||||
// Sanitize sanitizes the configuration
|
||||
func Sanitize(cfg *config.Config) {
|
||||
// nothing to sanitize here
|
||||
// nothing to do yet
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
occfg "github.com/opencloud-eu/opencloud/pkg/config"
|
||||
"github.com/opencloud-eu/opencloud/pkg/shared"
|
||||
@@ -53,5 +54,25 @@ func Validate(cfg *config.Config) error {
|
||||
return shared.MissingServiceUserPassword(cfg.Service.Name, "REVA")
|
||||
}
|
||||
|
||||
ip, err := net.ResolveTCPAddr("tcp", cfg.IDM.LDAPAddr) // validate the LDAP address if set
|
||||
|
||||
if err != nil {
|
||||
return errors.New("invalid configuration: 'ldap_addr' is not a valid address")
|
||||
}
|
||||
|
||||
if !ip.IP.IsLoopback() {
|
||||
// loopback addresses are allowed to be used with ldap_addr, but not with ldaps_addr, for security reasons
|
||||
return errors.New("invalid configuration: 'ldap_addr' is set but 'ldaps_addr' is not set. For security reasons, the 'ldap_addr' setting is only allowed to be used with loopback addresses. Please set 'ldaps_addr' to a valid address and port to listen for LDAPS connections")
|
||||
}
|
||||
|
||||
if cfg.IDM.LDAPSAddr != "" {
|
||||
if cfg.IDM.Cert == "" {
|
||||
return errors.New("invalid configuration: 'ldaps_addr' is set but 'cert' is not set. Please set 'cert' to a valid path to a TLS certificate")
|
||||
}
|
||||
if cfg.IDM.Key == "" {
|
||||
return errors.New("invalid configuration: 'ldaps_addr' is set but 'key' is not set. Please set 'key' to a valid path to a TLS certificate key")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -35,7 +35,7 @@
|
||||
"@types/react-dom": "^17.0.26",
|
||||
"@types/react-redux": "^7.1.34",
|
||||
"@types/redux-logger": "^3.0.13",
|
||||
"axios": "^1.16.0",
|
||||
"axios": "^1.16.1",
|
||||
"i18next": "^26.3.0",
|
||||
"i18next-browser-languagedetector": "^8.2.1",
|
||||
"i18next-resources-to-backend": "^1.2.1",
|
||||
|
||||
@@ -119,8 +119,7 @@ func DefaultConfig() *config.Config {
|
||||
},
|
||||
},
|
||||
Ldap: config.Ldap{
|
||||
URI: "ldaps://localhost:9235",
|
||||
TLSCACert: filepath.Join(defaults.BaseDataPath(), "idm", "ldap.crt"),
|
||||
URI: "ldap://localhost:9236",
|
||||
BindDN: "uid=idp,ou=sysusers,o=libregraph-idm",
|
||||
BaseDN: "ou=users,o=libregraph-idm",
|
||||
Scope: "sub",
|
||||
|
||||
Generated
+50
-19
@@ -34,8 +34,8 @@ importers:
|
||||
specifier: ^3.0.13
|
||||
version: 3.0.13
|
||||
axios:
|
||||
specifier: ^1.16.0
|
||||
version: 1.16.0
|
||||
specifier: ^1.16.1
|
||||
version: 1.16.1
|
||||
i18next:
|
||||
specifier: ^26.3.0
|
||||
version: 26.3.0(typescript@6.0.3)
|
||||
@@ -1882,6 +1882,10 @@ packages:
|
||||
resolution: {integrity: sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==}
|
||||
engines: {node: '>=8.9'}
|
||||
|
||||
agent-base@6.0.2:
|
||||
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
||||
engines: {node: '>= 6.0.0'}
|
||||
|
||||
agent-base@7.1.4:
|
||||
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
|
||||
engines: {node: '>= 14'}
|
||||
@@ -1978,8 +1982,8 @@ packages:
|
||||
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
axios@1.16.0:
|
||||
resolution: {integrity: sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==}
|
||||
axios@1.16.1:
|
||||
resolution: {integrity: sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==}
|
||||
|
||||
b4a@1.8.0:
|
||||
resolution: {integrity: sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==}
|
||||
@@ -2943,6 +2947,10 @@ packages:
|
||||
resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
hasown@2.0.4:
|
||||
resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
he@1.2.0:
|
||||
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
|
||||
hasBin: true
|
||||
@@ -2996,6 +3004,10 @@ packages:
|
||||
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
https-proxy-agent@5.0.1:
|
||||
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
https-proxy-agent@7.0.6:
|
||||
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
|
||||
engines: {node: '>= 14'}
|
||||
@@ -7112,6 +7124,12 @@ snapshots:
|
||||
loader-utils: 2.0.4
|
||||
regex-parser: 2.3.1
|
||||
|
||||
agent-base@6.0.2:
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
agent-base@7.1.4: {}
|
||||
|
||||
ajv-formats@2.1.1(ajv@8.20.0):
|
||||
@@ -7200,13 +7218,15 @@ snapshots:
|
||||
dependencies:
|
||||
possible-typed-array-names: 1.1.0
|
||||
|
||||
axios@1.16.0:
|
||||
axios@1.16.1:
|
||||
dependencies:
|
||||
follow-redirects: 1.16.0
|
||||
form-data: 4.0.5
|
||||
https-proxy-agent: 5.0.1
|
||||
proxy-from-env: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
- supports-color
|
||||
|
||||
b4a@1.8.0: {}
|
||||
|
||||
@@ -7894,7 +7914,7 @@ snapshots:
|
||||
has-property-descriptors: 1.0.2
|
||||
has-proto: 1.2.0
|
||||
has-symbols: 1.1.0
|
||||
hasown: 2.0.3
|
||||
hasown: 2.0.4
|
||||
internal-slot: 1.1.0
|
||||
is-array-buffer: 3.0.5
|
||||
is-callable: 1.2.7
|
||||
@@ -7946,7 +7966,7 @@ snapshots:
|
||||
es-errors: 1.3.0
|
||||
get-intrinsic: 1.3.0
|
||||
has-tostringtag: 1.0.2
|
||||
hasown: 2.0.3
|
||||
hasown: 2.0.4
|
||||
|
||||
es-to-primitive@1.3.0:
|
||||
dependencies:
|
||||
@@ -8109,7 +8129,7 @@ snapshots:
|
||||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
es-set-tostringtag: 2.1.0
|
||||
hasown: 2.0.3
|
||||
hasown: 2.0.4
|
||||
mime-types: 2.1.35
|
||||
|
||||
fraction.js@5.3.4: {}
|
||||
@@ -8180,7 +8200,7 @@ snapshots:
|
||||
call-bound: 1.0.4
|
||||
define-properties: 1.2.1
|
||||
functions-have-names: 1.2.3
|
||||
hasown: 2.0.3
|
||||
hasown: 2.0.4
|
||||
is-callable: 1.2.7
|
||||
|
||||
functions-have-names@1.2.3: {}
|
||||
@@ -8196,12 +8216,12 @@ snapshots:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
es-define-property: 1.0.1
|
||||
es-errors: 1.3.0
|
||||
es-object-atoms: 1.1.1
|
||||
es-object-atoms: 1.1.2
|
||||
function-bind: 1.1.2
|
||||
get-proto: 1.0.1
|
||||
gopd: 1.2.0
|
||||
has-symbols: 1.1.0
|
||||
hasown: 2.0.3
|
||||
hasown: 2.0.4
|
||||
math-intrinsics: 1.1.0
|
||||
|
||||
get-own-enumerable-property-symbols@3.0.2: {}
|
||||
@@ -8209,7 +8229,7 @@ snapshots:
|
||||
get-proto@1.0.1:
|
||||
dependencies:
|
||||
dunder-proto: 1.0.1
|
||||
es-object-atoms: 1.1.1
|
||||
es-object-atoms: 1.1.2
|
||||
|
||||
get-symbol-description@1.1.0:
|
||||
dependencies:
|
||||
@@ -8349,6 +8369,10 @@ snapshots:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
hasown@2.0.4:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
he@1.2.0: {}
|
||||
|
||||
heimdalljs-logger@0.1.10:
|
||||
@@ -8426,6 +8450,13 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
https-proxy-agent@5.0.1:
|
||||
dependencies:
|
||||
agent-base: 6.0.2
|
||||
debug: 4.4.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
https-proxy-agent@7.0.6:
|
||||
dependencies:
|
||||
agent-base: 7.1.4
|
||||
@@ -8530,7 +8561,7 @@ snapshots:
|
||||
internal-slot@1.1.0:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
hasown: 2.0.3
|
||||
hasown: 2.0.4
|
||||
side-channel: 1.1.0
|
||||
|
||||
ip-address@10.2.0: {}
|
||||
@@ -8629,7 +8660,7 @@ snapshots:
|
||||
call-bound: 1.0.4
|
||||
gopd: 1.2.0
|
||||
has-tostringtag: 1.0.2
|
||||
hasown: 2.0.3
|
||||
hasown: 2.0.4
|
||||
|
||||
is-regexp@1.0.0: {}
|
||||
|
||||
@@ -9120,7 +9151,7 @@ snapshots:
|
||||
call-bind: 1.0.9
|
||||
call-bound: 1.0.4
|
||||
define-properties: 1.2.1
|
||||
es-object-atoms: 1.1.1
|
||||
es-object-atoms: 1.1.2
|
||||
has-symbols: 1.1.0
|
||||
object-keys: 1.1.1
|
||||
|
||||
@@ -10165,7 +10196,7 @@ snapshots:
|
||||
dependencies:
|
||||
dunder-proto: 1.0.1
|
||||
es-errors: 1.3.0
|
||||
es-object-atoms: 1.1.1
|
||||
es-object-atoms: 1.1.2
|
||||
|
||||
shebang-command@2.0.0:
|
||||
dependencies:
|
||||
@@ -10370,7 +10401,7 @@ snapshots:
|
||||
define-data-property: 1.1.4
|
||||
define-properties: 1.2.1
|
||||
es-abstract: 1.24.2
|
||||
es-object-atoms: 1.1.1
|
||||
es-object-atoms: 1.1.2
|
||||
has-property-descriptors: 1.0.2
|
||||
|
||||
string.prototype.trimend@1.0.9:
|
||||
@@ -10378,13 +10409,13 @@ snapshots:
|
||||
call-bind: 1.0.9
|
||||
call-bound: 1.0.4
|
||||
define-properties: 1.2.1
|
||||
es-object-atoms: 1.1.1
|
||||
es-object-atoms: 1.1.2
|
||||
|
||||
string.prototype.trimstart@1.0.8:
|
||||
dependencies:
|
||||
call-bind: 1.0.9
|
||||
define-properties: 1.2.1
|
||||
es-object-atoms: 1.1.1
|
||||
es-object-atoms: 1.1.2
|
||||
|
||||
string_decoder@1.1.1:
|
||||
dependencies:
|
||||
|
||||
@@ -3,6 +3,22 @@ autoInstallPeers: true
|
||||
publicHoistPattern:
|
||||
- "*"
|
||||
|
||||
# Ensure platform-specific optional dependencies (e.g. Rollup's native
|
||||
# binaries pulled in via workbox) are fetched for every architecture we
|
||||
# build/package for, not just the host that runs `pnpm install`. Without
|
||||
# this, cross-arch RPM/OBS builds fail with "Cannot find module
|
||||
# @rollup/rollup-linux-arm64-gnu" on aarch64.
|
||||
supportedArchitectures:
|
||||
os:
|
||||
- current
|
||||
- linux
|
||||
cpu:
|
||||
- x64
|
||||
- arm64
|
||||
libc:
|
||||
- glibc
|
||||
- musl
|
||||
|
||||
overrides:
|
||||
fast-uri: ">=3.1.2"
|
||||
postcss: ">=8.5.10"
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package defaults
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/config/defaults"
|
||||
"github.com/opencloud-eu/opencloud/pkg/shared"
|
||||
"github.com/opencloud-eu/opencloud/pkg/structs"
|
||||
"github.com/opencloud-eu/opencloud/services/users/pkg/config"
|
||||
@@ -38,8 +35,7 @@ func DefaultConfig() *config.Config {
|
||||
Driver: "ldap",
|
||||
Drivers: config.Drivers{
|
||||
LDAP: config.LDAPDriver{
|
||||
URI: "ldaps://localhost:9235",
|
||||
CACert: filepath.Join(defaults.BaseDataPath(), "idm", "ldap.crt"),
|
||||
URI: "ldap://localhost:9236",
|
||||
Insecure: false,
|
||||
UserBaseDN: "ou=users,o=libregraph-idm",
|
||||
GroupBaseDN: "ou=groups,o=libregraph-idm",
|
||||
|
||||
@@ -30,3 +30,4 @@ _testmain.go
|
||||
cover.html
|
||||
README.html
|
||||
.idea
|
||||
.claude/
|
||||
+5
@@ -53,3 +53,8 @@ linters:
|
||||
- varnamelen
|
||||
- wrapcheck
|
||||
- wsl
|
||||
- gomodguard
|
||||
settings:
|
||||
govet:
|
||||
disable:
|
||||
- inline
|
||||
+7
-2
@@ -51,7 +51,8 @@ Validator returns only InvalidValidationError for bad validation input, nil or V
|
||||
|
||||
```go
|
||||
err := validate.Struct(mystruct)
|
||||
validationErrors := err.(validator.ValidationErrors)
|
||||
var validationErrors validator.ValidationErrors
|
||||
errors.As(err, &validationErrors)
|
||||
```
|
||||
|
||||
Usage and documentation
|
||||
@@ -128,6 +129,7 @@ validate := validator.New(validator.WithRequiredStructEnabled())
|
||||
| url | URL String |
|
||||
| http_url | HTTP(s) URL String |
|
||||
| https_url | HTTPS-only URL String |
|
||||
| origin | Web origin (URL with HTTP(S) scheme and host, but no path/query/fragment) |
|
||||
| url_encoded | URL Encoded |
|
||||
| urn_rfc2141 | Urn RFC 2141 String |
|
||||
|
||||
@@ -169,6 +171,7 @@ validate := validator.New(validator.WithRequiredStructEnabled())
|
||||
| bic_iso_9362_2014 | Business Identifier Code (ISO 9362:2014) |
|
||||
| bic | Business Identifier Code (ISO 9362:2022) |
|
||||
| bcp47_language_tag | Language tag (BCP 47) |
|
||||
| bcp47_strict_language_tag | Language tag (BCP 47), strictly following RFC 5646 |
|
||||
| btc_addr | Bitcoin Address |
|
||||
| btc_addr_bech32 | Bitcoin Bech32 Address (segwit) |
|
||||
| credit_card | Credit Card Number |
|
||||
@@ -222,7 +225,7 @@ validate := validator.New(validator.WithRequiredStructEnabled())
|
||||
| sha384 | SHA384 hash |
|
||||
| sha512 | SHA512 hash |
|
||||
| ripemd128 | RIPEMD-128 hash |
|
||||
| ripemd128 | RIPEMD-160 hash |
|
||||
| ripemd160 | RIPEMD-160 hash |
|
||||
| tiger128 | TIGER128 hash |
|
||||
| tiger160 | TIGER160 hash |
|
||||
| tiger192 | TIGER192 hash |
|
||||
@@ -250,11 +253,13 @@ validate := validator.New(validator.WithRequiredStructEnabled())
|
||||
| file | Existing File |
|
||||
| filepath | File Path |
|
||||
| image | Image |
|
||||
| mimetype | MIME Type |
|
||||
| isdefault | Is Default |
|
||||
| len | Length |
|
||||
| max | Maximum |
|
||||
| min | Minimum |
|
||||
| oneof | One Of |
|
||||
| noneof | None Of |
|
||||
| required | Required |
|
||||
| required_if | Required If |
|
||||
| required_unless | Required Unless |
|
||||
|
||||
+314
-94
@@ -3,7 +3,6 @@ package validator
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"cmp"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
@@ -16,7 +15,9 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -141,6 +142,7 @@ var (
|
||||
"http_url": isHttpURL,
|
||||
"https_url": isHttpsURL,
|
||||
"uri": isURI,
|
||||
"origin": isOrigin,
|
||||
"urn_rfc2141": isUrnRFC2141, // RFC 2141
|
||||
"file": isFile,
|
||||
"filepath": isFilePath,
|
||||
@@ -159,6 +161,7 @@ var (
|
||||
"startsnotwith": startsNotWith,
|
||||
"endsnotwith": endsNotWith,
|
||||
"image": isImage,
|
||||
"mimetype": isMIMEType,
|
||||
"isbn": isISBN,
|
||||
"isbn10": isISBN10,
|
||||
"isbn13": isISBN13,
|
||||
@@ -217,6 +220,8 @@ var (
|
||||
"unique": isUnique,
|
||||
"oneof": isOneOf,
|
||||
"oneofci": isOneOfCI,
|
||||
"noneof": isNoneOf,
|
||||
"noneofci": isNoneOfCI,
|
||||
"html": isHTML,
|
||||
"html_encoded": isHTMLEncoded,
|
||||
"url_encoded": isURLEncoded,
|
||||
@@ -240,6 +245,7 @@ var (
|
||||
"iso4217": isIso4217,
|
||||
"iso4217_numeric": isIso4217Numeric,
|
||||
"bcp47_language_tag": isBCP47LanguageTag,
|
||||
"bcp47_strict_language_tag": isBCP47StrictLanguageTag,
|
||||
"postcode_iso3166_alpha2": isPostcodeByIso3166Alpha2,
|
||||
"postcode_iso3166_alpha2_field": isPostcodeByIso3166Alpha2Field,
|
||||
"bic_iso_9362_2014": isIsoBic2014Format,
|
||||
@@ -261,6 +267,33 @@ var (
|
||||
var (
|
||||
oneofValsCache = map[string][]string{}
|
||||
oneofValsCacheRWLock = sync.RWMutex{}
|
||||
|
||||
// BCP47 language tag
|
||||
// according to https://www.rfc-editor.org/rfc/bcp/bcp47.txt
|
||||
bcp47LanguageTagRe = regexp.MustCompile(strings.Join([]string{
|
||||
// group 1:
|
||||
`^(`,
|
||||
// irregular
|
||||
`en-gb-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|`,
|
||||
`sgn-be-fr|sgn-be-nl|sgn-ch-de|`,
|
||||
// regular
|
||||
`art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang|`,
|
||||
// privateuse
|
||||
`x-[a-z0-9]{1,8}`,
|
||||
`)$`,
|
||||
|
||||
`|`,
|
||||
|
||||
// langtag
|
||||
`^`,
|
||||
`((?:[a-z]{2,3}(?:-[a-z]{3}){0,3})|[a-z]{4}|[a-z]{5,8})`, // group 2: language
|
||||
`(?:-([a-z]{4}))?`, // group 3: script
|
||||
`(?:-([a-z]{2}|[0-9]{3}))?`, // group 4: region
|
||||
`(?:-((?:[a-z0-9]{5,8}|[0-9][a-z0-9]{3})(?:-(?:[a-z0-9]{5,8}|[0-9][a-z0-9]{3}))*))?`, // group 5: variant
|
||||
`(?:-((?:[a-wyz0-9](?:-[a-z0-9]{2,8})+)(?:-(?:[a-wyz0-9](?:-[a-z0-9]{2,8})+))*))?`, // group 6: extension
|
||||
`(?:-x(?:-[a-z0-9]{1,8})+)?`,
|
||||
`$`,
|
||||
}, ""))
|
||||
)
|
||||
|
||||
func parseOneOfParam2(s string) []string {
|
||||
@@ -307,12 +340,8 @@ func isOneOf(fl FieldLevel) bool {
|
||||
default:
|
||||
panic(fmt.Sprintf("Bad field type %s", field.Type()))
|
||||
}
|
||||
for i := 0; i < len(vals); i++ {
|
||||
if vals[i] == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
return slices.Contains(vals, v)
|
||||
}
|
||||
|
||||
// isOneOfCI is the validation function for validating if the current field's value is one of the provided string values (case insensitive).
|
||||
@@ -323,13 +352,20 @@ func isOneOfCI(fl FieldLevel) bool {
|
||||
if field.Kind() != reflect.String {
|
||||
panic(fmt.Sprintf("Bad field type %s", field.Type()))
|
||||
}
|
||||
v := field.String()
|
||||
for _, val := range vals {
|
||||
if strings.EqualFold(val, v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
return slices.ContainsFunc(vals, func(val string) bool {
|
||||
return strings.EqualFold(val, field.String())
|
||||
})
|
||||
}
|
||||
|
||||
// isNoneOf validates that the current field's value is not one of the provided string or integer values
|
||||
func isNoneOf(fl FieldLevel) bool {
|
||||
return !isOneOf(fl)
|
||||
}
|
||||
|
||||
// isNoneOfCI validates that the current field's value is not one of the provided string values (case insensitive)
|
||||
func isNoneOfCI(fl FieldLevel) bool {
|
||||
return !isOneOfCI(fl)
|
||||
}
|
||||
|
||||
// isUnique is the validation function for validating if each array|slice|map value is unique
|
||||
@@ -1526,6 +1562,64 @@ func isURI(fl FieldLevel) bool {
|
||||
panic(fmt.Sprintf("Bad field type %s", field.Type()))
|
||||
}
|
||||
|
||||
// isOrigin checks if a field value is a valid web origin URL with HTTP(S) scheme and host defined, but no path, query, or fragment.
|
||||
func isOrigin(fl FieldLevel) bool {
|
||||
field := fl.Field()
|
||||
|
||||
if field.Kind() == reflect.String {
|
||||
s := field.String()
|
||||
|
||||
if len(s) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Fragments with empty content ("#") are not detectable after parse and
|
||||
// u.Fragment will be empty even if # is present in the URL.
|
||||
if strings.Contains(s, "#") {
|
||||
return false
|
||||
}
|
||||
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if u.Scheme != "http" && u.Scheme != "https" {
|
||||
return false
|
||||
}
|
||||
|
||||
if u.Path != "" || u.RawQuery != "" || u.ForceQuery || u.Fragment != "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if u.User != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
hostname := u.Hostname()
|
||||
if hostname == "" {
|
||||
return false
|
||||
}
|
||||
if net.ParseIP(hostname) == nil && !hostnameRegexRFC1123().MatchString(hostname) {
|
||||
return false
|
||||
}
|
||||
|
||||
portStr := u.Port()
|
||||
if portStr != "" {
|
||||
// Port 0 is reserved (RFC 6335) and has no valid use as an origin port.
|
||||
// https://www.rfc-editor.org/rfc/rfc6335.html#section-6
|
||||
port, portErr := strconv.ParseUint(portStr, 10, 16)
|
||||
if portErr != nil || port == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("Bad field type %s", field.Type()))
|
||||
}
|
||||
|
||||
// isURL is the validation function for validating if the current field's value is a valid URL.
|
||||
func isURL(fl FieldLevel) bool {
|
||||
field := fl.Field()
|
||||
@@ -1635,6 +1729,63 @@ func isFile(fl FieldLevel) bool {
|
||||
panic(fmt.Sprintf("Bad field type %s", field.Type()))
|
||||
}
|
||||
|
||||
func detectFileMIMEType(field reflect.Value) (string, bool) {
|
||||
switch field.Kind() {
|
||||
case reflect.String:
|
||||
filePath := field.String()
|
||||
fileInfo, err := os.Stat(filePath)
|
||||
if err != nil || fileInfo.IsDir() {
|
||||
return "", false
|
||||
}
|
||||
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
|
||||
mime, err := mimetype.DetectReader(file)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
return mime.String(), true
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("Bad field type %s", field.Type()))
|
||||
}
|
||||
|
||||
func matchesMIMEType(mime, expected string) bool {
|
||||
expectedType, expectedSubtype, ok := strings.Cut(strings.TrimSpace(expected), "/")
|
||||
if !ok || expectedType == "" || expectedSubtype == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
mimeType, mimeSubtype, ok := strings.Cut(mime, "/")
|
||||
if !ok || mimeType == "" || mimeSubtype == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if expectedSubtype == "*" {
|
||||
return mimeType == expectedType
|
||||
}
|
||||
|
||||
return mimeType == expectedType && mimeSubtype == expectedSubtype
|
||||
}
|
||||
|
||||
// isMIMEType is the validation function for validating if the current field's value contains the path to a file
|
||||
// whose detected MIME type matches the provided validator param in the form type/subtype or type/*.
|
||||
func isMIMEType(fl FieldLevel) bool {
|
||||
mime, ok := detectFileMIMEType(fl.Field())
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return matchesMIMEType(mime, fl.Param())
|
||||
}
|
||||
|
||||
// isImage is the validation function for validating if the current field's value contains the path to a valid image file
|
||||
func isImage(fl FieldLevel) bool {
|
||||
mimetypes := map[string]bool{
|
||||
@@ -1663,39 +1814,14 @@ func isImage(fl FieldLevel) bool {
|
||||
"image/x-xpixmap": true,
|
||||
"image/x-xwindowdump": true,
|
||||
}
|
||||
field := fl.Field()
|
||||
|
||||
switch field.Kind() {
|
||||
case reflect.String:
|
||||
filePath := field.String()
|
||||
fileInfo, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if fileInfo.IsDir() {
|
||||
return false
|
||||
}
|
||||
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
|
||||
mime, err := mimetype.DetectReader(file)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if _, ok := mimetypes[mime.String()]; ok {
|
||||
return true
|
||||
}
|
||||
mime, ok := detectFileMIMEType(fl.Field())
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("Bad field type %s", field.Type()))
|
||||
_, ok = mimetypes[mime]
|
||||
return ok
|
||||
}
|
||||
|
||||
// isFilePath is the validation function for validating if the current field's value is a valid file path.
|
||||
@@ -3081,6 +3207,150 @@ func isBCP47LanguageTag(fl FieldLevel) bool {
|
||||
panic(fmt.Sprintf("Bad field type %s", field.Type()))
|
||||
}
|
||||
|
||||
// isBCP47StrictLanguageTag is the validation function for validating if the current field's value is a valid BCP 47 language tag
|
||||
// according to https://www.rfc-editor.org/rfc/bcp/bcp47.txt
|
||||
func isBCP47StrictLanguageTag(fl FieldLevel) bool {
|
||||
field := fl.Field()
|
||||
|
||||
if field.Kind() != reflect.String {
|
||||
panic(fmt.Sprintf("Bad field type %s", field.Type()))
|
||||
}
|
||||
|
||||
lower := strings.ToLower(field.String())
|
||||
lowerTagDash := lower + "-"
|
||||
|
||||
m := bcp47LanguageTagRe.FindStringSubmatch(lower)
|
||||
if m == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
grandfatheredOrPrivateuse := m[1]
|
||||
lang := m[2]
|
||||
script := m[3]
|
||||
region := m[4]
|
||||
variant := m[5]
|
||||
extension := m[6]
|
||||
|
||||
if grandfatheredOrPrivateuse != "" {
|
||||
return true
|
||||
}
|
||||
|
||||
// language = 2*3ALPHA ; shortest ISO 639 code
|
||||
// ["-" extlang] ; sometimes followed by
|
||||
// ; extended language subtags
|
||||
// / 4ALPHA ; or reserved for future use
|
||||
// / 5*8ALPHA ; or registered language subtag
|
||||
switch n := len(lang); {
|
||||
// 2*3ALPHA "-" extlang
|
||||
case strings.Contains(lang, "-"):
|
||||
parts := strings.Split(lang, "-")
|
||||
|
||||
baseLang := parts[0]
|
||||
base, err := language.ParseBase(baseLang)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// base.String() normalizes the base to the shortest code
|
||||
// for the language
|
||||
if base.String() != baseLang {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, e := range parts[1:] {
|
||||
prefixes, ok := iana_subtag_registry_extlangs[e]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(prefixes) > 0 {
|
||||
found := false
|
||||
for _, p := range prefixes {
|
||||
if strings.HasPrefix(lowerTagDash, p) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
// 2*3ALPHA ; shortest ISO 639 code
|
||||
case n <= 3:
|
||||
base, err := language.ParseBase(lang)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// base.String() normalizes the base to the shortest code
|
||||
// for the language
|
||||
if base.String() != lang {
|
||||
return false
|
||||
}
|
||||
// 4ALPHA ; or reserved for future use
|
||||
case n == 4:
|
||||
return false
|
||||
// 5*8ALPHA ; or registered language subtag
|
||||
default:
|
||||
// registered language subtag with 5+ characters.
|
||||
// As of today there aren't any.
|
||||
// https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
|
||||
return false
|
||||
}
|
||||
|
||||
// script = 4ALPHA ; ISO 15924 code
|
||||
if script != "" {
|
||||
_, err := language.ParseScript(script)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// region = 2ALPHA ; ISO 3166-1 code
|
||||
// 3DIGIT ; UN M.49 code
|
||||
if region != "" {
|
||||
if len(region) == 2 {
|
||||
_, err := language.ParseRegion(region)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
// Can't use language.ParseRegion() here because not all
|
||||
// UN M.49 region codes are allowed, just the subset present
|
||||
// in the IANA subtag registry.
|
||||
_, ok := iana_subtag_registry_m49_codes[region]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// variant = 5*8alphanum ; registered variants
|
||||
// / (DIGIT 3alphanum)
|
||||
if variant != "" {
|
||||
for v := range strings.SplitSeq(variant, "-") {
|
||||
_, err := language.ParseVariant(v)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_, ok := iana_subtag_registry_variants[v]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if extension != "" {
|
||||
_, err := language.ParseExtension(extension)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// isIsoBic2014Format is the validation function for validating if the current field's value is a valid Business Identifier Code (SWIFT code), defined in ISO 9362 2014
|
||||
func isIsoBic2014Format(fl FieldLevel) bool {
|
||||
bicString := fl.Field().String()
|
||||
@@ -3235,59 +3505,9 @@ func isEIN(fl FieldLevel) bool {
|
||||
return einRegex().MatchString(field.String())
|
||||
}
|
||||
|
||||
func isValidateFn(fl FieldLevel) bool {
|
||||
const defaultParam = `Validate`
|
||||
|
||||
field := fl.Field()
|
||||
validateFn := cmp.Or(fl.Param(), defaultParam)
|
||||
|
||||
ok, err := tryCallValidateFn(field, validateFn)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
var (
|
||||
errMethodNotFound = errors.New(`method not found`)
|
||||
errMethodReturnNoValues = errors.New(`method return o values (void)`)
|
||||
errMethodReturnInvalidType = errors.New(`method should return invalid type`)
|
||||
)
|
||||
|
||||
func tryCallValidateFn(field reflect.Value, validateFn string) (bool, error) {
|
||||
method := field.MethodByName(validateFn)
|
||||
if field.CanAddr() && !method.IsValid() {
|
||||
method = field.Addr().MethodByName(validateFn)
|
||||
}
|
||||
|
||||
if !method.IsValid() {
|
||||
return false, fmt.Errorf("unable to call %q on type %q: %w",
|
||||
validateFn, field.Type().String(), errMethodNotFound)
|
||||
}
|
||||
|
||||
returnValues := method.Call([]reflect.Value{})
|
||||
if len(returnValues) == 0 {
|
||||
return false, fmt.Errorf("unable to use result of method %q on type %q: %w",
|
||||
validateFn, field.Type().String(), errMethodReturnNoValues)
|
||||
}
|
||||
|
||||
firstReturnValue := returnValues[0]
|
||||
|
||||
switch firstReturnValue.Kind() {
|
||||
case reflect.Bool:
|
||||
return firstReturnValue.Bool(), nil
|
||||
case reflect.Interface:
|
||||
errorType := reflect.TypeOf((*error)(nil)).Elem()
|
||||
|
||||
if firstReturnValue.Type().Implements(errorType) {
|
||||
return firstReturnValue.IsNil(), nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("unable to use result of method %q on type %q: %w (got interface %v expect error)",
|
||||
validateFn, field.Type().String(), errMethodReturnInvalidType, firstReturnValue.Type().String())
|
||||
default:
|
||||
return false, fmt.Errorf("unable to use result of method %q on type %q: %w (got %v expect error or bool)",
|
||||
validateFn, field.Type().String(), errMethodReturnInvalidType, firstReturnValue.Type().String())
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -144,7 +144,7 @@ func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStr
|
||||
|
||||
if v.hasTagNameFunc {
|
||||
name := v.tagNameFunc(fld)
|
||||
if len(name) > 0 {
|
||||
if len(name) > 0 || v.omitBlankFieldNames {
|
||||
customName = name
|
||||
}
|
||||
}
|
||||
|
||||
+12
@@ -1175,3 +1175,15 @@ var iso3166_2 = map[string]struct{}{
|
||||
"ZW-BU": {}, "ZW-HA": {}, "ZW-MA": {}, "ZW-MC": {}, "ZW-ME": {},
|
||||
"ZW-MI": {}, "ZW-MN": {}, "ZW-MS": {}, "ZW-MV": {}, "ZW-MW": {},
|
||||
}
|
||||
|
||||
// Subset of UN M.49 region codes present in the IANA Language Subtag Registry:
|
||||
// https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
|
||||
var iana_subtag_registry_m49_codes = map[string]struct{}{
|
||||
"001": {}, "002": {}, "003": {}, "005": {}, "009": {},
|
||||
"011": {}, "013": {}, "014": {}, "015": {}, "017": {},
|
||||
"018": {}, "019": {}, "021": {}, "029": {}, "030": {},
|
||||
"034": {}, "035": {}, "039": {}, "053": {}, "054": {},
|
||||
"057": {}, "061": {}, "142": {}, "143": {}, "145": {},
|
||||
"150": {}, "151": {}, "154": {}, "155": {}, "202": {},
|
||||
"419": {},
|
||||
}
|
||||
+42
@@ -184,6 +184,12 @@ so the above will become excludesall=0x7C
|
||||
Field `validate:"excludesall=0x7C"` // GOOD! Use the UTF-8 hex representation.
|
||||
}
|
||||
|
||||
# Build tags
|
||||
|
||||
The library provides a build tag for build size optimizations. If you are not using
|
||||
`validateFn` you can add the `validator_novalidatefn` build tag to enabled better dead
|
||||
code elimination. With this build tag, any usage of `validateFn` tags will panic.
|
||||
|
||||
# Baked In Validators and Tags
|
||||
|
||||
Here is a list of the current built in validators:
|
||||
@@ -546,6 +552,24 @@ Works the same as oneof but is case insensitive and therefore only accepts strin
|
||||
Usage: oneofci=red green
|
||||
oneofci='red green' 'blue yellow'
|
||||
|
||||
# None Of
|
||||
|
||||
For strings, ints, and uints, noneof will ensure that the value is not one of
|
||||
the values in the parameter. The parameter should be a list of values separated by whitespace.
|
||||
Values may be strings or numbers. To inversely match strings with spaces in them, include the target string between single quotes.
|
||||
Kind of like an 'enum'.
|
||||
|
||||
Usage: noneof=red green
|
||||
noneof='red green' 'blue yellow'
|
||||
noneof=5 7 9
|
||||
|
||||
|
||||
# None Of Case Insensitive
|
||||
Works the same as noneof but is case insensitive and therefore only accepts strings.
|
||||
|
||||
Usage: noneofci=red green
|
||||
noneofci='red green' 'blue yellow'
|
||||
|
||||
# Greater Than
|
||||
|
||||
For numbers, this will ensure that the value is greater than the
|
||||
@@ -969,6 +993,16 @@ This is done using os.Stat and github.com/gabriel-vasile/mimetype
|
||||
|
||||
Usage: image
|
||||
|
||||
# MIME type path
|
||||
|
||||
This validates that a string value contains a valid file path and that
|
||||
the file exists on the machine and matches the provided MIME type in the
|
||||
form type/subtype or type/*.
|
||||
This is done using os.Stat and github.com/gabriel-vasile/mimetype
|
||||
|
||||
Usage: mimetype=image/png
|
||||
Usage: mimetype=image/*
|
||||
|
||||
# File Path
|
||||
|
||||
This validates that a string value contains a valid file path but does not
|
||||
@@ -1442,6 +1476,14 @@ More information on https://pkg.go.dev/golang.org/x/text/language
|
||||
|
||||
Usage: bcp47_language_tag
|
||||
|
||||
# BCP 47 Strict Language Tag
|
||||
|
||||
This validates that a string value is a valid BCP 47 language tag strictly following RFC 5646 rules,
|
||||
unlike language.Parse which also accepts Unicode extensions.
|
||||
see https://www.rfc-editor.org/rfc/bcp/bcp47.txt
|
||||
|
||||
Usage: bcp47_strict_language_tag
|
||||
|
||||
BIC (SWIFT code - 2022 standard)
|
||||
|
||||
This validates that a string value is a valid Business Identifier Code (SWIFT code), defined in ISO 9362:2022.
|
||||
|
||||
+14
@@ -24,3 +24,17 @@ func WithPrivateFieldValidation() Option {
|
||||
v.privateFieldValidation = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithTagNameFuncBlankOmit makes a blank return from a RegisterTagNameFunc omit
|
||||
// the field from the error namespace instead of silently falling back to the
|
||||
// struct field name.
|
||||
//
|
||||
// This was made opt-in behaviour to maintain backward compatibility with
|
||||
// existing callers that rely on the fallback for error namespaces.
|
||||
//
|
||||
// It is recommended you enable this as it will be the default behaviour in v11+.
|
||||
func WithTagNameFuncBlankOmit() Option {
|
||||
return func(v *Validate) {
|
||||
v.omitBlankFieldNames = true
|
||||
}
|
||||
}
|
||||
+7
-7
@@ -33,7 +33,7 @@ const (
|
||||
uUID3RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$"
|
||||
uUID4RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
|
||||
uUID5RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
|
||||
uUIDRegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
|
||||
uUIDRegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
|
||||
uUID3RFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-3[0-9a-fA-F]{3}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
|
||||
uUID4RFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
|
||||
uUID5RFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-5[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
|
||||
@@ -56,12 +56,12 @@ const (
|
||||
latitudeRegexString = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$"
|
||||
longitudeRegexString = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$"
|
||||
sSNRegexString = `^[0-9]{3}[ -]?(0[1-9]|[1-9][0-9])[ -]?([1-9][0-9]{3}|[0-9][1-9][0-9]{2}|[0-9]{2}[1-9][0-9]|[0-9]{3}[1-9])$`
|
||||
hostnameRegexStringRFC952 = `^[a-zA-Z]([a-zA-Z0-9\-]+[\.]?)*[a-zA-Z0-9]$` // https://tools.ietf.org/html/rfc952
|
||||
hostnameRegexStringRFC1123 = `^([a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62}){1}(\.[a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62})*?$` // accepts hostname starting with a digit https://tools.ietf.org/html/rfc1123
|
||||
hostnameRegexStringRFC952 = `^[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$` // https://tools.ietf.org/html/rfc952
|
||||
hostnameRegexStringRFC1123 = `^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$` // accepts hostname starting with a digit https://tools.ietf.org/html/rfc1123
|
||||
fqdnRegexStringRFC1123 = `^([a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62})(\.[a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62})*?(\.[a-zA-Z]{1}[a-zA-Z0-9-]{0,62})\.?$` // same as hostnameRegexStringRFC1123 but must contain a non numerical TLD (possibly ending with '.')
|
||||
btcAddressRegexString = `^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$` // bitcoin address
|
||||
btcAddressUpperRegexStringBech32 = `^BC1[02-9AC-HJ-NP-Z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32
|
||||
btcAddressLowerRegexStringBech32 = `^bc1[02-9ac-hj-np-z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32
|
||||
btcAddressRegexString = `^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$` // bitcoin address
|
||||
btcAddressUpperRegexStringBech32 = `^BC1[02-9AC-HJ-NP-Z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32
|
||||
btcAddressLowerRegexStringBech32 = `^bc1[02-9ac-hj-np-z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32
|
||||
ethAddressRegexString = `^0x[0-9a-fA-F]{40}$`
|
||||
ethAddressUpperRegexString = `^0x[0-9A-F]{40}$`
|
||||
ethAddressLowerRegexString = `^0x[0-9a-f]{40}$`
|
||||
@@ -77,7 +77,7 @@ const (
|
||||
cveRegexString = `^CVE-(1999|2\d{3})-(0[^0]\d{2}|0\d[^0]\d{1}|0\d{2}[^0]|[1-9]{1}\d{3,})$` // CVE Format Id https://cve.mitre.org/cve/identifiers/syntaxchange.html
|
||||
mongodbIdRegexString = "^[a-f\\d]{24}$"
|
||||
mongodbConnStringRegexString = "^mongodb(\\+srv)?:\\/\\/(([a-zA-Z\\d]+):([a-zA-Z\\d$:\\/?#\\[\\]@]+)@)?(([a-z\\d.-]+)(:[\\d]+)?)((,(([a-z\\d.-]+)(:(\\d+))?))*)?(\\/[a-zA-Z-_]{1,64})?(\\?(([a-zA-Z]+)=([a-zA-Z\\d]+))(&(([a-zA-Z\\d]+)=([a-zA-Z\\d]+))?)*)?$"
|
||||
cronRegexString = `(@(annually|yearly|monthly|weekly|daily|hourly|reboot))|(@every (\d+(ns|us|µs|ms|s|m|h))+)|((((\d+,)+\d+|((\*|\d+)(\/|-)\d+)|\d+|\*) ?){5,7})`
|
||||
cronRegexString = `^((@(annually|yearly|monthly|weekly|daily|hourly|reboot))|(@every (\d+(ns|us|µs|ms|s|m|h))+)|(([A-Za-z0-9*?][A-Za-z0-9*?/,#L-]+|[*?0-9])( +([A-Za-z0-9*?][A-Za-z0-9*?/,#L-]+|[*?0-9])){4,6}))$`
|
||||
spicedbIDRegexString = `^(([a-zA-Z0-9/_|\-=+]{1,})|\*)$`
|
||||
spicedbPermissionRegexString = "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$"
|
||||
spicedbTypeRegexString = "^([a-z][a-z0-9_]{1,61}[a-z0-9]/)?[a-z][a-z0-9_]{1,62}[a-z0-9]$"
|
||||
|
||||
+15
@@ -1466,6 +1466,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
|
||||
return t
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: "timezone",
|
||||
translation: "{0} must be a valid time zone",
|
||||
override: false,
|
||||
},
|
||||
{
|
||||
tag: "postcode_iso3166_alpha2",
|
||||
translation: "{0} does not match postcode format of {1} country",
|
||||
@@ -1504,11 +1509,21 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
|
||||
translation: "{0} must be a valid image",
|
||||
override: false,
|
||||
},
|
||||
{
|
||||
tag: "mimetype",
|
||||
translation: "{0} must be a valid MIME type",
|
||||
override: false,
|
||||
},
|
||||
{
|
||||
tag: "cve",
|
||||
translation: "{0} must be a valid cve identifier",
|
||||
override: false,
|
||||
},
|
||||
{
|
||||
tag: "bcp47_strict_language_tag",
|
||||
translation: "{0} must be a valid BCP 47 language tag",
|
||||
override: false,
|
||||
},
|
||||
{
|
||||
tag: "validateFn",
|
||||
translation: "{0} must be a valid object",
|
||||
|
||||
+20
-6
@@ -116,7 +116,7 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr
|
||||
|
||||
if ct.hasTag {
|
||||
if kind == reflect.Invalid {
|
||||
v.str1 = string(append(ns, cf.altName...))
|
||||
v.str1 = appendAltName(ns, cf.altName)
|
||||
if v.v.hasTagNameFunc {
|
||||
v.str2 = string(append(structNs, cf.name...))
|
||||
} else {
|
||||
@@ -138,7 +138,7 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr
|
||||
return
|
||||
}
|
||||
|
||||
v.str1 = string(append(ns, cf.altName...))
|
||||
v.str1 = appendAltName(ns, cf.altName)
|
||||
if v.v.hasTagNameFunc {
|
||||
v.str2 = string(append(structNs, cf.name...))
|
||||
} else {
|
||||
@@ -192,7 +192,9 @@ OUTER:
|
||||
// VarWithField - this allows for validating against each field within the struct against a specific value
|
||||
// pretty handy in certain situations
|
||||
if len(cf.name) > 0 {
|
||||
ns = append(append(ns, cf.altName...), '.')
|
||||
if len(cf.altName) > 0 {
|
||||
ns = append(append(ns, cf.altName...), '.')
|
||||
}
|
||||
structNs = append(append(structNs, cf.name...), '.')
|
||||
}
|
||||
|
||||
@@ -212,7 +214,9 @@ OUTER:
|
||||
// VarWithField - this allows for validating against each field within the struct against a specific value
|
||||
// pretty handy in certain situations
|
||||
if len(cf.name) > 0 {
|
||||
ns = append(append(ns, cf.altName...), '.')
|
||||
if len(cf.altName) > 0 {
|
||||
ns = append(append(ns, cf.altName...), '.')
|
||||
}
|
||||
structNs = append(append(structNs, cf.name...), '.')
|
||||
}
|
||||
|
||||
@@ -409,7 +413,7 @@ OUTER:
|
||||
|
||||
if ct.isBlockEnd || ct.next == nil {
|
||||
// if we get here, no valid 'or' value and no more tags
|
||||
v.str1 = string(append(ns, cf.altName...))
|
||||
v.str1 = appendAltName(ns, cf.altName)
|
||||
|
||||
if v.v.hasTagNameFunc {
|
||||
v.str2 = string(append(structNs, cf.name...))
|
||||
@@ -468,7 +472,7 @@ OUTER:
|
||||
v.ct = ct
|
||||
|
||||
if !ct.fn(ctx, v) {
|
||||
v.str1 = string(append(ns, cf.altName...))
|
||||
v.str1 = appendAltName(ns, cf.altName)
|
||||
|
||||
if v.v.hasTagNameFunc {
|
||||
v.str2 = string(append(structNs, cf.name...))
|
||||
@@ -499,6 +503,16 @@ OUTER:
|
||||
}
|
||||
}
|
||||
|
||||
func appendAltName(ns []byte, altName string) string {
|
||||
if len(altName) > 0 {
|
||||
return string(append(ns, altName...))
|
||||
}
|
||||
if n := len(ns); n > 0 && ns[n-1] == '.' {
|
||||
return string(ns[:n-1])
|
||||
}
|
||||
return string(ns)
|
||||
}
|
||||
|
||||
func getValue(val reflect.Value) interface{} {
|
||||
if val.CanInterface() {
|
||||
return val.Interface()
|
||||
|
||||
+6
-5
@@ -68,7 +68,7 @@ type FilterFunc func(ns []byte) bool
|
||||
|
||||
// CustomTypeFunc allows for overriding or adding custom field type handler functions
|
||||
// field = field value of the type to return a value to be validated
|
||||
// example Valuer from sql drive see https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29
|
||||
// example Valuer from sql driver see https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29
|
||||
type CustomTypeFunc func(field reflect.Value) interface{}
|
||||
|
||||
// TagNameFunc allows for adding of a custom tag name parser
|
||||
@@ -96,6 +96,7 @@ type Validate struct {
|
||||
hasTagNameFunc bool
|
||||
requiredStructEnabled bool
|
||||
privateFieldValidation bool
|
||||
omitBlankFieldNames bool
|
||||
}
|
||||
|
||||
// New returns a new instance of 'validate' with sane defaults.
|
||||
@@ -428,7 +429,7 @@ func (v *Validate) StructFilteredCtx(ctx context.Context, s interface{}, fn Filt
|
||||
}
|
||||
|
||||
// StructPartial validates the fields passed in only, ignoring all others.
|
||||
// Fields may be provided in a namespaced fashion relative to the struct provided
|
||||
// Fields may be provided in a namespaced fashion relative to the struct provided
|
||||
// eg. NestedStruct.Field or NestedArrayField[0].Struct.Name
|
||||
//
|
||||
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.
|
||||
@@ -439,7 +440,7 @@ func (v *Validate) StructPartial(s interface{}, fields ...string) error {
|
||||
|
||||
// StructPartialCtx validates the fields passed in only, ignoring all others and allows passing of contextual
|
||||
// validation information via context.Context
|
||||
// Fields may be provided in a namespaced fashion relative to the struct provided
|
||||
// Fields may be provided in a namespaced fashion relative to the struct provided
|
||||
// eg. NestedStruct.Field or NestedArrayField[0].Struct.Name
|
||||
//
|
||||
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.
|
||||
@@ -514,7 +515,7 @@ func (v *Validate) StructPartialCtx(ctx context.Context, s interface{}, fields .
|
||||
}
|
||||
|
||||
// StructExcept validates all fields except the ones passed in.
|
||||
// Fields may be provided in a namespaced fashion relative to the struct provided
|
||||
// Fields may be provided in a namespaced fashion relative to the struct provided
|
||||
// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name
|
||||
//
|
||||
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.
|
||||
@@ -525,7 +526,7 @@ func (v *Validate) StructExcept(s interface{}, fields ...string) error {
|
||||
|
||||
// StructExceptCtx validates all fields except the ones passed in and allows passing of contextual
|
||||
// validation information via context.Context
|
||||
// Fields may be provided in a namespaced fashion relative to the struct provided
|
||||
// Fields may be provided in a namespaced fashion relative to the struct provided
|
||||
// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name
|
||||
//
|
||||
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.
|
||||
|
||||
+3
@@ -19,3 +19,6 @@ indent_size = 2
|
||||
|
||||
[.golangci.yaml]
|
||||
indent_size = 2
|
||||
|
||||
[devenv.yaml]
|
||||
indent_size = 2
|
||||
+7
-3
@@ -1,6 +1,10 @@
|
||||
/.devenv/
|
||||
/.direnv/
|
||||
/.pre-commit-config.yaml
|
||||
/bin/
|
||||
/build/
|
||||
/var/
|
||||
|
||||
# Devenv
|
||||
.devenv*
|
||||
devenv.local.nix
|
||||
devenv.local.yaml
|
||||
.direnv
|
||||
.pre-commit-config.yaml
|
||||
-294
@@ -1,294 +0,0 @@
|
||||
{
|
||||
"nodes": {
|
||||
"cachix": {
|
||||
"inputs": {
|
||||
"devenv": [
|
||||
"devenv"
|
||||
],
|
||||
"flake-compat": [
|
||||
"devenv"
|
||||
],
|
||||
"git-hooks": [
|
||||
"devenv"
|
||||
],
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1742042642,
|
||||
"narHash": "sha256-D0gP8srrX0qj+wNYNPdtVJsQuFzIng3q43thnHXQ/es=",
|
||||
"owner": "cachix",
|
||||
"repo": "cachix",
|
||||
"rev": "a624d3eaf4b1d225f918de8543ed739f2f574203",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"ref": "latest",
|
||||
"repo": "cachix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"devenv": {
|
||||
"inputs": {
|
||||
"cachix": "cachix",
|
||||
"flake-compat": "flake-compat",
|
||||
"git-hooks": "git-hooks",
|
||||
"nix": "nix",
|
||||
"nixpkgs": "nixpkgs_3"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1744876578,
|
||||
"narHash": "sha256-8MTBj2REB8t29sIBLpxbR0+AEGJ7f+RkzZPAGsFd40c=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"rev": "7ff7c351bba20d0615be25ecdcbcf79b57b85fe1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1733328505,
|
||||
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": [
|
||||
"devenv",
|
||||
"nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1712014858,
|
||||
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-parts_2": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1743550720,
|
||||
"narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "c621e8422220273271f52058f618c94e405bb0f5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"git-hooks": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"devenv"
|
||||
],
|
||||
"gitignore": "gitignore",
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1742649964,
|
||||
"narHash": "sha256-DwOTp7nvfi8mRfuL1escHDXabVXFGT1VlPD1JHrtrco=",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"rev": "dcf5072734cb576d2b0c59b2ac44f5050b5eac82",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"gitignore": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"git-hooks",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1709087332,
|
||||
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"libgit2": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1697646580,
|
||||
"narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=",
|
||||
"owner": "libgit2",
|
||||
"repo": "libgit2",
|
||||
"rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "libgit2",
|
||||
"repo": "libgit2",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"devenv"
|
||||
],
|
||||
"flake-parts": "flake-parts",
|
||||
"libgit2": "libgit2",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"nixpkgs-23-11": [
|
||||
"devenv"
|
||||
],
|
||||
"nixpkgs-regression": [
|
||||
"devenv"
|
||||
],
|
||||
"pre-commit-hooks": [
|
||||
"devenv"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1741798497,
|
||||
"narHash": "sha256-E3j+3MoY8Y96mG1dUIiLFm2tZmNbRvSiyN7CrSKuAVg=",
|
||||
"owner": "domenkozar",
|
||||
"repo": "nix",
|
||||
"rev": "f3f44b2baaf6c4c6e179de8cbb1cc6db031083cd",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "domenkozar",
|
||||
"ref": "devenv-2.24",
|
||||
"repo": "nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1733212471,
|
||||
"narHash": "sha256-M1+uCoV5igihRfcUKrr1riygbe73/dzNnzPsmaLCmpo=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "55d15ad12a74eb7d4646254e13638ad0c4128776",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1743296961,
|
||||
"narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1717432640,
|
||||
"narHash": "sha256-+f9c4/ZX5MWDOuB1rKoWj+lBNm0z0rs4CK47HBLxy1o=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "88269ab3044128b7c2f4c7d68448b2fb50456870",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "release-24.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1733477122,
|
||||
"narHash": "sha256-qamMCz5mNpQmgBwc8SB5tVMlD5sbwVIToVZtSxMph9s=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv-nixpkgs",
|
||||
"rev": "7bd9e84d0452f6d2e63b6e6da29fe73fac951857",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"ref": "rolling",
|
||||
"repo": "devenv-nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_4": {
|
||||
"locked": {
|
||||
"lastModified": 1744536153,
|
||||
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"devenv": "devenv",
|
||||
"flake-parts": "flake-parts_2",
|
||||
"nixpkgs": "nixpkgs_4"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||
devenv.url = "github:cachix/devenv";
|
||||
};
|
||||
|
||||
outputs =
|
||||
inputs@{ flake-parts, ... }:
|
||||
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||
imports = [
|
||||
inputs.devenv.flakeModule
|
||||
];
|
||||
|
||||
systems = [
|
||||
"x86_64-linux"
|
||||
"x86_64-darwin"
|
||||
"aarch64-darwin"
|
||||
];
|
||||
|
||||
perSystem =
|
||||
{ pkgs, ... }:
|
||||
rec {
|
||||
devenv.shells = {
|
||||
default = {
|
||||
languages = {
|
||||
go.enable = true;
|
||||
};
|
||||
|
||||
pre-commit.hooks = {
|
||||
nixpkgs-fmt.enable = true;
|
||||
};
|
||||
|
||||
packages = with pkgs; [
|
||||
golangci-lint
|
||||
];
|
||||
|
||||
# https://github.com/cachix/devenv/issues/528#issuecomment-1556108767
|
||||
containers = pkgs.lib.mkForce { };
|
||||
};
|
||||
|
||||
ci = devenv.shells.default;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
+284
-48
@@ -173,6 +173,25 @@
|
||||
// Public: "I made it through!"
|
||||
// }
|
||||
//
|
||||
// # Custom Decoding with Unmarshaler
|
||||
//
|
||||
// Types can implement the Unmarshaler interface to control their own decoding. The interface
|
||||
// behaves similarly to how UnmarshalJSON does in the standard library. It can be used as an
|
||||
// alternative or companion to a DecodeHook.
|
||||
//
|
||||
// type TrimmedString string
|
||||
//
|
||||
// func (t *TrimmedString) UnmarshalMapstructure(input any) error {
|
||||
// str, ok := input.(string)
|
||||
// if !ok {
|
||||
// return fmt.Errorf("expected string, got %T", input)
|
||||
// }
|
||||
// *t = TrimmedString(strings.TrimSpace(str))
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// See the Unmarshaler interface documentation for more details.
|
||||
//
|
||||
// # Other Configuration
|
||||
//
|
||||
// mapstructure is highly configurable. See the DecoderConfig struct
|
||||
@@ -218,6 +237,17 @@ type DecodeHookFuncKind func(reflect.Kind, reflect.Kind, any) (any, error)
|
||||
// values.
|
||||
type DecodeHookFuncValue func(from reflect.Value, to reflect.Value) (any, error)
|
||||
|
||||
// Unmarshaler is the interface implemented by types that can unmarshal
|
||||
// themselves. UnmarshalMapstructure receives the input data (potentially
|
||||
// transformed by DecodeHook) and should populate the receiver with the
|
||||
// decoded values.
|
||||
//
|
||||
// The Unmarshaler interface takes precedence over the default decoding
|
||||
// logic for any type (structs, slices, maps, primitives, etc.).
|
||||
type Unmarshaler interface {
|
||||
UnmarshalMapstructure(any) error
|
||||
}
|
||||
|
||||
// DecoderConfig is the configuration that is used to create a new decoder
|
||||
// and allows customization of various aspects of decoding.
|
||||
type DecoderConfig struct {
|
||||
@@ -281,6 +311,13 @@ type DecoderConfig struct {
|
||||
// }
|
||||
Squash bool
|
||||
|
||||
// Deep will map structures in slices instead of copying them
|
||||
//
|
||||
// type Parent struct {
|
||||
// Children []Child `mapstructure:",deep"`
|
||||
// }
|
||||
Deep bool
|
||||
|
||||
// Metadata is the struct that will contain extra metadata about
|
||||
// the decoding. If this is nil, then no metadata will be tracked.
|
||||
Metadata *Metadata
|
||||
@@ -290,9 +327,15 @@ type DecoderConfig struct {
|
||||
Result any
|
||||
|
||||
// The tag name that mapstructure reads for field names. This
|
||||
// defaults to "mapstructure"
|
||||
// defaults to "mapstructure". Multiple tag names can be specified
|
||||
// as a comma-separated list (e.g., "yaml,json"), and the first
|
||||
// matching non-empty tag will be used.
|
||||
TagName string
|
||||
|
||||
// RootName specifies the name to use for the root element in error messages. For example:
|
||||
// '<rootName>' has unset fields: <fieldName>
|
||||
RootName string
|
||||
|
||||
// The option of the value in the tag that indicates a field should
|
||||
// be squashed. This defaults to "squash".
|
||||
SquashTagOption string
|
||||
@@ -304,11 +347,34 @@ type DecoderConfig struct {
|
||||
// MatchName is the function used to match the map key to the struct
|
||||
// field name or tag. Defaults to `strings.EqualFold`. This can be used
|
||||
// to implement case-sensitive tag values, support snake casing, etc.
|
||||
//
|
||||
// MatchName is used as a fallback comparison when the direct key lookup fails.
|
||||
// See also MapFieldName for transforming field names before lookup.
|
||||
MatchName func(mapKey, fieldName string) bool
|
||||
|
||||
// DecodeNil, if set to true, will cause the DecodeHook (if present) to run
|
||||
// even if the input is nil. This can be used to provide default values.
|
||||
DecodeNil bool
|
||||
|
||||
// MapFieldName is the function used to convert the struct field name to the map's key name.
|
||||
//
|
||||
// This is useful for automatically converting between naming conventions without
|
||||
// explicitly tagging each field. For example, to convert Go's PascalCase field names
|
||||
// to snake_case map keys:
|
||||
//
|
||||
// MapFieldName: func(s string) string {
|
||||
// return strcase.ToSnake(s)
|
||||
// }
|
||||
//
|
||||
// When decoding from a map to a struct, the transformed field name is used for
|
||||
// the initial lookup. If not found, MatchName is used as a fallback comparison.
|
||||
// Explicit struct tags always take precedence over MapFieldName.
|
||||
MapFieldName func(string) string
|
||||
|
||||
// DisableUnmarshaler, if set to true, disables the use of the Unmarshaler
|
||||
// interface. Types implementing Unmarshaler will be decoded using the
|
||||
// standard struct decoding logic instead.
|
||||
DisableUnmarshaler bool
|
||||
}
|
||||
|
||||
// A Decoder takes a raw interface value and turns it into structured
|
||||
@@ -445,6 +511,12 @@ func NewDecoder(config *DecoderConfig) (*Decoder, error) {
|
||||
config.MatchName = strings.EqualFold
|
||||
}
|
||||
|
||||
if config.MapFieldName == nil {
|
||||
config.MapFieldName = func(s string) string {
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
result := &Decoder{
|
||||
config: config,
|
||||
}
|
||||
@@ -458,7 +530,7 @@ func NewDecoder(config *DecoderConfig) (*Decoder, error) {
|
||||
// Decode decodes the given raw interface to the target pointer specified
|
||||
// by the configuration.
|
||||
func (d *Decoder) Decode(input any) error {
|
||||
err := d.decode("", input, reflect.ValueOf(d.config.Result).Elem())
|
||||
err := d.decode(d.config.RootName, input, reflect.ValueOf(d.config.Result).Elem())
|
||||
|
||||
// Retain some of the original behavior when multiple errors ocurr
|
||||
var joinedErr interface{ Unwrap() []error }
|
||||
@@ -540,36 +612,50 @@ func (d *Decoder) decode(name string, input any, outVal reflect.Value) error {
|
||||
|
||||
var err error
|
||||
addMetaKey := true
|
||||
switch outputKind {
|
||||
case reflect.Bool:
|
||||
err = d.decodeBool(name, input, outVal)
|
||||
case reflect.Interface:
|
||||
err = d.decodeBasic(name, input, outVal)
|
||||
case reflect.String:
|
||||
err = d.decodeString(name, input, outVal)
|
||||
case reflect.Int:
|
||||
err = d.decodeInt(name, input, outVal)
|
||||
case reflect.Uint:
|
||||
err = d.decodeUint(name, input, outVal)
|
||||
case reflect.Float32:
|
||||
err = d.decodeFloat(name, input, outVal)
|
||||
case reflect.Complex64:
|
||||
err = d.decodeComplex(name, input, outVal)
|
||||
case reflect.Struct:
|
||||
err = d.decodeStruct(name, input, outVal)
|
||||
case reflect.Map:
|
||||
err = d.decodeMap(name, input, outVal)
|
||||
case reflect.Ptr:
|
||||
addMetaKey, err = d.decodePtr(name, input, outVal)
|
||||
case reflect.Slice:
|
||||
err = d.decodeSlice(name, input, outVal)
|
||||
case reflect.Array:
|
||||
err = d.decodeArray(name, input, outVal)
|
||||
case reflect.Func:
|
||||
err = d.decodeFunc(name, input, outVal)
|
||||
default:
|
||||
// If we reached this point then we weren't able to decode it
|
||||
return newDecodeError(name, fmt.Errorf("unsupported type: %s", outputKind))
|
||||
|
||||
// Check if the target implements Unmarshaler and use it if not disabled
|
||||
unmarshaled := false
|
||||
if !d.config.DisableUnmarshaler {
|
||||
if unmarshaler, ok := getUnmarshaler(outVal); ok {
|
||||
if err = unmarshaler.UnmarshalMapstructure(input); err != nil {
|
||||
err = newDecodeError(name, err)
|
||||
}
|
||||
unmarshaled = true
|
||||
}
|
||||
}
|
||||
|
||||
if !unmarshaled {
|
||||
switch outputKind {
|
||||
case reflect.Bool:
|
||||
err = d.decodeBool(name, input, outVal)
|
||||
case reflect.Interface:
|
||||
err = d.decodeBasic(name, input, outVal)
|
||||
case reflect.String:
|
||||
err = d.decodeString(name, input, outVal)
|
||||
case reflect.Int:
|
||||
err = d.decodeInt(name, input, outVal)
|
||||
case reflect.Uint:
|
||||
err = d.decodeUint(name, input, outVal)
|
||||
case reflect.Float32:
|
||||
err = d.decodeFloat(name, input, outVal)
|
||||
case reflect.Complex64:
|
||||
err = d.decodeComplex(name, input, outVal)
|
||||
case reflect.Struct:
|
||||
err = d.decodeStruct(name, input, outVal)
|
||||
case reflect.Map:
|
||||
err = d.decodeMap(name, input, outVal)
|
||||
case reflect.Ptr:
|
||||
addMetaKey, err = d.decodePtr(name, input, outVal)
|
||||
case reflect.Slice:
|
||||
err = d.decodeSlice(name, input, outVal)
|
||||
case reflect.Array:
|
||||
err = d.decodeArray(name, input, outVal)
|
||||
case reflect.Func:
|
||||
err = d.decodeFunc(name, input, outVal)
|
||||
default:
|
||||
// If we reached this point then we weren't able to decode it
|
||||
return newDecodeError(name, fmt.Errorf("unsupported type: %s", outputKind))
|
||||
}
|
||||
}
|
||||
|
||||
// If we reached here, then we successfully decoded SOMETHING, so
|
||||
@@ -668,7 +754,7 @@ func (d *Decoder) decodeString(name string, data any, val reflect.Value) error {
|
||||
case reflect.Uint8:
|
||||
var uints []uint8
|
||||
if dataKind == reflect.Array {
|
||||
uints = make([]uint8, dataVal.Len(), dataVal.Len())
|
||||
uints = make([]uint8, dataVal.Len())
|
||||
for i := range uints {
|
||||
uints[i] = dataVal.Index(i).Interface().(uint8)
|
||||
}
|
||||
@@ -1060,8 +1146,8 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
|
||||
)
|
||||
}
|
||||
|
||||
tagValue := f.Tag.Get(d.config.TagName)
|
||||
keyName := f.Name
|
||||
tagValue, _ := getTagValue(f, d.config.TagName)
|
||||
keyName := d.config.MapFieldName(f.Name)
|
||||
|
||||
if tagValue == "" && d.config.IgnoreUntaggedFields {
|
||||
continue
|
||||
@@ -1070,6 +1156,9 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
|
||||
// If Squash is set in the config, we squash the field down.
|
||||
squash := d.config.Squash && v.Kind() == reflect.Struct && f.Anonymous
|
||||
|
||||
// If Deep is set in the config, set as default value.
|
||||
deep := d.config.Deep
|
||||
|
||||
v = dereferencePtrToStructIfNeeded(v, d.config.TagName)
|
||||
|
||||
// Determine the name of the key in the map
|
||||
@@ -1078,12 +1167,12 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
|
||||
continue
|
||||
}
|
||||
// If "omitempty" is specified in the tag, it ignores empty values.
|
||||
if strings.Index(tagValue[index+1:], "omitempty") != -1 && isEmptyValue(v) {
|
||||
if strings.Contains(tagValue[index+1:], "omitempty") && isEmptyValue(v) {
|
||||
continue
|
||||
}
|
||||
|
||||
// If "omitzero" is specified in the tag, it ignores zero values.
|
||||
if strings.Index(tagValue[index+1:], "omitzero") != -1 && v.IsZero() {
|
||||
if strings.Contains(tagValue[index+1:], "omitzero") && v.IsZero() {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1103,7 +1192,7 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if strings.Index(tagValue[index+1:], "remain") != -1 {
|
||||
if strings.Contains(tagValue[index+1:], "remain") {
|
||||
if v.Kind() != reflect.Map {
|
||||
return newDecodeError(
|
||||
name+"."+f.Name,
|
||||
@@ -1118,6 +1207,9 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
deep = deep || strings.Contains(tagValue[index+1:], "deep")
|
||||
|
||||
if keyNameTagValue := tagValue[:index]; keyNameTagValue != "" {
|
||||
keyName = keyNameTagValue
|
||||
}
|
||||
@@ -1164,6 +1256,41 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
|
||||
valMap.SetMapIndex(reflect.ValueOf(keyName), vMap)
|
||||
}
|
||||
|
||||
case reflect.Slice:
|
||||
if deep {
|
||||
var childType reflect.Type
|
||||
switch v.Type().Elem().Kind() {
|
||||
case reflect.Struct:
|
||||
childType = reflect.TypeOf(map[string]any{})
|
||||
default:
|
||||
childType = v.Type().Elem()
|
||||
}
|
||||
|
||||
sType := reflect.SliceOf(childType)
|
||||
|
||||
addrVal := reflect.New(sType)
|
||||
|
||||
vSlice := reflect.MakeSlice(sType, v.Len(), v.Cap())
|
||||
|
||||
if v.Len() > 0 {
|
||||
reflect.Indirect(addrVal).Set(vSlice)
|
||||
|
||||
err := d.decode(keyName, v.Interface(), reflect.Indirect(addrVal))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
vSlice = reflect.Indirect(addrVal)
|
||||
|
||||
valMap.SetMapIndex(reflect.ValueOf(keyName), vSlice)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
// When deep mapping is not needed, fallthrough to normal copy
|
||||
fallthrough
|
||||
|
||||
default:
|
||||
valMap.SetMapIndex(reflect.ValueOf(keyName), v)
|
||||
}
|
||||
@@ -1471,7 +1598,10 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
|
||||
remain := false
|
||||
|
||||
// We always parse the tags cause we're looking for other tags too
|
||||
tagParts := strings.Split(fieldType.Tag.Get(d.config.TagName), ",")
|
||||
tagParts := getTagParts(fieldType, d.config.TagName)
|
||||
if len(tagParts) == 0 {
|
||||
tagParts = []string{""}
|
||||
}
|
||||
for _, tag := range tagParts[1:] {
|
||||
if tag == d.config.SquashTagOption {
|
||||
squash = true
|
||||
@@ -1492,6 +1622,18 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
|
||||
if !fieldVal.IsNil() {
|
||||
structs = append(structs, fieldVal.Elem().Elem())
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if fieldVal.Type().Elem().Kind() == reflect.Struct {
|
||||
if fieldVal.IsNil() {
|
||||
fieldVal.Set(reflect.New(fieldVal.Type().Elem()))
|
||||
}
|
||||
structs = append(structs, fieldVal.Elem())
|
||||
} else {
|
||||
errs = append(errs, newDecodeError(
|
||||
name+"."+fieldType.Name,
|
||||
fmt.Errorf("unsupported type for squashed pointer: %s", fieldVal.Type().Elem().Kind()),
|
||||
))
|
||||
}
|
||||
default:
|
||||
errs = append(errs, newDecodeError(
|
||||
name+"."+fieldType.Name,
|
||||
@@ -1516,13 +1658,15 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
|
||||
field, fieldValue := f.field, f.val
|
||||
fieldName := field.Name
|
||||
|
||||
tagValue := field.Tag.Get(d.config.TagName)
|
||||
tagValue, _ := getTagValue(field, d.config.TagName)
|
||||
if tagValue == "" && d.config.IgnoreUntaggedFields {
|
||||
continue
|
||||
}
|
||||
tagValue = strings.SplitN(tagValue, ",", 2)[0]
|
||||
if tagValue != "" {
|
||||
fieldName = tagValue
|
||||
} else {
|
||||
fieldName = d.config.MapFieldName(fieldName)
|
||||
}
|
||||
|
||||
rawMapKey := reflect.ValueOf(fieldName)
|
||||
@@ -1605,8 +1749,14 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
// Improve error message when name is empty by showing the target struct type
|
||||
// in the case where it is empty for embedded structs.
|
||||
errorName := name
|
||||
if errorName == "" {
|
||||
errorName = val.Type().String()
|
||||
}
|
||||
errs = append(errs, newDecodeError(
|
||||
name,
|
||||
errorName,
|
||||
fmt.Errorf("has invalid keys: %s", strings.Join(keys, ", ")),
|
||||
))
|
||||
}
|
||||
@@ -1692,7 +1842,7 @@ func isStructTypeConvertibleToMap(typ reflect.Type, checkMapstructureTags bool,
|
||||
if f.PkgPath == "" && !checkMapstructureTags { // check for unexported fields
|
||||
return true
|
||||
}
|
||||
if checkMapstructureTags && f.Tag.Get(tagName) != "" { // check for mapstructure tags inside
|
||||
if checkMapstructureTags && hasAnyTag(f, tagName) { // check for mapstructure tags inside
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1700,13 +1850,99 @@ func isStructTypeConvertibleToMap(typ reflect.Type, checkMapstructureTags bool,
|
||||
}
|
||||
|
||||
func dereferencePtrToStructIfNeeded(v reflect.Value, tagName string) reflect.Value {
|
||||
if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
|
||||
if v.Kind() != reflect.Ptr {
|
||||
return v
|
||||
}
|
||||
deref := v.Elem()
|
||||
derefT := deref.Type()
|
||||
if isStructTypeConvertibleToMap(derefT, true, tagName) {
|
||||
return deref
|
||||
|
||||
switch v.Elem().Kind() {
|
||||
case reflect.Slice:
|
||||
return v.Elem()
|
||||
|
||||
case reflect.Struct:
|
||||
deref := v.Elem()
|
||||
derefT := deref.Type()
|
||||
if isStructTypeConvertibleToMap(derefT, true, tagName) {
|
||||
return deref
|
||||
}
|
||||
return v
|
||||
|
||||
default:
|
||||
return v
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func hasAnyTag(field reflect.StructField, tagName string) bool {
|
||||
_, ok := getTagValue(field, tagName)
|
||||
return ok
|
||||
}
|
||||
|
||||
func getTagParts(field reflect.StructField, tagName string) []string {
|
||||
tagValue, ok := getTagValue(field, tagName)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return strings.Split(tagValue, ",")
|
||||
}
|
||||
|
||||
func getTagValue(field reflect.StructField, tagName string) (string, bool) {
|
||||
for _, name := range splitTagNames(tagName) {
|
||||
if tag := field.Tag.Get(name); tag != "" {
|
||||
return tag, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func splitTagNames(tagName string) []string {
|
||||
if tagName == "" {
|
||||
return []string{"mapstructure"}
|
||||
}
|
||||
parts := strings.Split(tagName, ",")
|
||||
result := make([]string, 0, len(parts))
|
||||
|
||||
for _, name := range parts {
|
||||
name = strings.TrimSpace(name)
|
||||
if name != "" {
|
||||
result = append(result, name)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// unmarshalerType is cached for performance
|
||||
var unmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
|
||||
|
||||
// getUnmarshaler checks if the value implements Unmarshaler and returns
|
||||
// the Unmarshaler and a boolean indicating if it was found. It handles both
|
||||
// pointer and value receivers.
|
||||
func getUnmarshaler(val reflect.Value) (Unmarshaler, bool) {
|
||||
// Skip invalid or nil values
|
||||
if !val.IsValid() {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
switch val.Kind() {
|
||||
case reflect.Pointer, reflect.Interface:
|
||||
if val.IsNil() {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
// Check pointer receiver first (most common case)
|
||||
if val.CanAddr() {
|
||||
ptrVal := val.Addr()
|
||||
// Quick check: if no methods, can't implement any interface
|
||||
if ptrVal.Type().NumMethod() > 0 && ptrVal.Type().Implements(unmarshalerType) {
|
||||
return ptrVal.Interface().(Unmarshaler), true
|
||||
}
|
||||
}
|
||||
|
||||
// Check value receiver
|
||||
// Quick check: if no methods, can't implement any interface
|
||||
if val.Type().NumMethod() > 0 && val.CanInterface() && val.Type().Implements(unmarshalerType) {
|
||||
return val.Interface().(Unmarshaler), true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
-96
@@ -1,96 +0,0 @@
|
||||
## 1.5.0
|
||||
|
||||
* New option `IgnoreUntaggedFields` to ignore decoding to any fields
|
||||
without `mapstructure` (or the configured tag name) set [GH-277]
|
||||
* New option `ErrorUnset` which makes it an error if any fields
|
||||
in a target struct are not set by the decoding process. [GH-225]
|
||||
* New function `OrComposeDecodeHookFunc` to help compose decode hooks. [GH-240]
|
||||
* Decoding to slice from array no longer crashes [GH-265]
|
||||
* Decode nested struct pointers to map [GH-271]
|
||||
* Fix issue where `,squash` was ignored if `Squash` option was set. [GH-280]
|
||||
* Fix issue where fields with `,omitempty` would sometimes decode
|
||||
into a map with an empty string key [GH-281]
|
||||
|
||||
## 1.4.3
|
||||
|
||||
* Fix cases where `json.Number` didn't decode properly [GH-261]
|
||||
|
||||
## 1.4.2
|
||||
|
||||
* Custom name matchers to support any sort of casing, formatting, etc. for
|
||||
field names. [GH-250]
|
||||
* Fix possible panic in ComposeDecodeHookFunc [GH-251]
|
||||
|
||||
## 1.4.1
|
||||
|
||||
* Fix regression where `*time.Time` value would be set to empty and not be sent
|
||||
to decode hooks properly [GH-232]
|
||||
|
||||
## 1.4.0
|
||||
|
||||
* A new decode hook type `DecodeHookFuncValue` has been added that has
|
||||
access to the full values. [GH-183]
|
||||
* Squash is now supported with embedded fields that are struct pointers [GH-205]
|
||||
* Empty strings will convert to 0 for all numeric types when weakly decoding [GH-206]
|
||||
|
||||
## 1.3.3
|
||||
|
||||
* Decoding maps from maps creates a settable value for decode hooks [GH-203]
|
||||
|
||||
## 1.3.2
|
||||
|
||||
* Decode into interface type with a struct value is supported [GH-187]
|
||||
|
||||
## 1.3.1
|
||||
|
||||
* Squash should only squash embedded structs. [GH-194]
|
||||
|
||||
## 1.3.0
|
||||
|
||||
* Added `",omitempty"` support. This will ignore zero values in the source
|
||||
structure when encoding. [GH-145]
|
||||
|
||||
## 1.2.3
|
||||
|
||||
* Fix duplicate entries in Keys list with pointer values. [GH-185]
|
||||
|
||||
## 1.2.2
|
||||
|
||||
* Do not add unsettable (unexported) values to the unused metadata key
|
||||
or "remain" value. [GH-150]
|
||||
|
||||
## 1.2.1
|
||||
|
||||
* Go modules checksum mismatch fix
|
||||
|
||||
## 1.2.0
|
||||
|
||||
* Added support to capture unused values in a field using the `",remain"` value
|
||||
in the mapstructure tag. There is an example to showcase usage.
|
||||
* Added `DecoderConfig` option to always squash embedded structs
|
||||
* `json.Number` can decode into `uint` types
|
||||
* Empty slices are preserved and not replaced with nil slices
|
||||
* Fix panic that can occur in when decoding a map into a nil slice of structs
|
||||
* Improved package documentation for godoc
|
||||
|
||||
## 1.1.2
|
||||
|
||||
* Fix error when decode hook decodes interface implementation into interface
|
||||
type. [GH-140]
|
||||
|
||||
## 1.1.1
|
||||
|
||||
* Fix panic that can happen in `decodePtr`
|
||||
|
||||
## 1.1.0
|
||||
|
||||
* Added `StringToIPHookFunc` to convert `string` to `net.IP` and `net.IPNet` [GH-133]
|
||||
* Support struct to struct decoding [GH-137]
|
||||
* If source map value is nil, then destination map value is nil (instead of empty)
|
||||
* If source slice value is nil, then destination slice value is nil (instead of empty)
|
||||
* If source pointer is nil, then destination pointer is set to nil (instead of
|
||||
allocated zero value of type)
|
||||
|
||||
## 1.0.0
|
||||
|
||||
* Initial tagged stable release.
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Mitchell Hashimoto
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
# mapstructure [](https://godoc.org/github.com/mitchellh/mapstructure)
|
||||
|
||||
mapstructure is a Go library for decoding generic map values to structures
|
||||
and vice versa, while providing helpful error handling.
|
||||
|
||||
This library is most useful when decoding values from some data stream (JSON,
|
||||
Gob, etc.) where you don't _quite_ know the structure of the underlying data
|
||||
until you read a part of it. You can therefore read a `map[string]interface{}`
|
||||
and use this library to decode it into the proper underlying native Go
|
||||
structure.
|
||||
|
||||
## Installation
|
||||
|
||||
Standard `go get`:
|
||||
|
||||
```
|
||||
$ go get github.com/mitchellh/mapstructure
|
||||
```
|
||||
|
||||
## Usage & Example
|
||||
|
||||
For usage and examples see the [Godoc](http://godoc.org/github.com/mitchellh/mapstructure).
|
||||
|
||||
The `Decode` function has examples associated with it there.
|
||||
|
||||
## But Why?!
|
||||
|
||||
Go offers fantastic standard libraries for decoding formats such as JSON.
|
||||
The standard method is to have a struct pre-created, and populate that struct
|
||||
from the bytes of the encoded format. This is great, but the problem is if
|
||||
you have configuration or an encoding that changes slightly depending on
|
||||
specific fields. For example, consider this JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "person",
|
||||
"name": "Mitchell"
|
||||
}
|
||||
```
|
||||
|
||||
Perhaps we can't populate a specific structure without first reading
|
||||
the "type" field from the JSON. We could always do two passes over the
|
||||
decoding of the JSON (reading the "type" first, and the rest later).
|
||||
However, it is much simpler to just decode this into a `map[string]interface{}`
|
||||
structure, read the "type" key, then use something like this library
|
||||
to decode it into the proper structure.
|
||||
-279
@@ -1,279 +0,0 @@
|
||||
package mapstructure
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// typedDecodeHook takes a raw DecodeHookFunc (an interface{}) and turns
|
||||
// it into the proper DecodeHookFunc type, such as DecodeHookFuncType.
|
||||
func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc {
|
||||
// Create variables here so we can reference them with the reflect pkg
|
||||
var f1 DecodeHookFuncType
|
||||
var f2 DecodeHookFuncKind
|
||||
var f3 DecodeHookFuncValue
|
||||
|
||||
// Fill in the variables into this interface and the rest is done
|
||||
// automatically using the reflect package.
|
||||
potential := []interface{}{f1, f2, f3}
|
||||
|
||||
v := reflect.ValueOf(h)
|
||||
vt := v.Type()
|
||||
for _, raw := range potential {
|
||||
pt := reflect.ValueOf(raw).Type()
|
||||
if vt.ConvertibleTo(pt) {
|
||||
return v.Convert(pt).Interface()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecodeHookExec executes the given decode hook. This should be used
|
||||
// since it'll naturally degrade to the older backwards compatible DecodeHookFunc
|
||||
// that took reflect.Kind instead of reflect.Type.
|
||||
func DecodeHookExec(
|
||||
raw DecodeHookFunc,
|
||||
from reflect.Value, to reflect.Value) (interface{}, error) {
|
||||
|
||||
switch f := typedDecodeHook(raw).(type) {
|
||||
case DecodeHookFuncType:
|
||||
return f(from.Type(), to.Type(), from.Interface())
|
||||
case DecodeHookFuncKind:
|
||||
return f(from.Kind(), to.Kind(), from.Interface())
|
||||
case DecodeHookFuncValue:
|
||||
return f(from, to)
|
||||
default:
|
||||
return nil, errors.New("invalid decode hook signature")
|
||||
}
|
||||
}
|
||||
|
||||
// ComposeDecodeHookFunc creates a single DecodeHookFunc that
|
||||
// automatically composes multiple DecodeHookFuncs.
|
||||
//
|
||||
// The composed funcs are called in order, with the result of the
|
||||
// previous transformation.
|
||||
func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc {
|
||||
return func(f reflect.Value, t reflect.Value) (interface{}, error) {
|
||||
var err error
|
||||
data := f.Interface()
|
||||
|
||||
newFrom := f
|
||||
for _, f1 := range fs {
|
||||
data, err = DecodeHookExec(f1, newFrom, t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newFrom = reflect.ValueOf(data)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
// OrComposeDecodeHookFunc executes all input hook functions until one of them returns no error. In that case its value is returned.
|
||||
// If all hooks return an error, OrComposeDecodeHookFunc returns an error concatenating all error messages.
|
||||
func OrComposeDecodeHookFunc(ff ...DecodeHookFunc) DecodeHookFunc {
|
||||
return func(a, b reflect.Value) (interface{}, error) {
|
||||
var allErrs string
|
||||
var out interface{}
|
||||
var err error
|
||||
|
||||
for _, f := range ff {
|
||||
out, err = DecodeHookExec(f, a, b)
|
||||
if err != nil {
|
||||
allErrs += err.Error() + "\n"
|
||||
continue
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
return nil, errors.New(allErrs)
|
||||
}
|
||||
}
|
||||
|
||||
// StringToSliceHookFunc returns a DecodeHookFunc that converts
|
||||
// string to []string by splitting on the given sep.
|
||||
func StringToSliceHookFunc(sep string) DecodeHookFunc {
|
||||
return func(
|
||||
f reflect.Kind,
|
||||
t reflect.Kind,
|
||||
data interface{}) (interface{}, error) {
|
||||
if f != reflect.String || t != reflect.Slice {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
raw := data.(string)
|
||||
if raw == "" {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
return strings.Split(raw, sep), nil
|
||||
}
|
||||
}
|
||||
|
||||
// StringToTimeDurationHookFunc returns a DecodeHookFunc that converts
|
||||
// strings to time.Duration.
|
||||
func StringToTimeDurationHookFunc() DecodeHookFunc {
|
||||
return func(
|
||||
f reflect.Type,
|
||||
t reflect.Type,
|
||||
data interface{}) (interface{}, error) {
|
||||
if f.Kind() != reflect.String {
|
||||
return data, nil
|
||||
}
|
||||
if t != reflect.TypeOf(time.Duration(5)) {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// Convert it by parsing
|
||||
return time.ParseDuration(data.(string))
|
||||
}
|
||||
}
|
||||
|
||||
// StringToIPHookFunc returns a DecodeHookFunc that converts
|
||||
// strings to net.IP
|
||||
func StringToIPHookFunc() DecodeHookFunc {
|
||||
return func(
|
||||
f reflect.Type,
|
||||
t reflect.Type,
|
||||
data interface{}) (interface{}, error) {
|
||||
if f.Kind() != reflect.String {
|
||||
return data, nil
|
||||
}
|
||||
if t != reflect.TypeOf(net.IP{}) {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// Convert it by parsing
|
||||
ip := net.ParseIP(data.(string))
|
||||
if ip == nil {
|
||||
return net.IP{}, fmt.Errorf("failed parsing ip %v", data)
|
||||
}
|
||||
|
||||
return ip, nil
|
||||
}
|
||||
}
|
||||
|
||||
// StringToIPNetHookFunc returns a DecodeHookFunc that converts
|
||||
// strings to net.IPNet
|
||||
func StringToIPNetHookFunc() DecodeHookFunc {
|
||||
return func(
|
||||
f reflect.Type,
|
||||
t reflect.Type,
|
||||
data interface{}) (interface{}, error) {
|
||||
if f.Kind() != reflect.String {
|
||||
return data, nil
|
||||
}
|
||||
if t != reflect.TypeOf(net.IPNet{}) {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// Convert it by parsing
|
||||
_, net, err := net.ParseCIDR(data.(string))
|
||||
return net, err
|
||||
}
|
||||
}
|
||||
|
||||
// StringToTimeHookFunc returns a DecodeHookFunc that converts
|
||||
// strings to time.Time.
|
||||
func StringToTimeHookFunc(layout string) DecodeHookFunc {
|
||||
return func(
|
||||
f reflect.Type,
|
||||
t reflect.Type,
|
||||
data interface{}) (interface{}, error) {
|
||||
if f.Kind() != reflect.String {
|
||||
return data, nil
|
||||
}
|
||||
if t != reflect.TypeOf(time.Time{}) {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// Convert it by parsing
|
||||
return time.Parse(layout, data.(string))
|
||||
}
|
||||
}
|
||||
|
||||
// WeaklyTypedHook is a DecodeHookFunc which adds support for weak typing to
|
||||
// the decoder.
|
||||
//
|
||||
// Note that this is significantly different from the WeaklyTypedInput option
|
||||
// of the DecoderConfig.
|
||||
func WeaklyTypedHook(
|
||||
f reflect.Kind,
|
||||
t reflect.Kind,
|
||||
data interface{}) (interface{}, error) {
|
||||
dataVal := reflect.ValueOf(data)
|
||||
switch t {
|
||||
case reflect.String:
|
||||
switch f {
|
||||
case reflect.Bool:
|
||||
if dataVal.Bool() {
|
||||
return "1", nil
|
||||
}
|
||||
return "0", nil
|
||||
case reflect.Float32:
|
||||
return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil
|
||||
case reflect.Int:
|
||||
return strconv.FormatInt(dataVal.Int(), 10), nil
|
||||
case reflect.Slice:
|
||||
dataType := dataVal.Type()
|
||||
elemKind := dataType.Elem().Kind()
|
||||
if elemKind == reflect.Uint8 {
|
||||
return string(dataVal.Interface().([]uint8)), nil
|
||||
}
|
||||
case reflect.Uint:
|
||||
return strconv.FormatUint(dataVal.Uint(), 10), nil
|
||||
}
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func RecursiveStructToMapHookFunc() DecodeHookFunc {
|
||||
return func(f reflect.Value, t reflect.Value) (interface{}, error) {
|
||||
if f.Kind() != reflect.Struct {
|
||||
return f.Interface(), nil
|
||||
}
|
||||
|
||||
var i interface{} = struct{}{}
|
||||
if t.Type() != reflect.TypeOf(&i).Elem() {
|
||||
return f.Interface(), nil
|
||||
}
|
||||
|
||||
m := make(map[string]interface{})
|
||||
t.Set(reflect.ValueOf(m))
|
||||
|
||||
return f.Interface(), nil
|
||||
}
|
||||
}
|
||||
|
||||
// TextUnmarshallerHookFunc returns a DecodeHookFunc that applies
|
||||
// strings to the UnmarshalText function, when the target type
|
||||
// implements the encoding.TextUnmarshaler interface
|
||||
func TextUnmarshallerHookFunc() DecodeHookFuncType {
|
||||
return func(
|
||||
f reflect.Type,
|
||||
t reflect.Type,
|
||||
data interface{}) (interface{}, error) {
|
||||
if f.Kind() != reflect.String {
|
||||
return data, nil
|
||||
}
|
||||
result := reflect.New(t).Interface()
|
||||
unmarshaller, ok := result.(encoding.TextUnmarshaler)
|
||||
if !ok {
|
||||
return data, nil
|
||||
}
|
||||
if err := unmarshaller.UnmarshalText([]byte(data.(string))); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
package mapstructure
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Error implements the error interface and can represents multiple
|
||||
// errors that occur in the course of a single decode.
|
||||
type Error struct {
|
||||
Errors []string
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
points := make([]string, len(e.Errors))
|
||||
for i, err := range e.Errors {
|
||||
points[i] = fmt.Sprintf("* %s", err)
|
||||
}
|
||||
|
||||
sort.Strings(points)
|
||||
return fmt.Sprintf(
|
||||
"%d error(s) decoding:\n\n%s",
|
||||
len(e.Errors), strings.Join(points, "\n"))
|
||||
}
|
||||
|
||||
// WrappedErrors implements the errwrap.Wrapper interface to make this
|
||||
// return value more useful with the errwrap and go-multierror libraries.
|
||||
func (e *Error) WrappedErrors() []error {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := make([]error, len(e.Errors))
|
||||
for i, e := range e.Errors {
|
||||
result[i] = errors.New(e)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func appendErrors(errors []string, err error) []string {
|
||||
switch e := err.(type) {
|
||||
case *Error:
|
||||
return append(errors, e.Errors...)
|
||||
default:
|
||||
return append(errors, e.Error())
|
||||
}
|
||||
}
|
||||
-1540
File diff suppressed because it is too large.
Load diff
+1
@@ -17,3 +17,4 @@
|
||||
- Thomas Boerger <thomas@webhippie.de>
|
||||
- Miroslav Bauer <bauer@cesnet.cz>
|
||||
- Daniel Mueller <daniel.mueller@uni-muenster.de>
|
||||
- Michael Stingl <mail@michaelstingl.com>
|
||||
+1
-1
@@ -27,7 +27,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/cmd/revad/internal/grace"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/logger"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/registry"
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ import (
|
||||
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
|
||||
gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth/scope"
|
||||
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -28,7 +28,7 @@ import (
|
||||
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
|
||||
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -22,7 +22,7 @@ import (
|
||||
"context"
|
||||
|
||||
appauthpb "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appauth"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appauth/manager/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -28,8 +28,8 @@ import (
|
||||
registrypb "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/juliangruber/go-intersect"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/app"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/app/provider/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -24,7 +24,7 @@ import (
|
||||
"google.golang.org/grpc"
|
||||
|
||||
registrypb "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/app"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/app/registry/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -24,7 +24,7 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth/manager/registry"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -22,7 +22,7 @@ import (
|
||||
"context"
|
||||
|
||||
registrypb "github.com/cs3org/go-cs3apis/cs3/auth/registry/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth/registry/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ import (
|
||||
ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
|
||||
datatx "github.com/cs3org/go-cs3apis/cs3/tx/v1beta1"
|
||||
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
txdriver "github.com/opencloud-eu/reva/v2/pkg/datatx"
|
||||
txregistry "github.com/opencloud-eu/reva/v2/pkg/datatx/manager/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ import (
|
||||
"strings"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/sharedconf"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -24,7 +24,7 @@ import (
|
||||
"sort"
|
||||
|
||||
grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/group"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/group/manager/registry"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -22,7 +22,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/internal/grpc/services/helloworld/proto"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -22,7 +22,7 @@ import (
|
||||
"context"
|
||||
|
||||
ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/ocm/provider"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/ocm/provider/authorizer/registry"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -24,7 +24,7 @@ import (
|
||||
|
||||
permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/permission"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/permission/manager/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -24,7 +24,7 @@ import (
|
||||
"google.golang.org/grpc"
|
||||
|
||||
preferencespb "github.com/cs3org/go-cs3apis/cs3/preferences/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/preferences"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/preferences/registry"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -30,7 +30,7 @@ import (
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/password"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/permission"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -33,7 +33,7 @@ import (
|
||||
ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -39,7 +39,7 @@ import (
|
||||
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -33,7 +33,7 @@ import (
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/conversions"
|
||||
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -28,7 +28,7 @@ import (
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
registrypb "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1"
|
||||
typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -24,7 +24,7 @@ import (
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
vendor/github.com/opencloud-eu/reva/v2/internal/grpc/services/usershareprovider/usershareprovider.go
Generated
Vendored
+1
-1
@@ -33,7 +33,7 @@ import (
|
||||
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ import (
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/internal/http/interceptors/auth/credential/registry"
|
||||
tokenregistry "github.com/opencloud-eu/reva/v2/internal/http/interceptors/auth/token/registry"
|
||||
tokenwriterregistry "github.com/opencloud-eu/reva/v2/internal/http/interceptors/auth/tokenwriter/registry"
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@
|
||||
package cors
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rhttp/global"
|
||||
"github.com/rs/cors"
|
||||
)
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -25,7 +25,7 @@ import (
|
||||
"strings"
|
||||
|
||||
ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/ocm/provider"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -33,8 +33,8 @@ import (
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
iso6391 "github.com/emvi/iso-639-1"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
ua "github.com/mileusna/useragent"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -32,7 +32,7 @@ import (
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
|
||||
"github.com/gdexlab/go-render/render"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/internal/http/services/archiver/manager"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -27,8 +27,8 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rhttp"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -22,7 +22,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -21,7 +21,7 @@ package helloworld
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rhttp/global"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ package mentix
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/mentix/meshdata"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/logger"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
|
||||
Generated
Vendored
+4
@@ -159,6 +159,8 @@ var (
|
||||
ErrLocked = errors.New("webdav: locked")
|
||||
// ErrNoSuchLock is returned by a LockSystem's Refresh and Unlock methods.
|
||||
ErrNoSuchLock = errors.New("webdav: no such lock")
|
||||
// ErrNotFound is returned when a resource does not exist.
|
||||
ErrNotFound = errors.New("webdav: not found")
|
||||
// ErrNotImplemented is returned when hitting not implemented code paths
|
||||
ErrNotImplemented = errors.New("webdav: not implemented")
|
||||
// ErrTokenNotFound is returned when a token is not found
|
||||
@@ -206,6 +208,8 @@ func NewErrFromStatus(s *rpc.Status) error {
|
||||
return ErrForbidden
|
||||
case rpc.Code_CODE_LOCKED, rpc.Code_CODE_FAILED_PRECONDITION:
|
||||
return ErrLocked
|
||||
case rpc.Code_CODE_NOT_FOUND:
|
||||
return ErrNotFound
|
||||
case rpc.Code_CODE_UNIMPLEMENTED:
|
||||
return ErrNotImplemented
|
||||
default:
|
||||
|
||||
Generated
Vendored
+72
-23
@@ -546,20 +546,26 @@ func (s *svc) lockReference(ctx context.Context, w http.ResponseWriter, r *http.
|
||||
ld.OwnerXML = li.Owner.InnerXML // TODO optional, should be a URL
|
||||
ld.ZeroDepth = depth == 0
|
||||
|
||||
//TODO: @jfd the code tries to create a lock for a file that may not even exist,
|
||||
// should we do that in the decomposedfs as well? the node does not exist
|
||||
// this actually is a name based lock ... ugh
|
||||
token, err = s.LockSystem.Create(ctx, now, ld)
|
||||
|
||||
//
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, ocdavErrors.ErrLocked):
|
||||
return http.StatusLocked, err
|
||||
case errors.Is(err, ocdavErrors.ErrForbidden):
|
||||
return http.StatusForbidden, err
|
||||
case errors.Is(err, ocdavErrors.ErrNotFound):
|
||||
// RFC 4918 section 7.3: a LOCK request on an unmapped URL
|
||||
// creates an empty locked resource. Create the resource, then
|
||||
// retry the lock against the now-existing node.
|
||||
resourceCreated, status, cerr := s.createEmptyResource(ctx, ref)
|
||||
if cerr != nil {
|
||||
return status, cerr
|
||||
}
|
||||
// Report 201 only when the resource was actually created; a
|
||||
// concurrent create leaves an existing resource to lock (200).
|
||||
created = resourceCreated
|
||||
if token, err = s.LockSystem.Create(ctx, now, ld); err != nil {
|
||||
sublog.Error().Err(err).Msg("could not lock resource after creating it")
|
||||
return lockCreateErrToStatus(err), err
|
||||
}
|
||||
default:
|
||||
return http.StatusInternalServerError, err
|
||||
return lockCreateErrToStatus(err), err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -571,19 +577,9 @@ func (s *svc) lockReference(ctx context.Context, w http.ResponseWriter, r *http.
|
||||
}
|
||||
}()
|
||||
|
||||
// Create the resource if it didn't previously exist.
|
||||
// TODO use sdk to stat?
|
||||
/*
|
||||
if _, err := s.FileSystem.Stat(ctx, reqPath); err != nil {
|
||||
f, err := s.FileSystem.OpenFile(ctx, reqPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
// TODO: detect missing intermediate dirs and return http.StatusConflict?
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
f.Close()
|
||||
created = true
|
||||
}
|
||||
*/
|
||||
// The resource is created (if it did not previously exist) in the
|
||||
// ErrNotFound branch above, mirroring sabre/dav per RFC 4918 section 7.3.
|
||||
|
||||
// http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
|
||||
// Lock-Token value is a Coded-URL. We add angle brackets.
|
||||
w.Header().Set("Lock-Token", "<"+token+">")
|
||||
@@ -603,6 +599,59 @@ func (s *svc) lockReference(ctx context.Context, w http.ResponseWriter, r *http.
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// lockCreateErrToStatus maps a LockSystem.Create error to the HTTP status the
|
||||
// LOCK handler returns. ErrNotFound is handled separately by the caller (it
|
||||
// creates the resource and retries), so it falls through to 500 here.
|
||||
func lockCreateErrToStatus(err error) int {
|
||||
switch {
|
||||
case errors.Is(err, ocdavErrors.ErrLocked):
|
||||
return http.StatusLocked
|
||||
case errors.Is(err, ocdavErrors.ErrForbidden):
|
||||
return http.StatusForbidden
|
||||
default:
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
}
|
||||
|
||||
// createEmptyResource creates a 0-byte file at ref so that a LOCK on an
|
||||
// unmapped URL can succeed, as required by RFC 4918 section 7.3. It mirrors
|
||||
// the empty-file path of handlePut, which uses TouchFile for a zero-length
|
||||
// upload. The first return value reports whether the resource was actually
|
||||
// created (CODE_OK); on a concurrent create (CODE_ALREADY_EXISTS) it is false
|
||||
// so the caller locks the existing resource and returns 200 rather than 201.
|
||||
// On failure it returns an (http status, error) pair the LOCK handler can
|
||||
// return directly. Server-side failures (500) are logged, mirroring handlePut.
|
||||
func (s *svc) createEmptyResource(ctx context.Context, ref *provider.Reference) (bool, int, error) {
|
||||
client, err := s.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
appctx.GetLogger(ctx).Error().Err(err).Msg("error selecting next gateway client for lock")
|
||||
return false, http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
res, err := client.TouchFile(ctx, &provider.TouchFileRequest{Ref: ref})
|
||||
if err != nil {
|
||||
appctx.GetLogger(ctx).Error().Err(err).Interface("ref", ref).Msg("error sending grpc touch file request on lock")
|
||||
return false, http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
switch res.GetStatus().GetCode() {
|
||||
case rpc.Code_CODE_OK:
|
||||
return true, 0, nil
|
||||
case rpc.Code_CODE_ALREADY_EXISTS:
|
||||
// The resource appeared concurrently. Lock the existing one (200).
|
||||
return false, 0, nil
|
||||
case rpc.Code_CODE_NOT_FOUND:
|
||||
// A missing intermediate collection. RFC 4918 section 9.10.6 maps this
|
||||
// to 409 Conflict, not 500.
|
||||
return false, http.StatusConflict, ocdavErrors.NewErrFromStatus(res.GetStatus())
|
||||
case rpc.Code_CODE_PERMISSION_DENIED:
|
||||
return false, http.StatusForbidden, ocdavErrors.NewErrFromStatus(res.GetStatus())
|
||||
default:
|
||||
appctx.GetLogger(ctx).Error().Interface("status", res.GetStatus()).Interface("ref", ref).Msg("error touching file on lock")
|
||||
return false, http.StatusInternalServerError, ocdavErrors.NewErrFromStatus(res.GetStatus())
|
||||
}
|
||||
}
|
||||
|
||||
func writeLockInfo(w io.Writer, token string, ld LockDetails) (int, error) {
|
||||
depth := "infinity"
|
||||
if ld.ZeroDepth {
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -31,8 +31,8 @@ import (
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/jellydator/ttlcache/v2"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/opencloud-eu/reva/v2/internal/http/services/owncloud/ocdav/config"
|
||||
"github.com/opencloud-eu/reva/v2/internal/http/services/owncloud/ocdav/net"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
|
||||
Generated
Vendored
+5
-6
@@ -1736,12 +1736,11 @@ func hasPreview(md *provider.ResourceInfo, appendToOK func(p ...prop.PropertyXML
|
||||
}
|
||||
|
||||
func downloadURL(ctx context.Context, log zerolog.Logger, isPublic bool, path string, ls *link.PublicShare, publicURL string, baseURI string, urlSigner signedurl.Signer) string {
|
||||
parts := strings.Split(path, "/")
|
||||
encodedPath, err := url.JoinPath("/", parts...)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to encode the path for the download URL")
|
||||
return ""
|
||||
}
|
||||
// Encode the path with net.EncodePath, the same encoder the resource href
|
||||
// uses, so a filename with a literal "%" or "#" round-trips. url.JoinPath
|
||||
// treats a literal "%" as an escape, so such names 404.
|
||||
// See https://github.com/opencloud-eu/opencloud/issues/2852.
|
||||
encodedPath := net.EncodePath(path)
|
||||
|
||||
switch {
|
||||
case isPublic:
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -22,8 +22,8 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/jellydator/ttlcache/v2"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/opencloud-eu/reva/v2/internal/http/services/owncloud/ocs/config"
|
||||
"github.com/opencloud-eu/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/sharees"
|
||||
"github.com/opencloud-eu/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -25,7 +25,7 @@ import (
|
||||
preferences "github.com/cs3org/go-cs3apis/cs3/preferences/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rhttp/global"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -22,7 +22,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"contrib.go.opencensus.io/exporter/prometheus"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"go.opencensus.io/stats/view"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -26,7 +26,7 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rhttp/global"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ package siteacc
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/siteacc"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/siteacc/config"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ package sysinfo
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ import (
|
||||
appprovider "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1"
|
||||
appregistry "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/app"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/app/provider/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
|
||||
|
||||
+1
-1
@@ -37,8 +37,8 @@ import (
|
||||
appregistry "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1"
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/app"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/app/provider/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
|
||||
package micro
|
||||
|
||||
import "github.com/mitchellh/mapstructure"
|
||||
import "github.com/go-viper/mapstructure/v2"
|
||||
|
||||
type config struct {
|
||||
Namespace string `mapstructure:"namespace"`
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ import (
|
||||
"sync"
|
||||
|
||||
registrypb "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/app"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/app/registry/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ import (
|
||||
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appauth"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appauth/manager/registry"
|
||||
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
|
||||
+61
-192
@@ -2,9 +2,7 @@ package jsoncs3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -14,19 +12,21 @@ import (
|
||||
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/google/uuid"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appauth"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appauth/manager/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/metadatacache"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/utils/metadata"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sethvargo/go-diceware/diceware"
|
||||
"github.com/sethvargo/go-password/password"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type PasswordGenerator interface {
|
||||
@@ -40,9 +40,9 @@ func init() {
|
||||
type manager struct {
|
||||
sync.RWMutex // for lazy initialization
|
||||
mds metadata.Storage
|
||||
store *metadatacache.Store[string, map[string]*apppb.AppPassword]
|
||||
generator PasswordGenerator
|
||||
uTimeUpdateInterval time.Duration
|
||||
updateRetryCount int
|
||||
initialized bool
|
||||
}
|
||||
|
||||
@@ -59,8 +59,6 @@ type config struct {
|
||||
UpdateRetryCount int `mapstructure:"update_retry_count"`
|
||||
}
|
||||
|
||||
type updaterFunc func(map[string]*apppb.AppPassword) (map[string]*apppb.AppPassword, error)
|
||||
|
||||
const tracerName = "jsoncs3"
|
||||
|
||||
func New(m map[string]any) (appauth.Manager, error) {
|
||||
@@ -127,11 +125,17 @@ func New(m map[string]any) (appauth.Manager, error) {
|
||||
}
|
||||
|
||||
func NewWithOptions(mds metadata.Storage, generator PasswordGenerator, uTimeUpdateInterval time.Duration, updateRetries int) (*manager, error) {
|
||||
store := metadatacache.New(metadatacache.Options[string, map[string]*apppb.AppPassword]{
|
||||
Storage: mds,
|
||||
Path: func(userID string) string { return userID + ".json" },
|
||||
Retries: updateRetries,
|
||||
Init: func() map[string]*apppb.AppPassword { return map[string]*apppb.AppPassword{} },
|
||||
})
|
||||
return &manager{
|
||||
mds: mds,
|
||||
store: store,
|
||||
generator: generator,
|
||||
uTimeUpdateInterval: uTimeUpdateInterval,
|
||||
updateRetryCount: updateRetries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -170,7 +174,7 @@ func (m *manager) GenerateAppPassword(ctx context.Context, scope map[string]*aut
|
||||
cTime := &typespb.Timestamp{Seconds: uint64(time.Now().Unix())}
|
||||
|
||||
// For persisting we use the hashed password, since we don't
|
||||
// want to store it in cleartext
|
||||
// want to store it in cleartext.
|
||||
appPass := &apppb.AppPassword{
|
||||
Password: tokenHashed,
|
||||
TokenScope: scope,
|
||||
@@ -183,21 +187,26 @@ func (m *manager) GenerateAppPassword(ctx context.Context, scope map[string]*aut
|
||||
|
||||
id := uuid.New().String()
|
||||
|
||||
err = m.updateWithRetry(ctx, m.updateRetryCount, true, userID, func(a map[string]*apppb.AppPassword) (map[string]*apppb.AppPassword, error) {
|
||||
err = m.store.Update(ctx, userID.GetOpaqueId(), true, func(a map[string]*apppb.AppPassword) (map[string]*apppb.AppPassword, bool, error) {
|
||||
a[id] = appPass
|
||||
return a, nil
|
||||
return a, true, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Msg("failed to store new app password")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Here we need to resplace the hash with the cleartext password again since
|
||||
// the requestor needs to know the cleartext value.
|
||||
appPass.Password = token
|
||||
|
||||
return appPass, nil
|
||||
// Return a fresh AppPassword with the cleartext token for the caller.
|
||||
// Constructing from scratch avoids copying the proto struct (which contains a mutex).
|
||||
return &apppb.AppPassword{
|
||||
Password: token,
|
||||
TokenScope: scope,
|
||||
Label: label,
|
||||
Expiration: expiration,
|
||||
Ctime: cTime,
|
||||
Utime: cTime,
|
||||
User: userID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListAppPasswords lists the application passwords created by a user.
|
||||
@@ -216,23 +225,26 @@ func (m *manager) ListAppPasswords(ctx context.Context) ([]*apppb.AppPassword, e
|
||||
} else {
|
||||
return nil, errtypes.BadRequest("no user in context")
|
||||
}
|
||||
_, userAppPasswords, err := m.getUserAppPasswords(ctx, userID)
|
||||
|
||||
unlock := m.store.Lock(userID.GetOpaqueId())
|
||||
defer unlock()
|
||||
|
||||
passwords, ok, err := m.store.Get(ctx, userID.GetOpaqueId())
|
||||
if err != nil {
|
||||
if _, ok := err.(errtypes.NotFound); ok {
|
||||
return []*apppb.AppPassword{}, nil
|
||||
}
|
||||
log.Error().Err(err).Msg("getUserAppPasswords failed")
|
||||
log.Error().Err(err).Msg("store.Get failed")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userAppPasswordSlice := make([]*apppb.AppPassword, 0, len(userAppPasswords))
|
||||
|
||||
for id, p := range userAppPasswords {
|
||||
p.Password = id
|
||||
userAppPasswordSlice = append(userAppPasswordSlice, p)
|
||||
if !ok {
|
||||
return []*apppb.AppPassword{}, nil
|
||||
}
|
||||
|
||||
return userAppPasswordSlice, nil
|
||||
result := make([]*apppb.AppPassword, 0, len(passwords))
|
||||
for id, p := range passwords {
|
||||
pw := proto.Clone(p).(*apppb.AppPassword)
|
||||
pw.Password = id
|
||||
result = append(result, pw)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// InvalidateAppPassword invalidates a generated password.
|
||||
@@ -253,16 +265,16 @@ func (m *manager) InvalidateAppPassword(ctx context.Context, secretOrId string)
|
||||
return errtypes.BadRequest("no user in context")
|
||||
}
|
||||
|
||||
updater := func(a map[string]*apppb.AppPassword) (map[string]*apppb.AppPassword, error) {
|
||||
err := m.store.Update(ctx, userID.GetOpaqueId(), false, func(a map[string]*apppb.AppPassword) (map[string]*apppb.AppPassword, bool, error) {
|
||||
// Allow deleting a token using the ID inside the password property. This is needed because of
|
||||
// some shortcomings of the CS3 APIs. On the API level tokens don't have IDs
|
||||
// some shortcomings of the CS3 APIs. On the API level tokens don't have IDs;
|
||||
// ListAppPasswords in this backend returns the ID as the password value.
|
||||
if _, ok := a[secretOrId]; ok {
|
||||
delete(a, secretOrId)
|
||||
return a, nil
|
||||
return a, true, nil
|
||||
}
|
||||
|
||||
// Check if the supplied parameter matches any of the stored password tokens
|
||||
// Check if the supplied parameter matches any of the stored password tokens.
|
||||
for key, pw := range a {
|
||||
ok, err := argon2id.ComparePasswordAndHash(secretOrId, pw.Password)
|
||||
switch {
|
||||
@@ -270,15 +282,13 @@ func (m *manager) InvalidateAppPassword(ctx context.Context, secretOrId string)
|
||||
log.Debug().Err(err).Msg("Error comparing password and hash")
|
||||
case ok:
|
||||
delete(a, key)
|
||||
return a, nil
|
||||
return a, true, nil
|
||||
}
|
||||
}
|
||||
return a, errtypes.NotFound("password not found")
|
||||
}
|
||||
|
||||
err := m.updateWithRetry(ctx, m.updateRetryCount, false, userID, updater)
|
||||
return a, false, errtypes.NotFound("password not found")
|
||||
})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("getUserAppPasswords failed")
|
||||
log.Error().Err(err).Msg("store.Update failed")
|
||||
return errtypes.NotFound("password not found")
|
||||
}
|
||||
return nil
|
||||
@@ -295,13 +305,12 @@ func (m *manager) GetAppPassword(ctx context.Context, user *userpb.UserId, secre
|
||||
return nil, err
|
||||
}
|
||||
|
||||
errUpdateSkipped := errors.New("update skipped")
|
||||
|
||||
var (
|
||||
matchedPw *apppb.AppPassword
|
||||
matchedID string
|
||||
)
|
||||
updater := func(a map[string]*apppb.AppPassword) (map[string]*apppb.AppPassword, error) {
|
||||
|
||||
err := m.store.Update(ctx, user.GetOpaqueId(), false, func(a map[string]*apppb.AppPassword) (map[string]*apppb.AppPassword, bool, error) {
|
||||
matchedPw = nil
|
||||
for id, pw := range a {
|
||||
ok, err := argon2id.ComparePasswordAndHash(secret, pw.Password)
|
||||
@@ -312,35 +321,31 @@ func (m *manager) GetAppPassword(ctx context.Context, user *userpb.UserId, secre
|
||||
// password found
|
||||
if pw.Expiration != nil && pw.Expiration.Seconds != 0 && uint64(time.Now().Unix()) > pw.Expiration.Seconds {
|
||||
log.Debug().Str("AppPasswordId", id).Msg("password expired")
|
||||
return nil, errtypes.NotFound("password not found")
|
||||
return nil, false, errtypes.NotFound("password not found")
|
||||
}
|
||||
|
||||
matchedPw = pw
|
||||
matchedID = id
|
||||
// password not expired
|
||||
// Updating the Utime will cause an Upload for every single GetAppPassword request. We are limiting this to one
|
||||
// update per 'uTimeUpdateInterval' (default 5 min) otherwise this backend will become unusable.
|
||||
if time.Since(utils.TSToTime(pw.Utime)) > m.uTimeUpdateInterval {
|
||||
a[id].Utime = utils.TSNow()
|
||||
return a, nil
|
||||
return a, true, nil
|
||||
}
|
||||
return a, errUpdateSkipped
|
||||
return a, false, nil
|
||||
}
|
||||
}
|
||||
return nil, false, errtypes.NotFound("password not found")
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errtypes.NotFound("password not found")
|
||||
}
|
||||
|
||||
err := m.updateWithRetry(ctx, m.updateRetryCount, false, user, updater)
|
||||
switch {
|
||||
case err == nil:
|
||||
fallthrough
|
||||
case errors.Is(err, errUpdateSkipped):
|
||||
// Don't return the hashed password, put the ID into the password field
|
||||
matchedPw.Password = matchedID
|
||||
return matchedPw, nil
|
||||
}
|
||||
|
||||
return nil, errtypes.NotFound("password not found")
|
||||
// Return a clone with the ID in the password field so the cached entry
|
||||
// is not corrupted.
|
||||
result := proto.Clone(matchedPw).(*apppb.AppPassword)
|
||||
result.Password = matchedID
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (m *manager) initialize(ctx context.Context) error {
|
||||
@@ -372,142 +377,6 @@ func (m *manager) initialize(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *manager) updateWithRetry(ctx context.Context, retries int, createIfNotFound bool, userid *userpb.UserId, updater updaterFunc) error {
|
||||
log := appctx.GetLogger(ctx)
|
||||
_, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "initialize")
|
||||
defer span.End()
|
||||
|
||||
retry := true
|
||||
var (
|
||||
etag string
|
||||
userAppPasswords map[string]*apppb.AppPassword
|
||||
err error
|
||||
)
|
||||
|
||||
// retry for the specified number of times, then error out
|
||||
for i := 0; i < retries && retry; i++ {
|
||||
if i > 0 {
|
||||
// if we're retrying, wait a bit before the next try
|
||||
jitter := time.Duration(rand.Int63n(int64(100 * time.Millisecond)))
|
||||
time.Sleep(jitter + 100*time.Millisecond)
|
||||
}
|
||||
|
||||
etag, userAppPasswords, err = m.getUserAppPasswords(ctx, userid)
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
// empty
|
||||
case errtypes.NotFound:
|
||||
if createIfNotFound {
|
||||
userAppPasswords = map[string]*apppb.AppPassword{}
|
||||
} else {
|
||||
log.Debug().Err(err).Msg("getUserAppPasswords failed (not found)")
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, "downloading app tokens failed")
|
||||
return err
|
||||
}
|
||||
case errtypes.TooEarly:
|
||||
// Ideally this should never happen as we disable asynchronous uploads for the metadata storage.
|
||||
log.Debug().Err(err).Int("try", i).Msg("getUserAppPasswords failed (too early, retrying)")
|
||||
retry = true
|
||||
continue
|
||||
default:
|
||||
log.Debug().Err(err).Msg("getUserAppPasswords failed")
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, "downloading app tokens failed")
|
||||
return err
|
||||
}
|
||||
|
||||
userAppPasswords, err = updater(userAppPasswords)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.updateUserAppPassword(ctx, userid, userAppPasswords, etag)
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
retry = false
|
||||
case errtypes.Aborted:
|
||||
log.Debug().Err(err).Int("attempt", i).Msg("updateUserAppPassword failed (retrying)")
|
||||
retry = true
|
||||
default:
|
||||
log.Debug().Err(err).Int("attempt", i).Msg("updateUserAppPassword failed (not retrying)")
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
if retry {
|
||||
log.Debug().Err(err).Msg("updateUserAppPassword failed")
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, "updating app token failed")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *manager) updateUserAppPassword(ctx context.Context, userid *userpb.UserId, appPasswords map[string]*apppb.AppPassword, ifMatchEtag string) error {
|
||||
log := appctx.GetLogger(ctx)
|
||||
ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "getUserAppPasswords")
|
||||
jsonPath := userAppTokenJSONPath(userid)
|
||||
|
||||
pwBytes, err := json.Marshal(appPasswords)
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
ur := metadata.UploadRequest{
|
||||
Path: jsonPath,
|
||||
Content: pwBytes,
|
||||
IfMatchEtag: ifMatchEtag,
|
||||
}
|
||||
|
||||
// If there is no etag, make sure to only upload if the file wasn't craeted yet
|
||||
if ifMatchEtag == "" {
|
||||
ur.IfNoneMatch = []string{"*"}
|
||||
}
|
||||
_, err = m.mds.Upload(ctx, ur)
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
log.Debug().Err(err).Msg("failed to upload AppPasswword")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *manager) getUserAppPasswords(ctx context.Context, userid *userpb.UserId) (string, map[string]*apppb.AppPassword, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "getUserAppPasswords")
|
||||
jsonPath := userAppTokenJSONPath(userid)
|
||||
dlreq := metadata.DownloadRequest{
|
||||
Path: jsonPath,
|
||||
}
|
||||
|
||||
var userAppPasswords = map[string]*apppb.AppPassword{}
|
||||
dlres, err := m.mds.Download(ctx, dlreq)
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
err = json.Unmarshal(dlres.Content, &userAppPasswords)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("unmarshaling app tokens failed")
|
||||
return "", nil, err
|
||||
}
|
||||
case errtypes.NotFound:
|
||||
return "", nil, errtypes.NotFound("password not found")
|
||||
default:
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, "downloading app tokens failed")
|
||||
return "", nil, err
|
||||
}
|
||||
return dlres.Etag, userAppPasswords, nil
|
||||
}
|
||||
|
||||
func userAppTokenJSONPath(userID *userpb.UserId) string {
|
||||
return userID.GetOpaqueId() + ".json"
|
||||
}
|
||||
|
||||
type randomPassword struct {
|
||||
Strength int `mapstructure:"token_strength"`
|
||||
}
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ import (
|
||||
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
|
||||
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth/manager/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ import (
|
||||
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
|
||||
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth/manager/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth/scope"
|
||||
|
||||
+1
-1
@@ -27,8 +27,8 @@ import (
|
||||
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/google/uuid"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth/manager/registry"
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ import (
|
||||
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth/manager/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth/scope"
|
||||
|
||||
+1
-1
@@ -32,8 +32,8 @@ import (
|
||||
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
|
||||
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/juliangruber/go-intersect"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth/manager/registry"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -28,7 +28,7 @@ import (
|
||||
rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
|
||||
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth/manager/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth/scope"
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -7,7 +7,7 @@ import (
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth/manager/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth/scope"
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ import (
|
||||
"context"
|
||||
|
||||
registrypb "github.com/cs3org/go-cs3apis/cs3/auth/registry/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth/registry/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
|
||||
+1
-1
@@ -34,8 +34,8 @@ import (
|
||||
|
||||
datatx "github.com/cs3org/go-cs3apis/cs3/tx/v1beta1"
|
||||
typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/google/uuid"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
txdriver "github.com/opencloud-eu/reva/v2/pkg/datatx"
|
||||
registry "github.com/opencloud-eu/reva/v2/pkg/datatx/manager/registry"
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ import (
|
||||
|
||||
grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/group"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/group/manager/registry"
|
||||
|
||||
+1
-1
@@ -27,8 +27,8 @@ import (
|
||||
grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/google/uuid"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/group"
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ import (
|
||||
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/publicshare"
|
||||
|
||||
Loaded 100 of 146 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user