Use inotifywait module from upstream

This commit is contained in:
Klaas Freitag
2026-06-09 15:39:21 +02:00
parent 0f97fc5e60
commit 41a9ad809d
128 changed files with 1352 additions and 7395 deletions

10
go.mod
View File

@@ -33,7 +33,7 @@ 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
@@ -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
@@ -288,7 +288,6 @@ require (
github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/minio-go/v7 v7.1.0 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/go-archive v0.2.0 // 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

18
go.sum
View File

@@ -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=
@@ -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 h1:1MNSZj8e2DEMGrzORvTlyCrwYQPnCgtRuPQT+bzD4DM=
github.com/opencloud-eu/reva/v2 v2.46.2/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=

View File

@@ -30,3 +30,4 @@ _testmain.go
cover.html
README.html
.idea
.claude/

View File

@@ -53,3 +53,8 @@ linters:
- varnamelen
- wrapcheck
- wsl
- gomodguard
settings:
govet:
disable:
- inline

View File

@@ -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 |

View File

@@ -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())
}
}

View File

@@ -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
}
}

View File

@@ -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": {},
}

View File

@@ -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.

View File

@@ -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
}
}

View File

@@ -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]$"

View File

@@ -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",

View File

@@ -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()

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -1,46 +0,0 @@
# mapstructure [![Godoc](https://godoc.org/github.com/mitchellh/mapstructure?status.svg)](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.

View File

@@ -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
}
}

View File

@@ -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())
}
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -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>

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"
)

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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:

View File

@@ -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 {

View File

@@ -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"

View File

@@ -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:

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"`

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"`
}

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"
)

View File

@@ -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"

View File

@@ -25,8 +25,8 @@ import (
"net"
"sort"
"github.com/go-viper/mapstructure/v2"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
"github.com/mitchellh/mapstructure"
"github.com/opencloud-eu/reva/v2/internal/grpc/interceptors/appctx"
"github.com/opencloud-eu/reva/v2/internal/grpc/interceptors/auth"
"github.com/opencloud-eu/reva/v2/internal/grpc/interceptors/log"

View File

@@ -24,7 +24,7 @@ import (
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/mitchellh/mapstructure"
"github.com/go-viper/mapstructure/v2"
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
"github.com/pkg/errors"
"github.com/rs/zerolog"

View File

@@ -26,7 +26,7 @@ import (
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/mitchellh/mapstructure"
"github.com/go-viper/mapstructure/v2"
"github.com/pkg/errors"
"github.com/rs/zerolog"

View File

@@ -27,7 +27,7 @@ import (
"runtime"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/mitchellh/mapstructure"
"github.com/go-viper/mapstructure/v2"
"github.com/pkg/errors"
"github.com/rs/zerolog"
tusd "github.com/tus/tusd/v2/pkg/handler"

View File

@@ -27,7 +27,7 @@ import (
"sort"
"time"
"github.com/mitchellh/mapstructure"
"github.com/go-viper/mapstructure/v2"
"github.com/opencloud-eu/reva/v2/internal/http/interceptors/appctx"
"github.com/opencloud-eu/reva/v2/internal/http/interceptors/auth"
"github.com/opencloud-eu/reva/v2/internal/http/interceptors/log"

View File

@@ -29,8 +29,8 @@ import (
rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/go-viper/mapstructure/v2"
"github.com/google/uuid"
"github.com/mitchellh/mapstructure"
"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"

View File

@@ -23,7 +23,7 @@ import (
"os"
"sync"
"github.com/mitchellh/mapstructure"
"github.com/go-viper/mapstructure/v2"
)
var (

View File

@@ -34,7 +34,7 @@ import (
cephfs2 "github.com/ceph/go-ceph/cephfs"
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/mitchellh/mapstructure"
"github.com/go-viper/mapstructure/v2"
"github.com/pkg/errors"
"github.com/rs/zerolog"

View File

@@ -19,7 +19,7 @@
package decomposeds3
import (
"github.com/mitchellh/mapstructure"
"github.com/go-viper/mapstructure/v2"
"github.com/pkg/errors"
)

View File

@@ -19,7 +19,7 @@
package eos
import (
"github.com/mitchellh/mapstructure"
"github.com/go-viper/mapstructure/v2"
"github.com/opencloud-eu/reva/v2/pkg/events"
"github.com/opencloud-eu/reva/v2/pkg/storage"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/registry"

View File

@@ -19,7 +19,7 @@
package eosgrpc
import (
"github.com/mitchellh/mapstructure"
"github.com/go-viper/mapstructure/v2"
"github.com/opencloud-eu/reva/v2/pkg/events"
"github.com/opencloud-eu/reva/v2/pkg/storage"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/registry"

View File

@@ -19,7 +19,7 @@
package eosgrpchome
import (
"github.com/mitchellh/mapstructure"
"github.com/go-viper/mapstructure/v2"
"github.com/opencloud-eu/reva/v2/pkg/events"
"github.com/opencloud-eu/reva/v2/pkg/storage"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/registry"

View File

@@ -19,7 +19,7 @@
package eoshome
import (
"github.com/mitchellh/mapstructure"
"github.com/go-viper/mapstructure/v2"
"github.com/opencloud-eu/reva/v2/pkg/events"
"github.com/opencloud-eu/reva/v2/pkg/storage"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/registry"

View File

@@ -19,7 +19,7 @@
package local
import (
"github.com/mitchellh/mapstructure"
"github.com/go-viper/mapstructure/v2"
"github.com/opencloud-eu/reva/v2/pkg/events"
"github.com/opencloud-eu/reva/v2/pkg/storage"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/registry"

View File

@@ -19,7 +19,7 @@
package localhome
import (
"github.com/mitchellh/mapstructure"
"github.com/go-viper/mapstructure/v2"
"github.com/opencloud-eu/reva/v2/pkg/events"
"github.com/opencloud-eu/reva/v2/pkg/storage"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/registry"

View File

@@ -31,7 +31,7 @@ import (
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
types "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"

View File

@@ -39,7 +39,7 @@ import (
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/mitchellh/mapstructure"
"github.com/go-viper/mapstructure/v2"
"github.com/pkg/errors"
"github.com/pkg/xattr"
"github.com/rs/zerolog"

View File

@@ -21,7 +21,7 @@ package options
import (
"time"
"github.com/mitchellh/mapstructure"
"github.com/go-viper/mapstructure/v2"
decomposedoptions "github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/options"
"github.com/pkg/errors"
)

Some files were not shown because too many files have changed in this diff Show More